From 55b230943e1207acfaea0496337ed0c554f1038d Mon Sep 17 00:00:00 2001 From: Bob Stasyszyn Date: Wed, 27 Nov 2024 09:31:18 -0500 Subject: [PATCH] feat: Add support for DataIntegrityProof in status lists (#1805) Signed-off-by: Bob Stasyszyn --- pkg/cslmanager/cslmanager.go | 1 + pkg/cslmanager/cslmanager_test.go | 177 ++++++++++++++++-- .../eventhandler/eventhandler_service.go | 6 +- .../eventhandler/eventhandler_service_test.go | 115 +++++++++++- 4 files changed, 267 insertions(+), 32 deletions(-) diff --git a/pkg/cslmanager/cslmanager.go b/pkg/cslmanager/cslmanager.go index 7f9d2c5b6..755bcc043 100644 --- a/pkg/cslmanager/cslmanager.go +++ b/pkg/cslmanager/cslmanager.go @@ -243,6 +243,7 @@ func (s *Manager) createNewVCAndCSLIndexWrapper(ctx context.Context, SignatureRepresentation: profile.VCConfig.SignatureRepresentation, VCStatusListType: profile.VCConfig.Status.Type, SDJWT: vc.SDJWT{Enable: false}, + DataIntegrityProof: profile.VCConfig.DataIntegrityProof, } cslURL, err := s.cslVCStore.GetCSLURL(s.externalURL, profile.GroupID, listID) diff --git a/pkg/cslmanager/cslmanager_test.go b/pkg/cslmanager/cslmanager_test.go index a7746e379..f52442529 100644 --- a/pkg/cslmanager/cslmanager_test.go +++ b/pkg/cslmanager/cslmanager_test.go @@ -20,17 +20,16 @@ import ( "github.com/golang/mock/gomock" "github.com/google/uuid" - "github.com/trustbloc/kms-go/spi/kms" - - "github.com/trustbloc/vcs/internal/mock/vcskms" - + "github.com/multiformats/go-multibase" "github.com/stretchr/testify/require" - "github.com/trustbloc/did-go/doc/did" model "github.com/trustbloc/did-go/doc/did/endpoint" vdrmock "github.com/trustbloc/did-go/vdr/mock" + "github.com/trustbloc/kms-go/spi/kms" + "github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022" "github.com/trustbloc/vc-go/verifiable" + "github.com/trustbloc/vcs/internal/mock/vcskms" "github.com/trustbloc/vcs/pkg/doc/vc" "github.com/trustbloc/vcs/pkg/doc/vc/bitstring" vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" @@ -53,10 +52,78 @@ const ( ) func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { - testProfile := getTestProfile() loader := testutil.DocumentLoader(t) t.Run("test success", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + + ctrl := gomock.NewController(t) + mockKMSRegistry := NewMockKMSRegistry(ctrl) + mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(5).Return(&vcskms.MockKMS{}, nil) + ctx := context.Background() + + cslIndexStore := newMockCSLIndexStore() + cslVCStore := newMockCSLVCStore() + + mockVCStatusStore := NewMockVCStatusStore(ctrl) + mockVCStatusStore.EXPECT(). + Put(gomock.Any(), testProfileID, testProfileVersion, credID, gomock.Any()). + Times(5).Return(nil) + + listID, err := cslIndexStore.GetLatestListID(context.Background()) + require.NoError(t, err) + + s, err := New(&Config{ + CSLIndexStore: cslIndexStore, + CSLVCStore: cslVCStore, + VCStatusStore: mockVCStatusStore, + ListSize: 2, + KMSRegistry: mockKMSRegistry, + ExternalURL: "https://localhost:8080", + Crypto: vccrypto.New( + &vdrmock.VDRegistry{ResolveValue: createDIDDoc()}, loader), + }) + require.NoError(t, err) + + statusID, err := s.CreateCSLEntry(ctx, testProfile, credID) + require.NoError(t, err) + validateVCStatus(t, cslVCStore, statusID, listID, testProfile) + + statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) + require.NoError(t, err) + validateVCStatus(t, cslVCStore, statusID, listID, testProfile) + + // List size equals 2, so after 2 issuances CSL encodedBitString is full and listID must be updated. + updatedListID, err := cslIndexStore.GetLatestListID(ctx) + require.NoError(t, err) + require.NotEqual(t, updatedListID, listID) + + statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) + require.NoError(t, err) + validateVCStatus(t, cslVCStore, statusID, updatedListID, testProfile) + + statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) + require.NoError(t, err) + validateVCStatus(t, cslVCStore, statusID, updatedListID, testProfile) + + // List size equals 2, so after 4 issuances CSL encodedBitString is full and listID must be updated. + updatedListIDSecond, err := cslIndexStore.GetLatestListID(ctx) + require.NoError(t, err) + require.NotEqual(t, updatedListID, updatedListIDSecond) + require.NotEqual(t, listID, updatedListIDSecond) + + statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) + require.NoError(t, err) + validateVCStatus(t, cslVCStore, statusID, updatedListIDSecond, testProfile) + }) + + t.Run("BitsringStatusList -> success", func(t *testing.T) { + testProfile := getTestProfile(vc.BitstringStatusList) + testProfile.VCConfig.DataIntegrityProof = vc.DataIntegrityProofConfig{ + Enable: true, + SuiteType: eddsa2022.SuiteType, + } + ctrl := gomock.NewController(t) mockKMSRegistry := NewMockKMSRegistry(ctrl) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(5).Return(&vcskms.MockKMS{}, nil) @@ -87,11 +154,11 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { statusID, err := s.CreateCSLEntry(ctx, testProfile, credID) require.NoError(t, err) - validateVCStatus(t, cslVCStore, statusID, listID) + validateBitstringVCStatus(t, cslVCStore, statusID, listID, testProfile) statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) require.NoError(t, err) - validateVCStatus(t, cslVCStore, statusID, listID) + validateBitstringVCStatus(t, cslVCStore, statusID, listID, testProfile) // List size equals 2, so after 2 issuances CSL encodedBitString is full and listID must be updated. updatedListID, err := cslIndexStore.GetLatestListID(ctx) @@ -100,11 +167,11 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) require.NoError(t, err) - validateVCStatus(t, cslVCStore, statusID, updatedListID) + validateBitstringVCStatus(t, cslVCStore, statusID, updatedListID, testProfile) statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) require.NoError(t, err) - validateVCStatus(t, cslVCStore, statusID, updatedListID) + validateBitstringVCStatus(t, cslVCStore, statusID, updatedListID, testProfile) // List size equals 2, so after 4 issuances CSL encodedBitString is full and listID must be updated. updatedListIDSecond, err := cslIndexStore.GetLatestListID(ctx) @@ -114,10 +181,12 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { statusID, err = s.CreateCSLEntry(ctx, testProfile, credID) require.NoError(t, err) - validateVCStatus(t, cslVCStore, statusID, updatedListIDSecond) + validateBitstringVCStatus(t, cslVCStore, statusID, updatedListIDSecond, testProfile) }) t.Run("test error get key manager", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + ctrl := gomock.NewController(t) mockKMSRegistry := NewMockKMSRegistry(ctrl) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(1).Return(nil, errors.New("some error")) @@ -145,7 +214,7 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { t.Run("test error get status processor", func(t *testing.T) { ctrl := gomock.NewController(t) - profile := getTestProfile() + profile := getTestProfile(vc.StatusList2021VCStatus) profile.VCConfig.Status.Type = "undefined" mockKMSRegistry := NewMockKMSRegistry(ctrl) @@ -177,6 +246,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error from get latest list id from store", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + ctrl := gomock.NewController(t) mockKMSRegistry := NewMockKMSRegistry(ctrl) @@ -200,6 +271,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error from put latest list id to store", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + ctrl := gomock.NewController(t) mockKMSRegistry := NewMockKMSRegistry(ctrl) @@ -224,7 +297,7 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error create CSL wrapper URL", func(t *testing.T) { - profile := getTestProfile() + profile := getTestProfile(vc.StatusList2021VCStatus) mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) @@ -247,6 +320,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error from CSL VC store", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(&vcskms.MockKMS{}, nil) ctx := context.Background() @@ -278,6 +353,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error put typedID to store - list size too small", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(1).Return(&vcskms.MockKMS{}, nil) @@ -299,7 +376,7 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error put typedID to store - no available unused indexes", func(t *testing.T) { - profile := getTestProfile() + profile := getTestProfile(vc.StatusList2021VCStatus) mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) @@ -341,13 +418,15 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) require.NoError(t, err) - status, err := s.CreateCSLEntry(context.Background(), testProfile, credID) + status, err := s.CreateCSLEntry(context.Background(), profile, credID) require.Error(t, err) require.Nil(t, status) require.Contains(t, err.Error(), "getUnusedIndex failed") }) t.Run("test error from store csl list in store", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(1).Return(&vcskms.MockKMS{}, nil) @@ -372,6 +451,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error update latest list id", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(1).Return(&vcskms.MockKMS{}, nil) @@ -396,6 +477,8 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) t.Run("test error put typedID to store", func(t *testing.T) { + testProfile := getTestProfile(vc.StatusList2021VCStatus) + ctrl := gomock.NewController(t) mockKMSRegistry := NewMockKMSRegistry(ctrl) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).Times(1).Return(&vcskms.MockKMS{}, nil) @@ -425,7 +508,7 @@ func TestCredentialStatusList_CreateCSLEntry(t *testing.T) { }) } -func getTestProfile() *profileapi.Issuer { +func getTestProfile(statusType vc.StatusType) *profileapi.Issuer { return &profileapi.Issuer{ ID: testProfileID, Version: testProfileVersion, @@ -436,7 +519,7 @@ func getTestProfile() *profileapi.Issuer { SigningAlgorithm: "Ed25519Signature2018", KeyType: kms.ED25519Type, Status: profileapi.StatusConfig{ - Type: vc.StatusList2021VCStatus, + Type: statusType, }, }, SigningDID: &profileapi.SigningDID{ @@ -712,10 +795,10 @@ func createDIDDoc() *did.Doc { } func validateVCStatus(t *testing.T, cslVCStore *mockCSLVCStore, statusID *credentialstatus.StatusListEntry, - expectedListID credentialstatus.ListID) { + expectedListID credentialstatus.ListID, profile *profileapi.Issuer) { t.Helper() - require.Equal(t, string(vc.StatusList2021VCStatus), statusID.TypedID.Type) + require.Equal(t, string(profile.VCConfig.Status.Type), statusID.TypedID.Type) require.Equal(t, "revocation", statusID.TypedID.CustomFields[statustype.StatusPurpose].(string)) existingStatusListVCID, ok := statusID.TypedID.CustomFields[statustype.StatusListCredential].(string) @@ -725,7 +808,7 @@ func validateVCStatus(t *testing.T, cslVCStore *mockCSLVCStore, statusID *creden existingStatusVCListID := chunks[len(chunks)-1] require.Equal(t, string(expectedListID), existingStatusVCListID) - cslURL, err := cslVCStore.GetCSLURL("https://localhost:8080", getTestProfile().GroupID, expectedListID) + cslURL, err := cslVCStore.GetCSLURL("https://localhost:8080", profile.GroupID, expectedListID) require.NoError(t, err) vcWrapper, err := cslVCStore.Get(context.Background(), cslURL) @@ -759,3 +842,57 @@ func validateVCStatus(t *testing.T, cslVCStore *mockCSLVCStore, statusID *creden require.NoError(t, err) require.False(t, bitSet) } + +func validateBitstringVCStatus(t *testing.T, cslVCStore *mockCSLVCStore, statusID *credentialstatus.StatusListEntry, + expectedListID credentialstatus.ListID, profile *profileapi.Issuer) { + t.Helper() + + require.Equal(t, string(vc.BitstringStatusList), statusID.TypedID.Type) + require.Equal(t, "revocation", statusID.TypedID.CustomFields[statustype.StatusPurpose].(string)) + + existingStatusListVCID, ok := statusID.TypedID.CustomFields[statustype.StatusListCredential].(string) + require.True(t, ok) + + chunks := strings.Split(existingStatusListVCID, "/") + existingStatusVCListID := chunks[len(chunks)-1] + require.Equal(t, string(expectedListID), existingStatusVCListID) + + cslURL, err := cslVCStore.GetCSLURL("https://localhost:8080", profile.GroupID, expectedListID) + require.NoError(t, err) + + vcWrapper, err := cslVCStore.Get(context.Background(), cslURL) + require.NoError(t, err) + + loader := testutil.DocumentLoader(t) + + statusListVC, err := verifiable.ParseCredential(vcWrapper.VCByte, + verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + statusListVCC := statusListVC.Contents() + + require.Equal(t, existingStatusListVCID, statusListVCC.ID) + require.Equal(t, "did:test:abc", statusListVCC.Issuer.ID) + require.Equal(t, verifiable.V2ContextURI, statusListVCC.Context[0]) + credSubject := statusListVCC.Subject + + require.Equal(t, existingStatusListVCID+"#list", credSubject[0].ID) + require.Equal(t, statustype.StatusListBitstringVCSubjectType, credSubject[0].CustomFields["type"].(string)) + require.Equal(t, "revocation", credSubject[0].CustomFields[statustype.StatusPurpose].(string)) + require.NotEmpty(t, credSubject[0].CustomFields["encodedList"].(string)) + bitString, err := bitstring.DecodeBits(credSubject[0].CustomFields["encodedList"].(string), + bitstring.WithMultibaseEncoding(multibase.Base64url)) + require.NoError(t, err) + + revocationListIndex, err := strconv.Atoi(statusID.TypedID.CustomFields[statustype.StatusListIndex].(string)) + require.NoError(t, err) + bitSet, err := bitString.Get(revocationListIndex) + require.NoError(t, err) + require.False(t, bitSet) + + require.Len(t, statusListVC.Proofs(), 1) + + proof := statusListVC.Proofs()[0] + require.Equal(t, "DataIntegrityProof", proof["type"]) +} diff --git a/pkg/service/credentialstatus/eventhandler/eventhandler_service.go b/pkg/service/credentialstatus/eventhandler/eventhandler_service.go index 9fb267b39..01fc1bcbe 100644 --- a/pkg/service/credentialstatus/eventhandler/eventhandler_service.go +++ b/pkg/service/credentialstatus/eventhandler/eventhandler_service.go @@ -15,13 +15,14 @@ import ( "github.com/piprate/json-gold/ld" "github.com/trustbloc/logutil-go/pkg/log" + "github.com/trustbloc/vc-go/dataintegrity/models" "github.com/trustbloc/vc-go/verifiable" + vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/internal/logfields" "github.com/trustbloc/vcs/pkg/doc/vc" vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" "github.com/trustbloc/vcs/pkg/doc/vc/statustype" - vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/pkg/event/spi" vcskms "github.com/trustbloc/vcs/pkg/kms" profileapi "github.com/trustbloc/vcs/pkg/profile" @@ -163,6 +164,7 @@ func (s *Service) signCSL(profileID, profileVersion string, csl *verifiable.Cred SignatureRepresentation: issuerProfile.VCConfig.SignatureRepresentation, VCStatusListType: issuerProfile.VCConfig.Status.Type, SDJWT: vc.SDJWT{Enable: false}, + DataIntegrityProof: issuerProfile.VCConfig.DataIntegrityProof, } signOpts, err := prepareSigningOpts(signer, csl.Proofs()) @@ -236,7 +238,7 @@ func prepareSigningOpts(profile *vc.Signer, proofs []verifiable.Proof) ([]vccryp return nil, err } - if signTypeName != "" { + if signTypeName != "" && signTypeName != models.DataIntegrityProof { signType, err := vcsverifiable.GetSignatureTypeByName(signTypeName) if err != nil { return nil, err diff --git a/pkg/service/credentialstatus/eventhandler/eventhandler_service_test.go b/pkg/service/credentialstatus/eventhandler/eventhandler_service_test.go index a1ac9e32d..a31159938 100644 --- a/pkg/service/credentialstatus/eventhandler/eventhandler_service_test.go +++ b/pkg/service/credentialstatus/eventhandler/eventhandler_service_test.go @@ -21,12 +21,13 @@ import ( "github.com/stretchr/testify/require" "github.com/trustbloc/kms-go/spi/kms" - "github.com/trustbloc/vcs/internal/mock/vcskms" - + "github.com/multiformats/go-multibase" "github.com/trustbloc/did-go/doc/did" vdrmock "github.com/trustbloc/did-go/vdr/mock" + "github.com/trustbloc/vc-go/dataintegrity/suite/eddsa2022" "github.com/trustbloc/vc-go/verifiable" + "github.com/trustbloc/vcs/internal/mock/vcskms" "github.com/trustbloc/vcs/pkg/doc/vc" "github.com/trustbloc/vcs/pkg/doc/vc/bitstring" vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" @@ -108,11 +109,41 @@ const ( "proofPurpose": 123 } } +}` + //nolint:lll + cslWrapperBitstringBytes = `{ + "vc": { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "credentialSubject": { + "encodedList": "uH4sIAAAAAAAA_-zAgQAAAACAoP2pF6kAAAAAAAAAAAAAAAAAAACgOgAA__-N53xXgD4AAA", + "id": "https://localhost:8080/issuer/profiles/externalID/credentials/status/05bd0c2e-5a06-4393-849c-42330f02a3f7#list", + "statusPurpose": "revocation", + "type": "BitstringStatusList" + }, + "id": "https://localhost:8080/issuer/profiles/externalID/credentials/status/05bd0c2e-5a06-4393-849c-42330f02a3f7", + "issuer": "did:test:abc", + "proof": { + "created": "2024-11-26T13:46:18-05:00", + "cryptosuite": "eddsa-rdfc-2022", + "proofPurpose": "assertionMethod", + "proofValue": "z", + "type": "DataIntegrityProof", + "verificationMethod": "did:test:abc#key1" + }, + "type": [ + "VerifiableCredential", + "BitstringStatusListCredential" + ], + "validFrom": "2024-11-26T18:46:18.403867Z" + } }` ) func TestService_HandleEvent(t *testing.T) { - profile := getTestProfile() + profile := getTestProfile(vc.StatusList2021VCStatus) loader := testutil.DocumentLoader(t) ctx := context.Background() mockProfileSrv := NewMockProfileService(gomock.NewController(t)) @@ -217,7 +248,7 @@ func TestService_HandleEvent(t *testing.T) { } func TestService_handleEventPayload(t *testing.T) { - profile := getTestProfile() + profile := getTestProfile(vc.StatusList2021VCStatus) loader := testutil.DocumentLoader(t) ctx := context.Background() mockProfileSrv := NewMockProfileService(gomock.NewController(t)) @@ -438,17 +469,19 @@ func TestService_handleEventPayload(t *testing.T) { } func TestService_signCSL(t *testing.T) { - profile := getTestProfile() loader := testutil.DocumentLoader(t) ctx := context.Background() - mockProfileSrv := NewMockProfileService(gomock.NewController(t)) - mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) mockKMSRegistry := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistry.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(&vcskms.MockKMS{}, nil) crypto := vccrypto.New( &vdrmock.VDRegistry{ResolveValue: createDIDDoc("did:test:abc")}, loader) t.Run("OK", func(t *testing.T) { + profile := getTestProfile(vc.StatusList2021VCStatus) + + mockProfileSrv := NewMockProfileService(gomock.NewController(t)) + mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) + cslStore := newMockCSLVCStore() var cslWrapper *credentialstatus.CSLVCWrapper @@ -474,6 +507,41 @@ func TestService_signCSL(t *testing.T) { require.NotEmpty(t, cslWrapper.VC.Proofs) }) + t.Run("BitstringStatusList -> OK", func(t *testing.T) { + profile := getTestProfile(vc.BitstringStatusList) + profile.VCConfig.DataIntegrityProof = vc.DataIntegrityProofConfig{ + Enable: true, + SuiteType: eddsa2022.SuiteType, + } + + mockProfileSrv := NewMockProfileService(gomock.NewController(t)) + mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) + + cslStore := newMockCSLVCStore() + + var cslWrapper *credentialstatus.CSLVCWrapper + err := json.Unmarshal([]byte(cslWrapperBitstringBytes), &cslWrapper) + require.NoError(t, err) + cslWrapper.VC = getVerifiedCSL(t, cslWrapper.VCByte, loader, statusBytePositionIndex, false) + + err = cslStore.Upsert(ctx, cslWrapper.VC.Contents().ID, cslWrapper) + require.NoError(t, err) + + s := New(&Config{ + DocumentLoader: loader, + CSLVCStore: cslStore, + ProfileService: mockProfileSrv, + KMSRegistry: mockKMSRegistry, + Crypto: crypto, + }) + + signedCSL, err := s.signCSL(profileID, profileVersion, cslWrapper.VC) + require.NoError(t, err) + require.NotEmpty(t, signedCSL) + cslWrapper.VC = getVerifiedCSL(t, signedCSL, loader, statusBytePositionIndex, false) + require.NotEmpty(t, cslWrapper.VC.Proofs) + }) + t.Run("Error failed to get profile", func(t *testing.T) { mockProfileSrvErr := NewMockProfileService(gomock.NewController(t)) mockProfileSrvErr.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(nil, errors.New("some error")) @@ -488,6 +556,11 @@ func TestService_signCSL(t *testing.T) { }) t.Run("Error failed to get KMS", func(t *testing.T) { + profile := getTestProfile(vc.StatusList2021VCStatus) + + mockProfileSrv := NewMockProfileService(gomock.NewController(t)) + mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) + mockKMSRegistryErr := NewMockKMSRegistry(gomock.NewController(t)) mockKMSRegistryErr.EXPECT().GetKeyManager(gomock.Any()).AnyTimes().Return(nil, errors.New("some error")) s := New(&Config{ @@ -502,6 +575,11 @@ func TestService_signCSL(t *testing.T) { }) t.Run("Error prepareSigningOpts failed", func(t *testing.T) { + profile := getTestProfile(vc.StatusList2021VCStatus) + + mockProfileSrv := NewMockProfileService(gomock.NewController(t)) + mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) + var cslWrapper *credentialstatus.CSLVCWrapper err := json.Unmarshal([]byte(cslWrapperBytesInvalidProof), &cslWrapper) require.NoError(t, err) @@ -524,6 +602,11 @@ func TestService_signCSL(t *testing.T) { }) t.Run("Error sign CSL failed", func(t *testing.T) { + profile := getTestProfile(vc.StatusList2021VCStatus) + + mockProfileSrv := NewMockProfileService(gomock.NewController(t)) + mockProfileSrv.EXPECT().GetProfile(gomock.Any(), gomock.Any()).AnyTimes().Return(profile, nil) + cryptoErr := vccrypto.New( &vdrmock.VDRegistry{ResolveErr: errors.New("some error")}, loader) var cslWrapper *credentialstatus.CSLVCWrapper @@ -677,7 +760,19 @@ func getVerifiedCSL( credSubject := csl.Contents().Subject require.NotEmpty(t, credSubject[0].CustomFields["encodedList"].(string)) - bitString, err := bitstring.DecodeBits(credSubject[0].CustomFields["encodedList"].(string)) + + var bitString *bitstring.BitString + + statusType, ok := credSubject[0].CustomFields["type"].(string) + require.True(t, ok) + + if statusType == "BitstringStatusList" { + bitString, err = bitstring.DecodeBits(credSubject[0].CustomFields["encodedList"].(string), + bitstring.WithMultibaseEncoding(multibase.Base64url)) + } else { + bitString, err = bitstring.DecodeBits(credSubject[0].CustomFields["encodedList"].(string)) + } + require.NoError(t, err) bitSet, err := bitString.Get(index) require.NoError(t, err) @@ -754,7 +849,7 @@ func (m *mockCSLVCStore) Get(_ context.Context, cslURL string) (*credentialstatu return w, nil } -func getTestProfile() *profileapi.Issuer { +func getTestProfile(statusType vc.StatusType) *profileapi.Issuer { return &profileapi.Issuer{ ID: profileID, Name: "testprofile", @@ -764,7 +859,7 @@ func getTestProfile() *profileapi.Issuer { SigningAlgorithm: "Ed25519Signature2018", KeyType: kms.ED25519Type, Status: profileapi.StatusConfig{ - Type: vc.StatusList2021VCStatus, + Type: statusType, }, }, SigningDID: &profileapi.SigningDID{