From 30ca3b674e21cb1ccfae77ff6b57878a72ef178f Mon Sep 17 00:00:00 2001 From: Mykhailo Sizov Date: Thu, 13 Oct 2022 15:23:47 +0300 Subject: [PATCH] feat: Credential Status API - update vc status Signed-off-by: Mykhailo Sizov --- cmd/vc-rest/startcmd/start.go | 8 +- pkg/doc/vc/crypto/crypto.go | 20 +- pkg/doc/vc/crypto/crypto_test.go | 18 +- pkg/doc/vc/signer.go | 1 + pkg/restapi/v1/issuer/controller.go | 52 ++++ pkg/restapi/v1/issuer/controller_test.go | 261 ++++++++++++++++++ .../credentialstatus_service.go | 55 ++-- .../credentialstatus_service_test.go | 99 +++++++ .../didconfiguration_service.go | 41 ++- .../didconfiguration_service_test.go | 32 ++- .../issuecredential_service.go | 52 +--- .../issuecredential_service_test.go | 13 +- .../revocation/revocation_service.go | 3 +- pkg/storage/mongodbprovider/csl.go | 14 +- test/bdd/features/vc_v1_e2e_api.feature | 2 + test/bdd/fixtures/profile/profiles.json | 14 +- test/bdd/pkg/v1/model/model.go | 31 +++ test/bdd/pkg/v1/vc/credential.go | 138 +++++++-- test/bdd/pkg/v1/vc/stress_steps.go | 6 +- test/bdd/pkg/v1/vc/vc_steps.go | 8 +- 20 files changed, 726 insertions(+), 142 deletions(-) diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 6b3b457da..0dc6b323e 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -275,10 +275,10 @@ func buildEchoHandler(conf *Configuration, cmd *cobra.Command) (*echo.Echo, erro verifierv1.RegisterHandlers(e, verifierController) didConfigSvc := didconfiguration.New(&didconfiguration.Config{ - VerifierProfileService: verifierProfileSvc, - IssuerProfileService: issuerProfileSvc, - IssuerCredentialService: issueCredentialSvc, - KmsRegistry: kmsRegistry, + VerifierProfileService: verifierProfileSvc, + IssuerProfileService: issuerProfileSvc, + Crypto: vcCrypto, + KmsRegistry: kmsRegistry, }) if conf.StartupParameters.devMode { diff --git a/pkg/doc/vc/crypto/crypto.go b/pkg/doc/vc/crypto/crypto.go index e97917ca9..51059b32d 100644 --- a/pkg/doc/vc/crypto/crypto.go +++ b/pkg/doc/vc/crypto/crypto.go @@ -150,8 +150,20 @@ type Crypto struct { documentLoader ld.DocumentLoader } -// SignCredentialLDP adds verifiable.LinkedDataProofContext to the vc. -func (c *Crypto) SignCredentialLDP( +func (c *Crypto) SignCredential( + signerData *vc.Signer, vc *verifiable.Credential, opts ...SigningOpts) (*verifiable.Credential, error) { + switch signerData.Format { + case vcsverifiable.Jwt: + return c.signCredentialJWT(signerData, vc, opts...) + case vcsverifiable.Ldp: + return c.signCredentialLDP(signerData, vc, opts...) + default: + return nil, fmt.Errorf("unknown signature format %s", signerData.Format) + } +} + +// signCredentialLDP adds verifiable.LinkedDataProofContext to the VC. +func (c *Crypto) signCredentialLDP( signerData *vc.Signer, vc *verifiable.Credential, opts ...SigningOpts) (*verifiable.Credential, error) { signOpts := &signingOpts{} // apply opts @@ -178,8 +190,8 @@ func (c *Crypto) SignCredentialLDP( return vc, nil } -// SignCredentialJWT returns vc in JWT format including the signature section. -func (c *Crypto) SignCredentialJWT( +// signCredentialJWT returns vc in JWT format including the signature section. +func (c *Crypto) signCredentialJWT( signerData *vc.Signer, vc *verifiable.Credential, opts ...SigningOpts) (*verifiable.Credential, error) { signOpts := &signingOpts{} // apply opts diff --git a/pkg/doc/vc/crypto/crypto_test.go b/pkg/doc/vc/crypto/crypto_test.go index 92fc2e0b2..efd9b7577 100644 --- a/pkg/doc/vc/crypto/crypto_test.go +++ b/pkg/doc/vc/crypto/crypto_test.go @@ -41,7 +41,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit testutil.DocumentLoader(t), ) - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( getTestSigner(), &verifiable.Credential{ID: "http://example.edu/credentials/1872"}) require.NoError(t, err) require.Equal(t, 1, len(signedVC.Proofs)) @@ -181,7 +181,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit vcSigner = tc.vcSigner } - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( vcSigner, &verifiable.Credential{ID: "http://example.edu/credentials/1872"}, tc.signingOpts...) @@ -227,7 +227,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit ) p := getTestSigner() p.Creator = "wrongValue" - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( p, &verifiable.Credential{ID: "http://example.edu/credentials/1872"}) require.Error(t, err) require.Contains(t, err.Error(), "verificationMethod value wrongValue should be in did#keyID format") @@ -239,7 +239,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit &vdrmock.MockVDRegistry{ResolveValue: createDIDDoc("did:trustbloc:abc")}, testutil.DocumentLoader(t), ) - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( getTestSignerWithCrypto( &cryptomock.Crypto{SignErr: fmt.Errorf("failed to sign")}), &verifiable.Credential{ID: "http://example.edu/credentials/1872"}) @@ -254,7 +254,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit p := getTestSigner() - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( p, &verifiable.Credential{ID: "http://example.edu/credentials/1872"}, WithPurpose("invalid")) require.Error(t, err) @@ -268,7 +268,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit p := getTestSigner() - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( p, &verifiable.Credential{ID: "http://example.edu/credentials/1872"}, WithPurpose(CapabilityInvocation)) require.NoError(t, err) @@ -281,7 +281,7 @@ func TestCrypto_SignCredentialLDP(t *testing.T) { //nolint:gocognit p := getTestSigner() - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( p, &verifiable.Credential{ID: "http://example.edu/credentials/1872"}, WithPurpose(CapabilityInvocation)) require.NoError(t, err) @@ -296,7 +296,7 @@ func TestCrypto_SignCredentialBBS(t *testing.T) { testutil.DocumentLoader(t), ) - signedVC, err := c.SignCredentialLDP( + signedVC, err := c.signCredentialLDP( &vc.Signer{ DID: "did:trustbloc:abc", SignatureType: "BbsBlsSignature2020", @@ -683,7 +683,7 @@ func TestCrypto_SignCredentialJWT(t *testing.T) { vdr: tt.fields.getVDR(), documentLoader: testutil.DocumentLoader(t), } - got, err := c.SignCredentialJWT(tt.args.signerData, tt.args.getVC(), tt.args.opts...) + got, err := c.signCredentialJWT(tt.args.signerData, tt.args.getVC(), tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("SignCredentialJWT() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/doc/vc/signer.go b/pkg/doc/vc/signer.go index 523cc4ab0..cc96c606c 100644 --- a/pkg/doc/vc/signer.go +++ b/pkg/doc/vc/signer.go @@ -27,6 +27,7 @@ type Signer struct { Creator string SignatureType vcsverifiable.SignatureType KeyType kms.KeyType + Format vcsverifiable.Format // VC format - LDP/JWT. SignatureRepresentation verifiable.SignatureRepresentation // For LDP only. KMS keyManager } diff --git a/pkg/restapi/v1/issuer/controller.go b/pkg/restapi/v1/issuer/controller.go index 54c17923b..be7442f2b 100644 --- a/pkg/restapi/v1/issuer/controller.go +++ b/pkg/restapi/v1/issuer/controller.go @@ -13,6 +13,7 @@ import ( "context" "errors" "fmt" + "net/http" "strings" "time" @@ -69,6 +70,7 @@ type oidc4VCService interface { type vcStatusManager interface { GetRevocationListVC(id string) (*verifiable.Credential, error) GetCredentialStatusURL(issuerProfileURL, issuerProfileID, statusID string) (string, error) + UpdateVCStatus(signer *vc.Signer, profileName, CredentialID, status string) error } type Config struct { @@ -324,5 +326,55 @@ func (c *Controller) GetCredentialsStatus(ctx echo.Context, profileID string, st // PostCredentialsStatus updates credential status. // POST /issuer/profiles/{profileID}/credentials/status. func (c *Controller) PostCredentialsStatus(ctx echo.Context, profileID string) error { + var body UpdateCredentialStatusRequest + + if err := util.ReadBody(ctx, &body); err != nil { + return err + } + + if err := c.updateCredentialStatus(ctx, &body, profileID); err != nil { + return err + } + + return ctx.NoContent(http.StatusOK) +} + +func (c *Controller) updateCredentialStatus(ctx echo.Context, body *UpdateCredentialStatusRequest, + profileID string) error { + oidcOrgID, err := util.GetOrgIDFromOIDC(ctx) + if err != nil { + return err + } + + profile, err := c.accessOIDCProfile(profileID, oidcOrgID) + if err != nil { + return err + } + + keyManager, err := c.kmsRegistry.GetKeyManager(profile.KMSConfig) + if err != nil { + return fmt.Errorf("failed to get kms: %w", err) + } + + if body.CredentialStatus.Type != credentialstatus.StatusList2021Entry { + return resterr.NewValidationError(resterr.InvalidValue, "CredentialStatus.Type", + fmt.Errorf("credential status %s not supported", body.CredentialStatus.Type)) + } + + signer := &vc.Signer{ + Format: profile.VCConfig.Format, + DID: profile.SigningDID.DID, + Creator: profile.SigningDID.Creator, + SignatureType: profile.VCConfig.SigningAlgorithm, + KeyType: profile.VCConfig.KeyType, + KMS: keyManager, + SignatureRepresentation: profile.VCConfig.SignatureRepresentation, + } + + err = c.vcStatusManager.UpdateVCStatus(signer, profile.Name, body.CredentialID, body.CredentialStatus.Status) + if err != nil { + return resterr.NewSystemError("VCStatusManager", "UpdateVCStatus", err) + } + return nil } diff --git a/pkg/restapi/v1/issuer/controller_test.go b/pkg/restapi/v1/issuer/controller_test.go index ace795569..29b4acb28 100644 --- a/pkg/restapi/v1/issuer/controller_test.go +++ b/pkg/restapi/v1/issuer/controller_test.go @@ -639,6 +639,267 @@ func TestController_PostIssuerProfilesProfileIDInteractionsInitiateOidc(t *testi }) } +func TestController_PostCredentialsStatus(t *testing.T) { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + keyManager := mocks.NewMockVCSKeyManager(gomock.NewController(t)) + keyManager.EXPECT().SupportedKeyTypes().AnyTimes().Return(ariesSupportedKeyTypes) + + kmsRegistry := NewMockKMSRegistry(gomock.NewController(t)) + kmsRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(keyManager, nil) + + mockVCStatusManager := NewMockVCStatusManager(gomock.NewController(t)) + mockVCStatusManager.EXPECT().UpdateVCStatus( + gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + t.Run("Success", func(t *testing.T) { + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(&profileapi.Issuer{ + OrganizationID: orgID, + ID: "testId", + VCConfig: &profileapi.VCConfig{}, + SigningDID: &profileapi.SigningDID{}, + }, nil) + + controller := NewController(&Config{ + KMSRegistry: kmsRegistry, + ProfileSvc: mockProfileSvc, + DocumentLoader: testutil.DocumentLoader(t), + VcStatusManager: mockVCStatusManager, + }) + + c := echoContext(withRequestBody( + []byte(`{"credentialID": "1","credentialStatus":{"type":"StatusList2021Entry"}}`))) + + err := controller.PostCredentialsStatus(c, "testId") + require.NoError(t, err) + }) + + t.Run("Failed", func(t *testing.T) { + controller := NewController(&Config{}) + c := echoContext(withRequestBody([]byte("abc"))) + err := controller.PostCredentialsStatus(c, "testId") + + requireValidationError(t, "invalid-value", "requestBody", err) + }) +} + +func TestController_UpdateCredentialStatus(t *testing.T) { + t.Run("Success", func(t *testing.T) { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(&profileapi.Issuer{ + OrganizationID: orgID, + ID: "testId", + VCConfig: &profileapi.VCConfig{}, + SigningDID: &profileapi.SigningDID{}, + }, nil) + + kmsRegistry := NewMockKMSRegistry(gomock.NewController(t)) + kmsRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(nil, nil) + + mockVCStatusManager := NewMockVCStatusManager(gomock.NewController(t)) + mockVCStatusManager.EXPECT().UpdateVCStatus( + gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + + controller := NewController(&Config{ + KMSRegistry: kmsRegistry, + ProfileSvc: mockProfileSvc, + DocumentLoader: testutil.DocumentLoader(t), + VcStatusManager: mockVCStatusManager, + }) + + body := &UpdateCredentialStatusRequest{ + CredentialID: "1", + CredentialStatus: CredentialStatus{ + Type: "StatusList2021Entry", + }, + } + + err := controller.updateCredentialStatus(echoContext(), body, "testId") + require.NoError(t, err) + }) + + t.Run("Failed", func(t *testing.T) { + type fields struct { + getProfileSvc func() profileService + getVCStatusManager func() vcStatusManager + getKMSRegistry func() kmsRegistry + } + type args struct { + ctx echo.Context + body *UpdateCredentialStatusRequest + profileID string + } + tests := []struct { + name string + fields fields + args args + wantErr string + }{ + { + name: "Missing authorization", + fields: fields{ + getProfileSvc: func() profileService { + return nil + }, + getVCStatusManager: func() vcStatusManager { + return nil + }, + getKMSRegistry: func() kmsRegistry { + return nil + }, + }, + args: args{ + ctx: echoContext(withOrgID("")), + body: &UpdateCredentialStatusRequest{}, + profileID: "test", + }, + wantErr: "missing authorization", + }, + { + name: "Profile doesn't exist", + fields: fields{ + getProfileSvc: func() profileService { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(nil, errors.New("not found")) + return mockProfileSvc + }, + getVCStatusManager: func() vcStatusManager { + return nil + }, + getKMSRegistry: func() kmsRegistry { + return nil + }, + }, + args: args{ + ctx: echoContext(), + body: &UpdateCredentialStatusRequest{}, + profileID: "testId", + }, + wantErr: "profile with given id testId, dosn't exists", + }, + { + name: "KMS registry error", + fields: fields{ + getProfileSvc: func() profileService { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(&profileapi.Issuer{ + OrganizationID: orgID, + ID: "testId", + VCConfig: &profileapi.VCConfig{}, + SigningDID: &profileapi.SigningDID{}, + }, nil) + return mockProfileSvc + }, + getKMSRegistry: func() kmsRegistry { + kmsRegistry := NewMockKMSRegistry(gomock.NewController(t)) + kmsRegistry.EXPECT().GetKeyManager( + gomock.Any()).AnyTimes().Return(nil, errors.New("some error")) + return kmsRegistry + }, + getVCStatusManager: func() vcStatusManager { + return nil + }, + }, + args: args{ + ctx: echoContext(), + body: &UpdateCredentialStatusRequest{}, + profileID: "testId", + }, + wantErr: "failed to get kms", + }, + { + name: "Not supported cred type", + fields: fields{ + getProfileSvc: func() profileService { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(&profileapi.Issuer{ + OrganizationID: orgID, + ID: "testId", + VCConfig: &profileapi.VCConfig{}, + SigningDID: &profileapi.SigningDID{}, + }, nil) + return mockProfileSvc + }, + getKMSRegistry: func() kmsRegistry { + kmsRegistry := NewMockKMSRegistry(gomock.NewController(t)) + kmsRegistry.EXPECT().GetKeyManager( + gomock.Any()).AnyTimes().Return(nil, nil) + return kmsRegistry + }, + getVCStatusManager: func() vcStatusManager { + return nil + }, + }, + args: args{ + ctx: echoContext(), + body: &UpdateCredentialStatusRequest{ + CredentialStatus: CredentialStatus{ + Type: "invalid", + }, + }, + profileID: "testId", + }, + wantErr: "credential status invalid not supported", + }, + { + name: "UpdateVCStatus error", + fields: fields{ + getProfileSvc: func() profileService { + mockProfileSvc := NewMockProfileService(gomock.NewController(t)) + mockProfileSvc.EXPECT().GetProfile("testId").Times(1). + Return(&profileapi.Issuer{ + OrganizationID: orgID, + ID: "testId", + VCConfig: &profileapi.VCConfig{}, + SigningDID: &profileapi.SigningDID{}, + }, nil) + return mockProfileSvc + }, + getKMSRegistry: func() kmsRegistry { + kmsRegistry := NewMockKMSRegistry(gomock.NewController(t)) + kmsRegistry.EXPECT().GetKeyManager( + gomock.Any()).AnyTimes().Return(nil, nil) + return kmsRegistry + }, + getVCStatusManager: func() vcStatusManager { + mockVCStatusManager := NewMockVCStatusManager(gomock.NewController(t)) + mockVCStatusManager.EXPECT().UpdateVCStatus( + gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("some error")) + return mockVCStatusManager + }, + }, + args: args{ + ctx: echoContext(), + body: &UpdateCredentialStatusRequest{ + CredentialStatus: CredentialStatus{ + Type: "StatusList2021Entry", + }, + }, + profileID: "testId", + }, + wantErr: "system-error[VCStatusManager, UpdateVCStatus]: some error", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Controller{ + profileSvc: tt.fields.getProfileSvc(), + vcStatusManager: tt.fields.getVCStatusManager(), + kmsRegistry: tt.fields.getKMSRegistry(), + } + err := c.updateCredentialStatus(tt.args.ctx, tt.args.body, tt.args.profileID) + require.Error(t, err) + require.ErrorContains(t, err, tt.wantErr) + }) + } + }) +} + type options struct { orgID string requestBody []byte diff --git a/pkg/service/credentialstatus/credentialstatus_service.go b/pkg/service/credentialstatus/credentialstatus_service.go index 789e58bb9..1f516cf25 100644 --- a/pkg/service/credentialstatus/credentialstatus_service.go +++ b/pkg/service/credentialstatus/credentialstatus_service.go @@ -61,13 +61,14 @@ const ( ) type crypto interface { - SignCredentialLDP(dataProfile *vc.Signer, vc *verifiable.Credential, + SignCredential(signerData *vc.Signer, vc *verifiable.Credential, opts ...vccrypto.SigningOpts) (*verifiable.Credential, error) } // Service implement spec https://w3c-ccg.github.io/vc-status-rl-2020/. type Service struct { - store vcsstorage.CSLStore + cslStore vcsstorage.CSLStore + vcStore vcsstorage.VCStore listSize int crypto crypto documentLoader ld.DocumentLoader @@ -83,12 +84,17 @@ type credentialSubject struct { // New returns new Credential Status List. func New(provider vcsstorage.Provider, listSize int, c crypto, loader ld.DocumentLoader) (*Service, error) { - store, err := provider.OpenCSLStore() + cslStore, err := provider.OpenCSLStore() if err != nil { return nil, err } - return &Service{store: store, listSize: listSize, crypto: c, documentLoader: loader}, nil + vcStore, err := provider.OpenVCStore() + if err != nil { + return nil, err + } + + return &Service{cslStore: cslStore, vcStore: vcStore, listSize: listSize, crypto: c, documentLoader: loader}, nil } // CreateStatusID creates status ID. @@ -104,7 +110,7 @@ func (s *Service) CreateStatusID(profile *vc.Signer, cslWrapper.Size++ cslWrapper.RevocationListIndex++ - if err = s.store.PutCSLWrapper(cslWrapper); err != nil { + if err = s.cslStore.PutCSLWrapper(cslWrapper); err != nil { return nil, fmt.Errorf("failed to store csl in store: %w", err) } @@ -113,7 +119,7 @@ func (s *Service) CreateStatusID(profile *vc.Signer, id++ - if err := s.store.UpdateLatestListID(id); err != nil { + if err := s.cslStore.UpdateLatestListID(id); err != nil { return nil, fmt.Errorf("failed to store latest list ID in store: %w", err) } } @@ -129,6 +135,26 @@ func (s *Service) CreateStatusID(profile *vc.Signer, }, nil } +func (s *Service) UpdateVCStatus(signer *vc.Signer, profileName, credentialID, status string) error { + vcBytes, err := s.vcStore.Get(profileName, credentialID) + if err != nil { + return err + } + + credential, err := verifiable.ParseCredential(vcBytes, verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(s.documentLoader)) + if err != nil { + return err + } + + statusValue, err := strconv.ParseBool(status) + if err != nil { + return err + } + + return s.UpdateVC(credential, signer, statusValue) +} + // UpdateVC updates vc. // nolint: gocyclo, funlen func (s *Service) UpdateVC(v *verifiable.Credential, @@ -180,7 +206,7 @@ func (s *Service) UpdateVC(v *verifiable.Credential, // remove all proofs because we are updating VC cslWrapper.VC.Proofs = nil - signedCredential, err := s.crypto.SignCredentialLDP(profile, cslWrapper.VC, signOpts...) + signedCredential, err := s.crypto.SignCredential(profile, cslWrapper.VC, signOpts...) if err != nil { return err } @@ -192,7 +218,7 @@ func (s *Service) UpdateVC(v *verifiable.Credential, cslWrapper.VCByte = signedCredentialBytes - return s.store.PutCSLWrapper(cslWrapper) + return s.cslStore.PutCSLWrapper(cslWrapper) } func (s *Service) GetCredentialStatusURL(issuerProfileURL, issuerProfileID, statusID string) (string, error) { @@ -233,7 +259,7 @@ func (s *Service) GetRevocationListVC(id string) (*verifiable.Credential, error) } func (s *Service) getCSLWrapper(id string) (*vcsstorage.CSLWrapper, error) { - cslWrapper, err := s.store.GetCSLWrapper(id) + cslWrapper, err := s.cslStore.GetCSLWrapper(id) if err != nil { return nil, fmt.Errorf("failed to get csl from store: %w", err) } @@ -251,10 +277,10 @@ func (s *Service) getCSLWrapper(id string) (*vcsstorage.CSLWrapper, error) { func (s *Service) getLatestCSLWrapper(profile *vc.Signer, url string) (*vcsstorage.CSLWrapper, error) { // get latest id - id, err := s.store.GetLatestListID() + id, err := s.cslStore.GetLatestListID() if err != nil { //nolint: nestif if errors.Is(err, ariesstorage.ErrDataNotFound) { - if errPut := s.store.UpdateLatestListID(1); errPut != nil { + if errPut := s.cslStore.UpdateLatestListID(1); errPut != nil { return nil, fmt.Errorf("failed to store latest list ID in store: %w", errPut) } @@ -341,12 +367,7 @@ func (s *Service) createVC(vcID string, return nil, err } - signedCredential, err := s.crypto.SignCredentialLDP(profile, credential, signOpts...) - if err != nil { - return nil, err - } - - return signedCredential, nil + return s.crypto.SignCredential(profile, credential, signOpts...) } // prepareSigningOpts prepares signing opts from recently issued proof of given credential. diff --git a/pkg/service/credentialstatus/credentialstatus_service_test.go b/pkg/service/credentialstatus/credentialstatus_service_test.go index be77f0584..ce7315311 100644 --- a/pkg/service/credentialstatus/credentialstatus_service_test.go +++ b/pkg/service/credentialstatus/credentialstatus_service_test.go @@ -220,6 +220,103 @@ func TestCredentialStatusList_GetRevocationListVC(t *testing.T) { } func TestCredentialStatusList_RevokeVC(t *testing.T) { + t.Run("UpdateVCStatus success", func(t *testing.T) { + loader := testutil.DocumentLoader(t) + provider := ariesprovider.New(ariesmockstorage.NewMockStoreProvider()) + s, err := New(provider, 2, + vccrypto.New( + &vdrmock.MockVDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader), loader) + require.NoError(t, err) + + profile := getTestProfile() + status, err := s.CreateStatusID(profile, "localhost:8080/status") + require.NoError(t, err) + + cred, err := verifiable.ParseCredential([]byte(universityDegreeCred), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + cred.ID = credID + cred.Status = status + store, err := provider.OpenVCStore() + require.NoError(t, err) + + err = store.Put("testprofile", cred) + require.NoError(t, err) + + require.NoError(t, s.UpdateVCStatus(getTestProfile(), "testprofile", cred.ID, "true")) + + revocationListVC, err := s.GetRevocationListVC(status.CustomFields[StatusListCredential].(string)) + require.NoError(t, err) + revocationListIndex, err := strconv.Atoi(status.CustomFields[StatusListIndex].(string)) + require.NoError(t, err) + + credSubject, ok := revocationListVC.Subject.([]verifiable.Subject) + require.True(t, ok) + require.NotEmpty(t, credSubject[0].CustomFields["encodedList"].(string)) + bitString, err := utils.DecodeBits(credSubject[0].CustomFields["encodedList"].(string)) + require.NoError(t, err) + bitSet, err := bitString.Get(revocationListIndex) + require.NoError(t, err) + require.True(t, bitSet) + }) + + t.Run("UpdateVCStatus store.Get error", func(t *testing.T) { + s, err := New(ariesprovider.New(ariesmockstorage.NewMockStoreProvider()), 2, + nil, nil) + require.NoError(t, err) + + err = s.UpdateVCStatus(getTestProfile(), "testprofile", "testId", "true") + require.Error(t, err) + require.ErrorContains(t, err, "data not found") + }) + + t.Run("UpdateVCStatus ParseCredential error", func(t *testing.T) { + loader := testutil.DocumentLoader(t) + provider := ariesprovider.New(ariesmockstorage.NewMockStoreProvider()) + s, err := New(provider, 2, + vccrypto.New( + &vdrmock.MockVDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader), loader) + require.NoError(t, err) + + cred, err := verifiable.ParseCredential([]byte(universityDegreeCred), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + cred.Context = append([]string{}, cred.Context[1:]...) + store, err := provider.OpenVCStore() + require.NoError(t, err) + + err = store.Put("testprofile", cred) + require.NoError(t, err) + + err = s.UpdateVCStatus(getTestProfile(), "testprofile", cred.ID, "true") + require.Error(t, err) + require.ErrorContains(t, err, "verifiable credential is not valid") + }) + t.Run("UpdateVCStatus ParseBool error", func(t *testing.T) { + loader := testutil.DocumentLoader(t) + provider := ariesprovider.New(ariesmockstorage.NewMockStoreProvider()) + s, err := New(provider, 2, + vccrypto.New( + &vdrmock.MockVDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader), loader) + require.NoError(t, err) + + cred, err := verifiable.ParseCredential([]byte(universityDegreeCred), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + store, err := provider.OpenVCStore() + require.NoError(t, err) + + err = store.Put("testprofile", cred) + require.NoError(t, err) + + err = s.UpdateVCStatus(getTestProfile(), "testprofile", cred.ID, "invalid") + require.Error(t, err) + require.ErrorContains(t, err, "invalid syntax") + }) + t.Run("test vc status not exists", func(t *testing.T) { loader := testutil.DocumentLoader(t) s, err := New(ariesprovider.New(ariesmockstorage.NewMockStoreProvider()), 2, @@ -527,6 +624,7 @@ func TestPrepareSigningOpts(t *testing.T) { func getTestProfile() *vc.Signer { return &vc.Signer{ + Format: vcsverifiable.Ldp, DID: "did:test:abc", SignatureType: "Ed25519Signature2018", Creator: "did:test:abc#key1", @@ -536,6 +634,7 @@ func getTestProfile() *vc.Signer { func getTestSignerWithCrypto(crypto ariescrypto.Crypto) *vc.Signer { return &vc.Signer{ + Format: vcsverifiable.Ldp, DID: "did:test:abc", SignatureType: "Ed25519Signature2018", Creator: "did:test:abc#key1", diff --git a/pkg/service/didconfiguration/didconfiguration_service.go b/pkg/service/didconfiguration/didconfiguration_service.go index 5feddc3be..a3c131523 100644 --- a/pkg/service/didconfiguration/didconfiguration_service.go +++ b/pkg/service/didconfiguration/didconfiguration_service.go @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -//go:generate mockgen -destination service_mocks_test.go -self_package mocks -package didconfiguration -source=didconfiguration_service.go -mock_names kmsRegistry=MockKmsRegistry,verifierProfileService=MockVerifierProfileService,issuerProfileService=MockIssuerProfileService,issueCredentialService=MockIssueCredentialService +//go:generate mockgen -destination service_mocks_test.go -self_package mocks -package didconfiguration -source=didconfiguration_service.go -mock_names kmsRegistry=MockKmsRegistry,verifierProfileService=MockVerifierProfileService,issuerProfileService=MockIssuerProfileService,vcCrypto=MockVCCrypto package didconfiguration @@ -43,13 +43,9 @@ type kmsRegistry interface { GetKeyManager(config *vcskms.Config) (vcskms.VCSKeyManager, error) } -type issueCredentialService interface { - Sign( - format vcsverifiable.Format, - signer *vc.Signer, - credential *verifiable.Credential, - issuerSigningOpts []crypto.SigningOpts, - ) (*verifiable.Credential, error) +type vcCrypto interface { + SignCredential(signerData *vc.Signer, vc *verifiable.Credential, + opts ...crypto.SigningOpts) (*verifiable.Credential, error) } type ProfileType string @@ -60,17 +56,17 @@ const ( ) type Config struct { - VerifierProfileService verifierProfileService - IssuerProfileService issuerProfileService - IssuerCredentialService issueCredentialService - KmsRegistry kmsRegistry + VerifierProfileService verifierProfileService + IssuerProfileService issuerProfileService + Crypto vcCrypto + KmsRegistry kmsRegistry } type Service struct { - verifierProfileService verifierProfileService - issuerProfileService issuerProfileService - issuerCredentialService issueCredentialService - kmsRegistry kmsRegistry + verifierProfileService verifierProfileService + issuerProfileService issuerProfileService + vcCrypto vcCrypto + kmsRegistry kmsRegistry } type DidConfiguration struct { @@ -82,10 +78,10 @@ func New( config *Config, ) *Service { return &Service{ - verifierProfileService: config.VerifierProfileService, - issuerProfileService: config.IssuerProfileService, - issuerCredentialService: config.IssuerCredentialService, - kmsRegistry: config.KmsRegistry, + verifierProfileService: config.VerifierProfileService, + issuerProfileService: config.IssuerProfileService, + vcCrypto: config.Crypto, + kmsRegistry: config.KmsRegistry, } } @@ -131,6 +127,7 @@ func (s *Service) DidConfig( } signer = &vc.Signer{ + Format: format, DID: profile.SigningDID.DID, Creator: profile.SigningDID.Creator, SignatureType: profile.OIDCConfig.ROSigningAlgorithm, @@ -160,6 +157,7 @@ func (s *Service) DidConfig( } signer = &vc.Signer{ + Format: format, DID: profile.SigningDID.DID, Creator: profile.SigningDID.Creator, SignatureType: profile.VCConfig.SigningAlgorithm, @@ -171,8 +169,7 @@ func (s *Service) DidConfig( return nil, resterr.NewValidationError(resterr.InvalidValue, "profileType", errors.New("profileType should be verifier or issuer")) } - - cred, err := s.issuerCredentialService.Sign(format, signer, cred, nil) + cred, err := s.vcCrypto.SignCredential(signer, cred, []crypto.SigningOpts{}...) if err != nil { return nil, err diff --git a/pkg/service/didconfiguration/didconfiguration_service_test.go b/pkg/service/didconfiguration/didconfiguration_service_test.go index 4e1710390..a1924646a 100644 --- a/pkg/service/didconfiguration/didconfiguration_service_test.go +++ b/pkg/service/didconfiguration/didconfiguration_service_test.go @@ -1,3 +1,9 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + package didconfiguration import ( @@ -131,13 +137,13 @@ func TestDidConfiguration(t *testing.T) { issuerProfilerSvc.EXPECT().GetProfile(testCase.profileID).Return(testCase.issuerProfile, nil) } - issueCredentialSvc := NewMockIssueCredentialService(gomock.NewController(t)) + cryptoSvc := NewMockVCCrypto(gomock.NewController(t)) - issueCredentialSvc.EXPECT().Sign(testCase.expectedFormat, gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func(format vcsverifiable.Format, + cryptoSvc.EXPECT().SignCredential(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func( signer *vc.Signer, credential *verifiable.Credential, - issuerSigningOpts []crypto.SigningOpts, + issuerSigningOpts ...crypto.SigningOpts, ) (*verifiable.Credential, error) { assert.Equal(t, testCase.expectedSigner.DID, signer.DID) assert.Equal(t, testCase.expectedSigner.Creator, signer.Creator) @@ -164,10 +170,10 @@ func TestDidConfiguration(t *testing.T) { }) didConfigurationService := New(&Config{ - VerifierProfileService: verifierProfileSvc, - IssuerProfileService: issuerProfilerSvc, - IssuerCredentialService: issueCredentialSvc, - KmsRegistry: kmsRegistrySvc, + VerifierProfileService: verifierProfileSvc, + IssuerProfileService: issuerProfilerSvc, + Crypto: cryptoSvc, + KmsRegistry: kmsRegistrySvc, }) resp, err := didConfigurationService.DidConfig(context.TODO(), @@ -340,15 +346,15 @@ func TestWithSignError(t *testing.T) { Checks: &profile.VerificationChecks{}, }, nil) - issueCredentialSvc := NewMockIssueCredentialService(gomock.NewController(t)) + cryptoSvc := NewMockVCCrypto(gomock.NewController(t)) - issueCredentialSvc.EXPECT().Sign(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return( + cryptoSvc.EXPECT().SignCredential(gomock.Any(), gomock.Any(), gomock.Any()).Return( nil, errors.New("sign error")) configService := New(&Config{ - VerifierProfileService: verifierProfileSvc, - KmsRegistry: kmsRegistrySvc, - IssuerCredentialService: issueCredentialSvc, + VerifierProfileService: verifierProfileSvc, + KmsRegistry: kmsRegistrySvc, + Crypto: cryptoSvc, }) cred, err := configService.DidConfig( diff --git a/pkg/service/issuecredential/issuecredential_service.go b/pkg/service/issuecredential/issuecredential_service.go index 9f6f7de81..34abcf084 100644 --- a/pkg/service/issuecredential/issuecredential_service.go +++ b/pkg/service/issuecredential/issuecredential_service.go @@ -16,7 +16,6 @@ import ( "github.com/trustbloc/vcs/pkg/doc/vc" "github.com/trustbloc/vcs/pkg/doc/vc/crypto" "github.com/trustbloc/vcs/pkg/doc/vc/vcutil" - vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" vcskms "github.com/trustbloc/vcs/pkg/kms" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/service/credentialstatus" @@ -29,9 +28,7 @@ type vcStatusManager interface { } type vcCrypto interface { - SignCredentialLDP(signerData *vc.Signer, vc *verifiable.Credential, - opts ...crypto.SigningOpts) (*verifiable.Credential, error) - SignCredentialJWT(signerData *vc.Signer, vc *verifiable.Credential, + SignCredential(signerData *vc.Signer, vc *verifiable.Credential, opts ...crypto.SigningOpts) (*verifiable.Credential, error) } @@ -81,29 +78,26 @@ func (s *Service) IssueCredential(credential *verifiable.Credential, SignatureType: profile.VCConfig.SigningAlgorithm, KeyType: profile.VCConfig.KeyType, KMS: kms, + Format: profile.VCConfig.Format, SignatureRepresentation: profile.VCConfig.SignatureRepresentation, } - // todo: adjust s.vcStatusManager.CreateStatusID() to be able to work with JWT - // issue: https://github.com/trustbloc/vcs/issues/826 - if profile.VCConfig.Format == vcsverifiable.Ldp { - var status *verifiable.TypedID - var statusURL string - - statusURL, err = s.vcStatusManager.GetCredentialStatusURL(profile.URL, profile.ID, "") - if err != nil { - return nil, fmt.Errorf("failed to create status URL: %w", err) - } + var status *verifiable.TypedID + var statusURL string - status, err = s.vcStatusManager.CreateStatusID(signer, statusURL) - if err != nil { - return nil, fmt.Errorf("failed to add credential status: %w", err) - } + statusURL, err = s.vcStatusManager.GetCredentialStatusURL(profile.URL, profile.ID, "") + if err != nil { + return nil, fmt.Errorf("failed to create status URL: %w", err) + } - credential.Context = append(credential.Context, credentialstatus.Context) - credential.Status = status + status, err = s.vcStatusManager.CreateStatusID(signer, statusURL) + if err != nil { + return nil, fmt.Errorf("failed to add credential status: %w", err) } + credential.Context = append(credential.Context, credentialstatus.Context) + credential.Status = status + // update context vcutil.UpdateSignatureTypeContext(credential, profile.VCConfig.SigningAlgorithm) @@ -111,7 +105,7 @@ func (s *Service) IssueCredential(credential *verifiable.Credential, vcutil.UpdateIssuer(credential, profile.SigningDID.DID, profile.Name, true) // sign the credential - signedVC, err := s.Sign(profile.VCConfig.Format, signer, credential, issuerSigningOpts) + signedVC, err := s.crypto.SignCredential(signer, credential, issuerSigningOpts...) if err != nil { return nil, fmt.Errorf("failed to sign credential: %w", err) } @@ -124,19 +118,3 @@ func (s *Service) IssueCredential(credential *verifiable.Credential, return signedVC, nil } - -func (s *Service) Sign( - format vcsverifiable.Format, - signer *vc.Signer, - credential *verifiable.Credential, - issuerSigningOpts []crypto.SigningOpts, -) (*verifiable.Credential, error) { - switch format { - case vcsverifiable.Jwt: - return s.crypto.SignCredentialJWT(signer, credential, issuerSigningOpts...) - case vcsverifiable.Ldp: - return s.crypto.SignCredentialLDP(signer, credential, issuerSigningOpts...) - default: - return nil, fmt.Errorf("unknown signature format %s", format) - } -} diff --git a/pkg/service/issuecredential/issuecredential_service_test.go b/pkg/service/issuecredential/issuecredential_service_test.go index bafe3240d..01e0e2f3a 100644 --- a/pkg/service/issuecredential/issuecredential_service_test.go +++ b/pkg/service/issuecredential/issuecredential_service_test.go @@ -305,7 +305,7 @@ func TestService_IssueCredential(t *testing.T) { vcStatusManager.EXPECT().GetCredentialStatusURL(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil) cr := NewMockvcCrypto(gomock.NewController(t)) - cr.EXPECT().SignCredentialLDP(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("some error")) + cr.EXPECT().SignCredential(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("some error")) service, err := New(&Config{ KMSRegistry: kmRegistry, VCStatusManager: vcStatusManager, @@ -331,11 +331,20 @@ func TestService_IssueCredential(t *testing.T) { vcStatusManager := NewMockVCStatusManager(gomock.NewController(t)) vcStatusManager.EXPECT().CreateStatusID(gomock.Any(), gomock.Any()).AnyTimes().Return(nil, nil) + vcStatusManager.EXPECT().GetCredentialStatusURL( + gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return("", nil) + + keyID, _, err := customKMS.CreateAndExportPubKeyBytes(kms.ED25519Type) + require.NoError(t, err) + + didDoc := createDIDDoc("did:trustblock:abc", keyID) + crypto := vccrypto.New( + &vdrmock.MockVDRegistry{ResolveValue: didDoc}, testutil.DocumentLoader(t)) service, err := New(&Config{ KMSRegistry: kmRegistry, VCStatusManager: vcStatusManager, - Crypto: nil, + Crypto: crypto, StorageProvider: ariesprovider.New(ariesmockstorage.NewMockStoreProvider()), }) require.NoError(t, err) diff --git a/pkg/service/verifycredential/revocation/revocation_service.go b/pkg/service/verifycredential/revocation/revocation_service.go index 62f1f9e1d..39cfdb4d2 100644 --- a/pkg/service/verifycredential/revocation/revocation_service.go +++ b/pkg/service/verifycredential/revocation/revocation_service.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package revocation import ( + "bytes" "context" "crypto/tls" "fmt" @@ -114,5 +115,5 @@ func (s *Service) sendHTTPRequest(req *http.Request, status int, token string) ( return nil, fmt.Errorf("failed to read response body for status %d: %s", resp.StatusCode, string(body)) } - return body, nil + return bytes.Trim(body, "\n"), nil } diff --git a/pkg/storage/mongodbprovider/csl.go b/pkg/storage/mongodbprovider/csl.go index f27c15741..e1622dce5 100644 --- a/pkg/storage/mongodbprovider/csl.go +++ b/pkg/storage/mongodbprovider/csl.go @@ -48,16 +48,14 @@ func (m *MongoDBCSLStore) PutCSLWrapper(cslWrapper *storage.CSLWrapper) error { } // Save space in the database by using the VC ID name as the MongoDB document _id field - // and removing the ID from the VC JSON. + // and removing the ID from the VC JSON-LD. mongoDBDocument[mongoDBDocumentIDFieldName] = vcID vcMap, ok := mongoDBDocument["vc"].(map[string]interface{}) - if !ok { - return errors.New("prepared MongoDB document missing VC or couldn't be asserted as a map") + if ok { + delete(vcMap, idFieldName) } - delete(vcMap, idFieldName) - filter := bson.M{mongoDBDocumentIDFieldName: cslWrapper.VC.ID} writeModel := mongodriver.NewReplaceOneModel().SetFilter(filter). @@ -73,12 +71,10 @@ func (m *MongoDBCSLStore) GetCSLWrapper(id string) (*storage.CSLWrapper, error) } vcMap, ok := mongoDBDocument["vc"].(map[string]interface{}) - if !ok { - return nil, errors.New("MongoDB document missing VC or couldn't be asserted as a map") + if ok { + vcMap[idFieldName] = mongoDBDocument[mongoDBDocumentIDFieldName] } - vcMap[idFieldName] = mongoDBDocument[mongoDBDocumentIDFieldName] - cslWrapperBytes, err := json.Marshal(mongoDBDocument) if err != nil { return nil, err diff --git a/test/bdd/features/vc_v1_e2e_api.feature b/test/bdd/features/vc_v1_e2e_api.feature index 31a269e9e..52b0da973 100644 --- a/test/bdd/features/vc_v1_e2e_api.feature +++ b/test/bdd/features/vc_v1_e2e_api.feature @@ -15,6 +15,8 @@ Feature: Using VC REST API Scenario Outline: Store, retrieve, verify credential and presentation using different kind of profiles Given V1 New verifiable credential is created from "" in "" format under "" profile for organization "" with signature representation "" And V1 verifiable credential is verified under "" profile for organization "" + Then V1 verifiable credential is revoked under "" profile for organization "" + And V1 verifiable credential is unable to be verified under "" profile for organization "" Examples: | issuerProfile | verifierProfile | organization | credential | vcFormat | signatureHolder | diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index c113f8232..fbdd37a30 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -183,10 +183,12 @@ "signatureRepresentation": 1, "keyType": "ECDSAP384DER", "format": "ldp", - "didMethod": "key" + "didMethod": "orb" } }, - "createDID": true + "createDID": true, + "didDomain": "https://testnet.orb.local", + "didServiceAuthToken": "tk1" }, { "issuer": { @@ -200,10 +202,12 @@ "signatureRepresentation": 1, "keyType": "ECDSAP256DER", "format": "ldp", - "didMethod": "key" + "didMethod": "orb" } }, - "createDID": true + "createDID": true, + "didDomain": "https://testnet.orb.local", + "didServiceAuthToken": "tk1" } ], "verifiers": [ @@ -313,7 +317,7 @@ "jwt" ], "proof": true, - "status": false + "status": true }, "presentation": { "format": [ diff --git a/test/bdd/pkg/v1/model/model.go b/test/bdd/pkg/v1/model/model.go index 457a4eb97..97977fe70 100644 --- a/test/bdd/pkg/v1/model/model.go +++ b/test/bdd/pkg/v1/model/model.go @@ -217,3 +217,34 @@ type PresentationChecks struct { Proof bool Format []string } + +// UpdateCredentialStatusRequest request struct for updating VC status. +type UpdateCredentialStatusRequest struct { + CredentialID string `json:"credentialID"` + + // Credential status. + CredentialStatus CredentialStatus `json:"credentialStatus"` +} + +// CredentialStatus Credential status. +type CredentialStatus struct { + Status string `json:"status"` + Type string `json:"type"` +} + +// VerifyCredentialResponse is a model for response of credentials verification. +type VerifyCredentialResponse struct { + Checks *[]VerifyCredentialCheckResult `json:"checks,omitempty"` +} + +// VerifyCredentialCheckResult verify credential response containing failure check details. +type VerifyCredentialCheckResult struct { + // Check title. + Check string `json:"check"` + + // Error message. + Error string `json:"error"` + + // Verification method. + VerificationMethod string `json:"verificationMethod"` +} diff --git a/test/bdd/pkg/v1/vc/credential.go b/test/bdd/pkg/v1/vc/credential.go index 5a3bd340a..edbb70724 100644 --- a/test/bdd/pkg/v1/vc/credential.go +++ b/test/bdd/pkg/v1/vc/credential.go @@ -27,11 +27,32 @@ func (e *Steps) issueVC(credential, vcFormat, profileName, organizationName, sig return err } + credBytes := e.bddContext.CreatedCredential + checkProof := true + if vcFormat == "jwt_vc" { - return nil + loader, err := bddutil.DocumentLoader() + if err != nil { + return fmt.Errorf("create document loader: %w", err) + } + + cred, err := verifiable.ParseCredential(e.bddContext.CreatedCredential, verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(loader)) + if err != nil { + return err + } + + cred.JWT = "" + + credBytes, err = cred.MarshalJSON() + if err != nil { + return fmt.Errorf("cred marshal error: %w", err) + } + + checkProof = false } - return e.checkVC(e.bddContext.CreatedCredential, profileName, signatureRepresentation) + return e.checkVC(credBytes, profileName, signatureRepresentation, checkProof) } func (e *Steps) createCredential(issueCredentialURL, credential, vcFormat, profileName, organizationName string) error { @@ -113,10 +134,40 @@ func getIssueCredentialRequestData(vc *verifiable.Credential, desiredFormat stri } func (e *Steps) verifyVC(profileName, organizationName string) error { - return e.verifyCredential(credentialServiceURL, profileName, organizationName) + result, err := e.getVerificationResult(credentialServiceURL, profileName, organizationName) + if err != nil { + return err + } + + if result.Checks != nil { + return fmt.Errorf("credential verification failed") + } + + return nil +} + +func (e *Steps) verifyRevokedVC(profileName, organizationName string) error { + result, err := e.getVerificationResult(credentialServiceURL, profileName, organizationName) + if err != nil { + return err + } + + checks := *result.Checks + + expectedCheck := model.VerifyCredentialCheckResult{ + Check: "credentialStatus", + Error: "revoked", + VerificationMethod: "", + } + + if checks[0] != expectedCheck { + return fmt.Errorf("vc is not revoked. Cheks: %+v", checks) + } + + return nil } -func (e *Steps) verifyCredential(verifyCredentialURL, profileName, organizationName string) error { +func (e *Steps) revokeVC(profileName, organizationName string) error { loader, err := bddutil.DocumentLoader() if err != nil { return err @@ -132,22 +183,28 @@ func (e *Steps) verifyCredential(verifyCredentialURL, profileName, organizationN return err } - req := &model.VerifyCredentialData{ - Credential: cred, + req := &model.UpdateCredentialStatusRequest{ + CredentialID: cred.ID, + CredentialStatus: model.CredentialStatus{ + Status: "true", + Type: "StatusList2021Entry", + }, } - reqBytes, err := json.Marshal(req) + requestBytes, err := json.Marshal(req) if err != nil { return err } - endpointURL := fmt.Sprintf(verifyCredentialURLFormat, verifyCredentialURL, profileName) + endpointURL := fmt.Sprintf(updateCredentialStatusURLFormat, credentialServiceURL, profileName) + token := e.bddContext.Args[getOrgAuthTokenKey(organizationName)] resp, err := bddutil.HTTPSDo(http.MethodPost, endpointURL, "application/json", token, //nolint: bodyclose - bytes.NewBuffer(reqBytes), e.tlsConfig) + bytes.NewBuffer(requestBytes), e.tlsConfig) if err != nil { return err } + defer bddutil.CloseResponseBody(resp.Body) respBytes, err := io.ReadAll(resp.Body) @@ -159,21 +216,64 @@ func (e *Steps) verifyCredential(verifyCredentialURL, profileName, organizationN return bddutil.ExpectedStatusCodeError(http.StatusOK, resp.StatusCode, respBytes) } - payload := map[string]interface{}{} + return nil +} - err = json.Unmarshal(respBytes, &payload) +func (e *Steps) getVerificationResult( + verifyCredentialURL, profileName, organizationName string) (*model.VerifyCredentialResponse, error) { + loader, err := bddutil.DocumentLoader() if err != nil { - return err + return nil, err } - if len(payload) > 0 { - return fmt.Errorf("credential verification failed") + e.RLock() + createdCredential := e.bddContext.CreatedCredential + e.RUnlock() + + cred, err := verifiable.ParseCredential(createdCredential, verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(loader)) + if err != nil { + return nil, err } - return nil + req := &model.VerifyCredentialData{ + Credential: cred, + } + + reqBytes, err := json.Marshal(req) + if err != nil { + return nil, err + } + + endpointURL := fmt.Sprintf(verifyCredentialURLFormat, verifyCredentialURL, profileName) + token := e.bddContext.Args[getOrgAuthTokenKey(organizationName)] + resp, err := bddutil.HTTPSDo(http.MethodPost, endpointURL, "application/json", token, //nolint: bodyclose + bytes.NewBuffer(reqBytes), e.tlsConfig) + if err != nil { + return nil, err + } + defer bddutil.CloseResponseBody(resp.Body) + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, bddutil.ExpectedStatusCodeError(http.StatusOK, resp.StatusCode, respBytes) + } + + payload := &model.VerifyCredentialResponse{} + + err = json.Unmarshal(respBytes, &payload) + if err != nil { + return nil, err + } + + return payload, nil } -func (e *Steps) checkVC(vcBytes []byte, profileName, signatureRepresentation string) error { +func (e *Steps) checkVC(vcBytes []byte, profileName, signatureRepresentation string, checkProof bool) error { vcMap, err := getVCMap(vcBytes) if err != nil { return err @@ -189,7 +289,11 @@ func (e *Steps) checkVC(vcBytes []byte, profileName, signatureRepresentation str return err } - return e.checkSignatureHolder(vcMap, signatureRepresentation) + if checkProof { + return e.checkSignatureHolder(vcMap, signatureRepresentation) + } + + return nil } func (e *Steps) checkSignatureHolder(vcMap map[string]interface{}, diff --git a/test/bdd/pkg/v1/vc/stress_steps.go b/test/bdd/pkg/v1/vc/stress_steps.go index e4f459d9d..6dcee7d97 100644 --- a/test/bdd/pkg/v1/vc/stress_steps.go +++ b/test/bdd/pkg/v1/vc/stress_steps.go @@ -195,11 +195,15 @@ func (r *stressRequest) Invoke() (interface{}, error) { startTime = time.Now() - err = r.steps.verifyCredential(r.verifyUrl, r.verifyProfileName, r.organizationName) + res, err := r.steps.getVerificationResult(r.verifyUrl, r.verifyProfileName, r.organizationName) if err != nil { return nil, err } + if res.Checks != nil { + return nil, fmt.Errorf("credential verification failed") + } + perfInfo.verifyVCHTTPTime = time.Since(startTime).Milliseconds() return perfInfo, nil diff --git a/test/bdd/pkg/v1/vc/vc_steps.go b/test/bdd/pkg/v1/vc/vc_steps.go index 7fba7b265..ce566c10c 100644 --- a/test/bdd/pkg/v1/vc/vc_steps.go +++ b/test/bdd/pkg/v1/vc/vc_steps.go @@ -6,6 +6,8 @@ SPDX-License-Identifier: Apache-2.0 package vc +// POST /issuer/profiles/{profileID}/credentials/status. + import ( "context" "crypto/tls" @@ -27,7 +29,7 @@ const ( issuerProfileURLFormat = issuerProfileURL + "/%s" issueCredentialURLFormat = issuerProfileURLFormat + "/credentials/issue" oidcProviderURL = "https://localhost:4444" - updateCredentialStatusURLFormat = credentialServiceURL + "/%s/credentials/status" + updateCredentialStatusURLFormat = issuerProfileURLFormat + "/credentials/status" ) func getOrgAuthTokenKey(org string) string { @@ -61,6 +63,10 @@ func (e *Steps) RegisterSteps(s *godog.ScenarioContext) { e.issueVC) s.Step(`^V1 verifiable credential is verified under "([^"]*)" profile for organization "([^"]*)"$`, e.verifyVC) + s.Step(`^V1 verifiable credential is revoked under "([^"]*)" profile for organization "([^"]*)"$`, + e.revokeVC) + s.Step(`^V1 verifiable credential is unable to be verified under "([^"]*)" profile for organization "([^"]*)"$`, + e.verifyRevokedVC) s.Step(`^"([^"]*)" users request to create a vc and verify it "([^"]*)" with profiles issuer "([^"]*)" verify "([^"]*)" and org id "([^"]*)" using "([^"]*)" concurrent requests$`, e.stressTestForMultipleUsers)