From bd2ee943a35d59fe1f393aceb2276b4c12c62f86 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Wed, 19 Mar 2025 09:34:25 +0100 Subject: [PATCH 1/2] #3663: change credentialsubject to map, use casting instead of marshalling where possible --- api/ssi_types_test.go | 12 ++++-- auth/api/auth/v1/api_test.go | 5 ++- auth/api/iam/api.go | 7 +--- auth/api/iam/api_test.go | 4 +- auth/api/iam/s2s_vptoken_test.go | 6 +-- auth/api/iam/user.go | 4 +- auth/api/iam/user_test.go | 4 +- auth/client/iam/openid4vp_test.go | 4 +- auth/services/oauth/relying_party_test.go | 20 +++++----- auth/services/selfsigned/signer_test.go | 2 +- auth/services/selfsigned/types/types.go | 36 +++++++++--------- auth/services/selfsigned/types/types_test.go | 2 +- auth/services/selfsigned/validator_test.go | 8 ++-- didman/didman.go | 9 +---- didman/didman_test.go | 8 ++-- discovery/client.go | 2 +- discovery/test.go | 4 +- go.mod | 2 +- go.sum | 6 +++ test/json.go | 19 ++++++++++ vcr/api/vcr/v2/api_test.go | 16 ++++---- vcr/api/vcr/v2/types_test.go | 8 ++-- vcr/credential/store/sql.go | 9 +---- vcr/credential/store/sql_test.go | 11 +++--- vcr/credential/util.go | 19 ++-------- vcr/credential/util_test.go | 36 +++++++++--------- vcr/credential/validator.go | 14 ++----- vcr/credential/validator_test.go | 38 +++++++++---------- vcr/holder/presenter_test.go | 4 +- vcr/issuer/issuer_test.go | 8 ++-- vcr/issuer/network_publisher.go | 9 +---- vcr/issuer/network_publisher_test.go | 14 +++---- vcr/issuer/openid_test.go | 8 ++-- vcr/pe/presentation_submission_test.go | 4 +- vcr/revocation/statuslist2021_issuer.go | 2 +- .../statuslist2021_verifier_test.go | 36 +++++++++--------- vcr/revocation/types.go | 9 +++++ vcr/search_test.go | 3 +- vcr/store_test.go | 6 +-- vcr/test/credentials.go | 8 ++-- vcr/verifier/signature_verifier_test.go | 2 +- vcr/verifier/verifier_test.go | 4 +- 42 files changed, 213 insertions(+), 219 deletions(-) create mode 100644 test/json.go diff --git a/api/ssi_types_test.go b/api/ssi_types_test.go index a70c82c983..5ad6424fae 100644 --- a/api/ssi_types_test.go +++ b/api/ssi_types_test.go @@ -129,10 +129,14 @@ func createVerifiableCredential() vcr.VerifiableCredential { ssi.MustParseURI("NutsOrganizationCredential"), ssi.MustParseURI("VerifiableCredential"), }, - Issuer: ssi.MustParseURI("did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey"), - IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{"subject"}, - Proof: []interface{}{"because"}, + Issuer: ssi.MustParseURI("did:nuts:CuE3qeFGGLhEAS3gKzhMCeqd1dGa9at5JCbmCfyMU2Ey"), + IssuanceDate: issuanceDate, + CredentialSubject: []map[string]any{ + { + "id": "subject", + }, + }, + Proof: []interface{}{"because"}, } } diff --git a/auth/api/auth/v1/api_test.go b/auth/api/auth/v1/api_test.go index bed33228b7..c7a307bd6e 100644 --- a/auth/api/auth/v1/api_test.go +++ b/auth/api/auth/v1/api_test.go @@ -34,6 +34,7 @@ import ( "github.com/nuts-foundation/nuts-node/auth/services/oauth" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/core/to" + "github.com/nuts-foundation/nuts-node/test" "github.com/nuts-foundation/nuts-node/vcr" "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vdr" @@ -507,7 +508,7 @@ func TestWrapper_RequestAccessToken(t *testing.T) { Type: []ssi.URI{*credential.NutsAuthorizationCredentialTypeURI, vc.VerifiableCredentialTypeV1URI()}, Issuer: vdr.TestDIDA.URI(), IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{credential.NutsAuthorizationCredentialSubject{ + CredentialSubject: []map[string]any{test.MustRemarshalIntoMap(credential.NutsAuthorizationCredentialSubject{ ID: vdr.TestDIDB.String(), PurposeOfUse: "eTransfer", Resources: []credential.Resource{ @@ -517,7 +518,7 @@ func TestWrapper_RequestAccessToken(t *testing.T) { UserContext: true, }, }, - }}, + })}, Proof: []interface{}{vc.Proof{}}, }, } diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index c3affbcf97..65c7b2272d 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -754,12 +754,7 @@ func (r Wrapper) RequestServiceAccessToken(ctx context.Context, request RequestS // by the nuts-node to build the correct wallet for a DID. See https://github.com/nuts-foundation/nuts-node/issues/3696 // As a sideeffect it is no longer possible to pass signed credentials to this API. for _, cred := range credentials { - var credentialSubject []map[string]interface{} - if err := cred.UnmarshalCredentialSubject(&credentialSubject); err != nil { - // extremely unlikely - return nil, core.InvalidInputError("failed to parse credentialSubject.id: %w", err) - } - for _, credSub := range credentialSubject { + for _, credSub := range cred.CredentialSubject { if _, ok := credSub["id"]; ok { return nil, core.InvalidInputError("self-asserted credentials MUST NOT contain a 'credentialSubject.id'") } diff --git a/auth/api/iam/api_test.go b/auth/api/iam/api_test.go index 448807dea7..ed973f375f 100644 --- a/auth/api/iam/api_test.go +++ b/auth/api/iam/api_test.go @@ -1034,7 +1034,7 @@ func TestWrapper_RequestServiceAccessToken(t *testing.T) { body.Credentials = &[]vc.VerifiableCredential{ { ID: to.Ptr(ssi.MustParseURI("not empty")), - CredentialSubject: []any{map[string]string{"id": "not empty"}}, + CredentialSubject: []map[string]any{{"id": "not empty"}}, }, } request := RequestServiceAccessTokenRequestObject{SubjectID: holderSubjectID, Body: body} @@ -1498,7 +1498,7 @@ func createIssuerCredential(issuerDID did.DID, holderDID did.DID) *vc.Verifiable Issuer: issuerDID.URI(), Context: []ssi.URI{credential.NutsV1ContextURI}, Type: []ssi.URI{credType}, - CredentialSubject: []interface{}{map[string]interface{}{"id": holderDID.String()}}, + CredentialSubject: []map[string]any{{"id": holderDID.String()}}, IssuanceDate: time.Now(), } verifiableCredential, _ := vc.CreateJWTVerifiableCredential(nil, template, captureFn) diff --git a/auth/api/iam/s2s_vptoken_test.go b/auth/api/iam/s2s_vptoken_test.go index cb420e2aab..f0b3f177e6 100644 --- a/auth/api/iam/s2s_vptoken_test.go +++ b/auth/api/iam/s2s_vptoken_test.go @@ -308,7 +308,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { t.Run("VC without credentialSubject.id", func(t *testing.T) { ctx := newTestClient(t) presentation := test.CreateJSONLDPresentation(t, *subjectDID, proofVisitor, vc.VerifiableCredential{ - CredentialSubject: []interface{}{map[string]string{}}, + CredentialSubject: []map[string]any{{}}, }) resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), clientID, issuerSubjectID, requestedScope, submissionJSON, presentation.Raw()) @@ -353,8 +353,8 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { // This indicates the client presented credentials that don't actually match the presentation definition, // which could indicate a malicious client. otherVerifiableCredential := vc.VerifiableCredential{ - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": subjectDID.String(), // just for demonstration purposes, what matters is that the credential does not match the presentation definition. "IsAdministrator": true, diff --git a/auth/api/iam/user.go b/auth/api/iam/user.go index da86ce5f5f..76f3881b3d 100644 --- a/auth/api/iam/user.go +++ b/auth/api/iam/user.go @@ -180,8 +180,8 @@ func (r Wrapper) issueEmployeeCredential(ctx context.Context, session user.Sessi Issuer: issuerDID, IssuanceDate: issuanceDate, ExpirationDate: &expirationDate, - CredentialSubject: []interface{}{ - map[string]string{ + CredentialSubject: []map[string]any{ + { "id": session.Wallet.DID.String(), "identifier": userDetails.Id, "name": userDetails.Name, diff --git a/auth/api/iam/user_test.go b/auth/api/iam/user_test.go index a648855c35..1edc14d76c 100644 --- a/auth/api/iam/user_test.go +++ b/auth/api/iam/user_test.go @@ -131,8 +131,8 @@ func TestWrapper_handleUserLanding(t *testing.T) { assert.Equal(t, jwa.EC, sessionKey.KeyType()) // check for details of issued NutsEmployeeCredential assert.Equal(t, "NutsEmployeeCredential", employeeCredentialTemplate.Type[0].String()) - employeeCredentialSubject := employeeCredentialTemplate.CredentialSubject[0].(map[string]string) - assert.True(t, strings.HasPrefix(employeeCredentialSubject["id"], "did:jwk:")) + employeeCredentialSubject := employeeCredentialTemplate.CredentialSubject[0] + assert.True(t, strings.HasPrefix(employeeCredentialSubject["id"].(string), "did:jwk:")) assert.Equal(t, userDetails.Id, employeeCredentialSubject["identifier"]) assert.Equal(t, userDetails.Name, employeeCredentialSubject["name"]) assert.Equal(t, userDetails.Role, employeeCredentialSubject["roleName"]) diff --git a/auth/client/iam/openid4vp_test.go b/auth/client/iam/openid4vp_test.go index d1a13b3e51..e5c4ef6840 100644 --- a/auth/client/iam/openid4vp_test.go +++ b/auth/client/iam/openid4vp_test.go @@ -292,8 +292,8 @@ func TestRelyingParty_RequestRFC021AccessToken(t *testing.T) { credential.NutsV1ContextURI, }, Type: []ssi.URI{vc.VerifiableCredentialTypeV1URI(), ssi.MustParseURI("NutsEmployeeCredential")}, - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "roleName": "employee", "name": "John Doe", "identifier": "123", diff --git a/auth/services/oauth/relying_party_test.go b/auth/services/oauth/relying_party_test.go index e85f3d7315..2c8672f750 100644 --- a/auth/services/oauth/relying_party_test.go +++ b/auth/services/oauth/relying_party_test.go @@ -133,17 +133,19 @@ func TestService_CreateJwtBearerToken(t *testing.T) { Type: []ssi.URI{*credential.NutsAuthorizationCredentialTypeURI, vc.VerifiableCredentialTypeV1URI()}, Issuer: vdr.TestDIDA.URI(), IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{credential.NutsAuthorizationCredentialSubject{ - ID: vdr.TestDIDB.String(), - PurposeOfUse: "eTransfer", - Resources: []credential.Resource{ - { - Path: "/composition/1", - Operations: []string{"read"}, - UserContext: true, + CredentialSubject: []map[string]any{ + { + "id": vdr.TestDIDB.String(), + "purposeOfUse": "eTransfer", + "resources": []any{ + map[string]any{ + "path": "/composition/1", + "operations": []string{"read"}, + "userContext": true, + }, }, }, - }}, + }, Proof: []interface{}{vc.Proof{}}, } diff --git a/auth/services/selfsigned/signer_test.go b/auth/services/selfsigned/signer_test.go index a51fed7fac..36de0931e2 100644 --- a/auth/services/selfsigned/signer_test.go +++ b/auth/services/selfsigned/signer_test.go @@ -247,7 +247,7 @@ func TestSessionStore_SigningSessionStatus(t *testing.T) { assert.Equal(t, employer.URI(), credential.Issuer) assert.Equal(t, []ssi.URI{ssi.MustParseURI("NutsEmployeeCredential")}, credential.Type) - credentialSubject := credential.CredentialSubject[0].(map[string]interface{}) + credentialSubject := credential.CredentialSubject[0] assert.Equal(t, employer.String(), credentialSubject["id"]) assert.Equal(t, "Organization", credentialSubject["type"]) require.IsType(t, map[string]interface{}{}, credentialSubject["member"]) diff --git a/auth/services/selfsigned/types/types.go b/auth/services/selfsigned/types/types.go index 6c6e32b265..3e73375c56 100644 --- a/auth/services/selfsigned/types/types.go +++ b/auth/services/selfsigned/types/types.go @@ -20,7 +20,6 @@ package types import ( "context" - "encoding/json" "github.com/nuts-foundation/nuts-node/vcr/credential" "strings" "time" @@ -44,27 +43,26 @@ type Session struct { Employee Employee } -func (s Session) CredentialSubject() []interface{} { - subject := EmployeeIdentityCredentialSubject{ - BaseCredentialSubject: credential.BaseCredentialSubject{ - ID: s.Employer, +func (s Session) CredentialSubject() []map[string]any { + member := map[string]any{ + "identifier": s.Employee.Identifier, + "member": map[string]any{ + "familyName": s.Employee.FamilyName, + "initials": s.Employee.Initials, + "type": "Person", }, - Type: "Organization", - Member: EmployeeIdentityCredentialMember{ - Identifier: s.Employee.Identifier, - Member: EmployeeIdentityCredentialMemberMember{ - FamilyName: s.Employee.FamilyName, - Initials: s.Employee.Initials, - Type: "Person", - }, - RoleName: s.Employee.RoleName, - Type: "EmployeeRole", + "type": "EmployeeRole", + } + if s.Employee.RoleName != nil { + member["roleName"] = *s.Employee.RoleName + } + return []map[string]any{ + { + "id": s.Employer, + "type": "Organization", + "member": member, }, } - data, _ := json.Marshal(subject) - result := map[string]interface{}{} - _ = json.Unmarshal(data, &result) - return []interface{}{result} } // HumanReadableContract returns the contract text without the contract type (e.g. "NL:LoginContract:v3") diff --git a/auth/services/selfsigned/types/types_test.go b/auth/services/selfsigned/types/types_test.go index 8bbe2cdeca..7dc744c9c0 100644 --- a/auth/services/selfsigned/types/types_test.go +++ b/auth/services/selfsigned/types/types_test.go @@ -55,7 +55,7 @@ func TestSession_CredentialSubject(t *testing.T) { } res := s.CredentialSubject() require.Len(t, res, 1) - subject := res[0].(map[string]interface{}) + subject := res[0] // subject is an organization and contains information about the employer require.Equal(t, "did:nuts:123", subject["id"]) require.Equal(t, "Organization", subject["type"]) diff --git a/auth/services/selfsigned/validator_test.go b/auth/services/selfsigned/validator_test.go index a5182e8e05..26ac1862e4 100644 --- a/auth/services/selfsigned/validator_test.go +++ b/auth/services/selfsigned/validator_test.go @@ -448,10 +448,10 @@ func createOrganizationCredential(issuerDID string) vc.VerifiableCredential { Context: []ssi.URI{credential.NutsV1ContextURI}, Type: []ssi.URI{ssi.MustParseURI("NutsOrganizationCredential")}, Issuer: did.MustParseDID(issuerDID).URI(), - CredentialSubject: []interface{}{ - credential.NutsOrganizationCredentialSubject{ - ID: issuerDID, - Organization: map[string]string{ + CredentialSubject: []map[string]any{ + { + "id": issuerDID, + "organization": map[string]string{ "name": "CareBears", "city": "CareTown", }, diff --git a/didman/didman.go b/didman/didman.go index 825f98610f..7e0d6be83d 100644 --- a/didman/didman.go +++ b/didman/didman.go @@ -38,7 +38,6 @@ import ( "github.com/nuts-foundation/nuts-node/didman/log" "github.com/nuts-foundation/nuts-node/jsonld" "github.com/nuts-foundation/nuts-node/vcr" - "github.com/nuts-foundation/nuts-node/vcr/credential" ) // ModuleName contains the name of this module: Didman @@ -501,13 +500,7 @@ func (d *didman) resolveOrganizationDIDDocument(organization vc.VerifiableCreden if len(organization.CredentialSubject) == 0 { return nil, did.DID{}, errors.New("no credential subjects in organization credential") } - credentialSubject := make([]credential.BaseCredentialSubject, 0) - err := organization.UnmarshalCredentialSubject(&credentialSubject) - if err != nil { - return nil, did.DID{}, fmt.Errorf("unable to get DID from organization credential: %w", err) - } - organizationDIDStr := credentialSubject[0].ID - organizationDID, err := did.ParseDID(organizationDIDStr) + organizationDID, err := organization.SubjectDID() if err != nil { return nil, did.DID{}, fmt.Errorf("unable to parse DID from organization credential: %w", err) } diff --git a/didman/didman_test.go b/didman/didman_test.go index 892cc95e7d..b80ea67444 100644 --- a/didman/didman_test.go +++ b/didman/didman_test.go @@ -859,8 +859,8 @@ func TestDidman_SearchOrganizations(t *testing.T) { ctx := newMockContext(t) credentialWithInvalidSubjectID := vc.VerifiableCredential{} _ = json.Unmarshal([]byte(jsonld.TestOrganizationCredential), &credentialWithInvalidSubjectID) - credentialWithInvalidSubjectID.CredentialSubject = []interface{}{ - map[string]interface{}{ + credentialWithInvalidSubjectID.CredentialSubject = []map[string]any{ + { "id": "90", }, } @@ -877,8 +877,8 @@ func TestDidman_SearchOrganizations(t *testing.T) { ctx := newMockContext(t) credentialWithoutSubjectID := vc.VerifiableCredential{} _ = json.Unmarshal([]byte(jsonld.TestOrganizationCredential), &credentialWithoutSubjectID) - credentialWithoutSubjectID.CredentialSubject = []interface{}{ - map[string]interface{}{}, + credentialWithoutSubjectID.CredentialSubject = []map[string]any{ + {}, } ctx.vcr.EXPECT().Search(reqCtx, searchTerms, false, nil).Return([]vc.VerifiableCredential{credentialWithoutSubjectID}, nil) diff --git a/discovery/client.go b/discovery/client.go index 84ec5c300e..98d0eb5b4a 100644 --- a/discovery/client.go +++ b/discovery/client.go @@ -239,7 +239,7 @@ func (r *clientRegistrationManager) findCredentialsAndBuildPresentation(ctx cont registrationCredential = vc.VerifiableCredential{ Context: []ssi.URI{vc.VCContextV1URI(), credential.NutsV1ContextURI}, Type: []ssi.URI{vc.VerifiableCredentialTypeV1URI(), credential.DiscoveryRegistrationCredentialTypeV1URI()}, - CredentialSubject: []interface{}{parameters}, + CredentialSubject: []map[string]any{parameters}, } credentials = append(credentials, credential.AutoCorrectSelfAttestedCredential(registrationCredential, subjectDID)) } diff --git a/discovery/test.go b/discovery/test.go index 9000bb497a..7c693abead 100644 --- a/discovery/test.go +++ b/discovery/test.go @@ -197,7 +197,7 @@ func createCredential(issuerDID did.DID, subjectDID did.DID, credentialSubject m Issuer: issuerDID.URI(), IssuanceDate: issuanceDate, ExpirationDate: &expirationDate, - CredentialSubject: []interface{}{credentialSubject}, + CredentialSubject: []map[string]any{credentialSubject}, }, func(ctx context.Context, claims map[string]interface{}, headers map[string]interface{}) (string, error) { if claimVisitor != nil { claimVisitor(claims) @@ -214,7 +214,7 @@ func createHolderCredential(subjectDID did.DID, credentialSubject map[string]int c := vc.VerifiableCredential{ Context: []ssi.URI{vc.VCContextV1URI(), credential.NutsV1ContextURI}, Type: []ssi.URI{vc.VerifiableCredentialTypeV1URI(), credential.DiscoveryRegistrationCredentialTypeV1URI()}, - CredentialSubject: []interface{}{credentialSubject}, + CredentialSubject: []map[string]any{credentialSubject}, } c = credential.AutoCorrectSelfAttestedCredential(c, subjectDID) // serialize/deserialize diff --git a/go.mod b/go.mod index eaffc2f481..1ccfc60f0f 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/nats-io/nats-server/v2 v2.10.26 github.com/nats-io/nats.go v1.39.1 github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b - github.com/nuts-foundation/go-did v0.15.0 + github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926 github.com/nuts-foundation/go-leia/v4 v4.1.0 github.com/nuts-foundation/go-stoabs v1.11.0 github.com/nuts-foundation/sqlite v1.0.0 diff --git a/go.sum b/go.sum index 9061866306..9818cec552 100644 --- a/go.sum +++ b/go.sum @@ -365,6 +365,12 @@ github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b h1:80 github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b/go.mod h1:6YUioYirD6/8IahZkoS4Ypc8xbeJW76Xdk1QKcziNTM= github.com/nuts-foundation/go-did v0.15.0 h1:aNl6KC8jiyRJGl9PPKFBboLLC0wUm5h+tjE1UBDQEPw= github.com/nuts-foundation/go-did v0.15.0/go.mod h1:swjCJvcRxc+i1nyieIERWEb3vFb4N7iYC+qen2OIbNg= +github.com/nuts-foundation/go-did v0.15.1-0.20250318150623-d2ac97c002b6 h1:8dPWXKbZbe10YtN2o3vNKw9jzHF4l0k4S/ZLA+JZ/bo= +github.com/nuts-foundation/go-did v0.15.1-0.20250318150623-d2ac97c002b6/go.mod h1:+CKZlsyh7oXp35uXfXkWkauVKgAFmxxhvV9EFDc1Ips= +github.com/nuts-foundation/go-did v0.15.1-0.20250318154645-e72bfe1fdc5b h1:705SSiJW5L6ZtcCseFAej2aIdDz3T223acONGsXjT9g= +github.com/nuts-foundation/go-did v0.15.1-0.20250318154645-e72bfe1fdc5b/go.mod h1:+CKZlsyh7oXp35uXfXkWkauVKgAFmxxhvV9EFDc1Ips= +github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926 h1:9E0uBk36lyEQCaXI/8ElgJO7ng/71ZCIvsHoezVllLY= +github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926/go.mod h1:+CKZlsyh7oXp35uXfXkWkauVKgAFmxxhvV9EFDc1Ips= github.com/nuts-foundation/go-leia/v4 v4.1.0 h1:5Jo9c5hL4G6IP4JTI/UpPfpY6BILlHbdv9+PTf4lc14= github.com/nuts-foundation/go-leia/v4 v4.1.0/go.mod h1:tYveGED8tSbQYhZNv2DVTc51c2zEWmSF+MG96PAtalY= github.com/nuts-foundation/go-stoabs v1.11.0 h1:q18jVruPdFcVhodDrnKuhq/24i0pUC/YXgzJS0glKUU= diff --git a/test/json.go b/test/json.go new file mode 100644 index 0000000000..cee7ea0975 --- /dev/null +++ b/test/json.go @@ -0,0 +1,19 @@ +package test + +import "encoding/json" + +func remarshal(src interface{}, dst interface{}) error { + asJSON, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(asJSON, &dst) +} + +func MustRemarshalIntoMap(v interface{}) map[string]any { + var result map[string]interface{} + if err := remarshal(v, &result); err != nil { + panic(err) + } + return result +} diff --git a/vcr/api/vcr/v2/api_test.go b/vcr/api/vcr/v2/api_test.go index 6c6ae24d55..4253cb26be 100644 --- a/vcr/api/vcr/v2/api_test.go +++ b/vcr/api/vcr/v2/api_test.go @@ -49,7 +49,7 @@ import ( var holderDID = did.MustParseDID("did:web:example.com:iam:123") var credentialID = ssi.MustParseURI("did:web:example.com:iam:456#1") -var testVC = vc.VerifiableCredential{ID: &credentialID, CredentialSubject: []interface{}{map[string]interface{}{"ID": holderDID.String()}}} +var testVC = vc.VerifiableCredential{ID: &credentialID, CredentialSubject: []map[string]any{{"id": holderDID.String()}}} func TestWrapper_IssueVC(t *testing.T) { @@ -60,7 +60,7 @@ func TestWrapper_IssueVC(t *testing.T) { Context: []ssi.URI{vc.VCContextV1URI(), credential.NutsV1ContextURI}, Type: []ssi.URI{credentialType}, Issuer: issuerURI, - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:nuts:456"}}, + CredentialSubject: []map[string]any{map[string]interface{}{"id": "did:nuts:456"}}, } t.Run("ok with an actual credential - minimal", func(t *testing.T) { @@ -321,7 +321,7 @@ func TestWrapper_IssueVC(t *testing.T) { Context: []ssi.URI{vc.VCContextV1URI(), credential.NutsV1ContextURI}, Type: []ssi.URI{credentialType}, Issuer: ssi.MustParseURI("did:web:example.com:iam:123"), - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:web:example.com:iam:456"}}, + CredentialSubject: []map[string]any{map[string]interface{}{"id": "did:web:example.com:iam:456"}}, } t.Run("ok with statuslist", func(t *testing.T) { @@ -353,7 +353,7 @@ func TestWrapper_IssueVC(t *testing.T) { Type: []ssi.URI{credentialType}, Issuer: ssi.MustParseURI("did:web:example.com:iam:123"), ExpirationDate: &now, - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:web:example.com:iam:456"}}, + CredentialSubject: []map[string]any{{"id": "did:web:example.com:iam:456"}}, } nowStr := now.Format(time.RFC3339) @@ -494,7 +494,7 @@ func TestWrapper_SearchIssuedVCs(t *testing.T) { ID: &vcID, Type: []ssi.URI{testCredential}, Issuer: issuerID, - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:nuts:456"}}, + CredentialSubject: []map[string]any{{"id": "did:nuts:456"}}, } t.Run("ok - with subject, no results", func(t *testing.T) { @@ -618,7 +618,7 @@ func TestWrapper_VerifyVC(t *testing.T) { expectedVC := vc.VerifiableCredential{ Type: []ssi.URI{credentialType}, Issuer: issuerURI, - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:nuts:456"}}, + CredentialSubject: []map[string]any{{"id": "did:nuts:456"}}, } expectedVerifyRequest := VCVerificationRequest{ @@ -659,7 +659,7 @@ func TestWrapper_VerifyVC(t *testing.T) { expectedVC := vc.VerifiableCredential{ Type: []ssi.URI{credentialType}, Issuer: issuerURI, - CredentialSubject: []interface{}{map[string]interface{}{"id": "did:nuts:123"}}, + CredentialSubject: []map[string]any{{"id": "did:nuts:123"}}, } expectedVerifyRequest := VCVerificationRequest{ @@ -895,7 +895,7 @@ func TestWrapper_CreateVP(t *testing.T) { verifiableCredential := vc.VerifiableCredential{ Type: []ssi.URI{credentialType}, Issuer: issuerURI, - CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectDID.String()}}, } result := &vc.VerifiablePresentation{} expectedresponse := CreateVP200JSONResponse(*result) diff --git a/vcr/api/vcr/v2/types_test.go b/vcr/api/vcr/v2/types_test.go index 3d68c3f15f..8c04a1defe 100644 --- a/vcr/api/vcr/v2/types_test.go +++ b/vcr/api/vcr/v2/types_test.go @@ -29,8 +29,8 @@ import ( func Test_Marshalling(t *testing.T) { t.Run("IssueVC200JSONResponse", func(t *testing.T) { r := IssueVC200JSONResponse{ - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": "did:nuts:123", }}, } @@ -43,8 +43,8 @@ func Test_Marshalling(t *testing.T) { }) t.Run("ResolveVC200JSONResponse", func(t *testing.T) { r := ResolveVC200JSONResponse{ - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": "did:nuts:123", }}, } diff --git a/vcr/credential/store/sql.go b/vcr/credential/store/sql.go index a23181c3e9..013b44aa71 100644 --- a/vcr/credential/store/sql.go +++ b/vcr/credential/store/sql.go @@ -19,7 +19,6 @@ package store import ( - "encoding/json" "fmt" "github.com/nuts-foundation/go-did/vc" "gorm.io/gorm" @@ -91,14 +90,8 @@ func (c CredentialStore) Store(db *gorm.DB, credential vc.VerifiableCredential) if len(credential.CredentialSubject) != 1 { return nil, fmt.Errorf("expected exactly one credential subject, got %d", len(credential.CredentialSubject)) } - credentialSubjectJSON, err := json.Marshal(credential.CredentialSubject[0]) - if err != nil { - return nil, fmt.Errorf("failed to marshal credential subject: %w", err) - } - var credentialSubject map[string]interface{} - _ = json.Unmarshal(credentialSubjectJSON, &credentialSubject) // if we marshalled it, we can unmarshal into a map // now index it - paths, values := indexJSONObject(credentialSubject, nil, nil, "credentialSubject") + paths, values := indexJSONObject(credential.CredentialSubject[0], nil, nil, "credentialSubject") for i, path := range paths { if path == "credentialSubject.id" { // present as column, don't index diff --git a/vcr/credential/store/sql_test.go b/vcr/credential/store/sql_test.go index 852cf1f2f4..90650e9246 100644 --- a/vcr/credential/store/sql_test.go +++ b/vcr/credential/store/sql_test.go @@ -23,6 +23,7 @@ import ( ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/storage" + "github.com/nuts-foundation/nuts-node/test" "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -49,11 +50,11 @@ func init() { }) vcOrganization = vc.VerifiableCredential{ Issuer: organizationIssuer, - CredentialSubject: []interface{}{ - credential.NutsOrganizationCredentialSubject{ + CredentialSubject: []map[string]any{ + test.MustRemarshalIntoMap(credential.NutsOrganizationCredentialSubject{ ID: "did:example:org", Organization: map[string]string{"name": "Example Corp"}, - }, + }), }, } vcOrganization.ID, _ = ssi.ParseURI("3") @@ -349,8 +350,8 @@ func createPersonCredential(id string, subjectID string, properties map[string]i ID: &parsedID, Issuer: personIssuer, Type: []ssi.URI{ssi.MustParseURI("PersonCredential")}, - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": subjectID, "person": properties, }, diff --git a/vcr/credential/util.go b/vcr/credential/util.go index 8e7c150811..1e3f6f7329 100644 --- a/vcr/credential/util.go +++ b/vcr/credential/util.go @@ -136,7 +136,7 @@ func AutoCorrectSelfAttestedCredential(credential vc.VerifiableCredential, reque } if _, ok := credentialSubject[0]["id"]; !ok { credentialSubject[0]["id"] = requester.String() - credential.CredentialSubject = make([]interface{}, 1) + credential.CredentialSubject = make([]map[string]any, 1) credential.CredentialSubject[0] = credentialSubject[0] } } @@ -151,7 +151,6 @@ func FilterOnDIDMethod(credentials []vc.VerifiableCredential, didMethods []strin return credentials } var result []vc.VerifiableCredential -outer: for _, credential := range credentials { // check issuer issuerDID, err := did.ParseDID(credential.Issuer.String()) @@ -160,22 +159,10 @@ outer: continue } } - bl := make([]BaseCredentialSubject, 0) - err = credential.UnmarshalCredentialSubject(&bl) - if err != nil { + subjectDID, err := credential.SubjectDID() + if err == nil && !slices.Contains(didMethods, subjectDID.Method) { continue } - for _, b := range bl { - if b.ID != "" { - // check credentialSubject - subjectDID, err := did.ParseDID(b.ID) - if err == nil { - if !slices.Contains(didMethods, subjectDID.Method) { - continue outer - } - } - } - } result = append(result, credential) } return result diff --git a/vcr/credential/util_test.go b/vcr/credential/util_test.go index 8062b4827a..5bc45a4a8e 100644 --- a/vcr/credential/util_test.go +++ b/vcr/credential/util_test.go @@ -35,13 +35,13 @@ func TestResolveSubjectDID(t *testing.T) { did1 := did.MustParseDID("did:test:123") did2 := did.MustParseDID("did:test:456") credential1 := vc.VerifiableCredential{ - CredentialSubject: []interface{}{map[string]interface{}{"id": did1}}, + CredentialSubject: []map[string]any{{"id": did1.String()}}, } credential2 := vc.VerifiableCredential{ - CredentialSubject: []interface{}{map[string]interface{}{"id": did1}}, + CredentialSubject: []map[string]any{{"id": did1.String()}}, } credential3 := vc.VerifiableCredential{ - CredentialSubject: []interface{}{map[string]interface{}{"id": did2}}, + CredentialSubject: []map[string]any{{"id": did2.String()}}, } t.Run("all the same", func(t *testing.T) { actual, err := ResolveSubjectDID(credential1, credential2) @@ -54,7 +54,7 @@ func TestResolveSubjectDID(t *testing.T) { assert.Nil(t, actual) }) t.Run("no ID", func(t *testing.T) { - actual, err := ResolveSubjectDID(vc.VerifiableCredential{CredentialSubject: []interface{}{map[string]interface{}{}}}) + actual, err := ResolveSubjectDID(vc.VerifiableCredential{CredentialSubject: []map[string]any{{}}}) assert.EqualError(t, err, "unable to get subject DID from VC: credential subjects have no ID") assert.Nil(t, actual) }) @@ -79,7 +79,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { }, VerifiableCredential: []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, + CredentialSubject: []map[string]any{{"id": subjectDID}}, }, }, }) @@ -119,7 +119,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { }, VerifiableCredential: []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{map[string]interface{}{}}, + CredentialSubject: []map[string]any{{}}, }, }, }) @@ -137,7 +137,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { }, VerifiableCredential: []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{map[string]interface{}{"id": did.MustParseDID("did:test:456")}}, + CredentialSubject: []map[string]any{{"id": did.MustParseDID("did:test:456")}}, }, }, }) @@ -152,7 +152,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { }, VerifiableCredential: []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, + CredentialSubject: []map[string]any{{"id": subjectDID}}, }, }, }) @@ -168,7 +168,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { }, VerifiableCredential: []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, + CredentialSubject: []map[string]any{{"id": subjectDID}}, }, }, }) @@ -291,7 +291,7 @@ func TestPresentationExpirationDate(t *testing.T) { func TestAutoCorrectSelfAttestedCredential(t *testing.T) { requestor := did.MustParseDID("did:test:123") credential := vc.VerifiableCredential{ - CredentialSubject: make([]interface{}, 1), + CredentialSubject: make([]map[string]any, 1), } result := AutoCorrectSelfAttestedCredential(credential, requestor) @@ -299,7 +299,7 @@ func TestAutoCorrectSelfAttestedCredential(t *testing.T) { assert.Equal(t, requestor.URI(), result.Issuer) assert.NotEqual(t, time.Time{}, result.IssuanceDate) assert.NotEqual(t, "", result.ID.String()) - assert.Equal(t, requestor.String(), result.CredentialSubject[0].(map[string]interface{})["id"]) + assert.Equal(t, requestor.String(), result.CredentialSubject[0]["id"]) t.Run("autocorrect for multiple DIDs does not skip credentialSubject.id", func(t *testing.T) { altDID := did.MustParseDID("did:test:456") @@ -308,7 +308,7 @@ func TestAutoCorrectSelfAttestedCredential(t *testing.T) { result = AutoCorrectSelfAttestedCredential(credential, altDID) assert.NotEqual(t, subjectPointer, &result.CredentialSubject) - assert.Equal(t, altDID.String(), result.CredentialSubject[0].(map[string]interface{})["id"]) + assert.Equal(t, altDID.String(), result.CredentialSubject[0]["id"]) }) } @@ -317,8 +317,8 @@ func TestFilterOnDIDMethod(t *testing.T) { credentials := []vc.VerifiableCredential{ { Issuer: ssi.MustParseURI("did:test:123"), - CredentialSubject: []interface{}{ - map[string]interface{}{"id": ssi.MustParseURI("did:test:456")}, + CredentialSubject: []map[string]any{ + {"id": ssi.MustParseURI("did:test:456")}, }, }, } @@ -341,8 +341,8 @@ func TestFilterOnDIDMethod(t *testing.T) { t.Run("no match on credentialSubject", func(t *testing.T) { credentials := []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{ - map[string]interface{}{"id": ssi.MustParseURI("did:test:456")}, + CredentialSubject: []map[string]any{ + {"id": ssi.MustParseURI("did:test:456")}, }, }, } @@ -365,8 +365,8 @@ func TestFilterOnDIDMethod(t *testing.T) { t.Run("credentialSubject not a did", func(t *testing.T) { credentials := []vc.VerifiableCredential{ { - CredentialSubject: []interface{}{ - map[string]interface{}{"id": ssi.MustParseURI("client_id")}, + CredentialSubject: []map[string]any{ + {"id": ssi.MustParseURI("client_id")}, }, }, } diff --git a/vcr/credential/validator.go b/vcr/credential/validator.go index eb468b1b93..b718ae8001 100644 --- a/vcr/credential/validator.go +++ b/vcr/credential/validator.go @@ -311,12 +311,6 @@ func (d x509CredentialValidator) Validate(credential vc.VerifiableCredential) er // validatePolicyAssertions checks if the credentialSubject claims match the did issuer policies func validatePolicyAssertions(issuer did.DID, credential vc.VerifiableCredential) error { - // get base form of all credentialSubject - var target = make([]map[string]interface{}, 1) - if err := credential.UnmarshalCredentialSubject(&target); err != nil { - return err - } - // we create a map of policyName to policyValue, then we split the policyValue into another map // no checks required, this has been done by the did:x509 resolver x509DID, _ := didx509.ParseX509Did(issuer) @@ -333,13 +327,13 @@ func validatePolicyAssertions(issuer did.DID, credential vc.VerifiableCredential } // we usually don't use multiple credentialSubjects, but for this validation it doesn't matter - for _, credentialSubject := range target { - // remove id from target - delete(credentialSubject, "id") - + for _, credentialSubject := range credential.CredentialSubject { // for each assertion create a string as "%s:%s" with key/value // check if the resulting string is present in the policyString for policyName, values := range credentialSubject { + if policyName == "id" { + continue + } valueMap, ok := values.(map[string]interface{}) if !ok { return fmt.Errorf("invalid assertion value type for 'credentialSubject.%s'", policyName) diff --git a/vcr/credential/validator_test.go b/vcr/credential/validator_test.go index 4561f12c4f..e65f3e04a0 100644 --- a/vcr/credential/validator_test.go +++ b/vcr/credential/validator_test.go @@ -64,7 +64,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - missing credential subject", func(t *testing.T) { v := test.ValidNutsOrganizationCredential(t) - v.CredentialSubject = []interface{}{} + v.CredentialSubject = []map[string]any{} err := validator.Validate(v) @@ -75,7 +75,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { v := test.ValidNutsOrganizationCredential(t) var credentialSubject = make(map[string]interface{}) credentialSubject["id"] = vdr.TestDIDB.String() - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -89,7 +89,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { credentialSubject["organization"] = map[string]interface{}{ "city": "EIbergen", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -103,7 +103,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { credentialSubject["organization"] = map[string]interface{}{ "name": "Because we care B.V.", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -118,7 +118,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { "name": "Because we care B.V.", "city": " ", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -133,7 +133,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { "name": " ", "city": "EIbergen", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -147,7 +147,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { "name": "Because we care B.V.", "city": "EIbergen", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -162,7 +162,7 @@ func TestNutsOrganizationCredentialValidator_Validate(t *testing.T) { "name": "Because we care B.V.", "city": "EIbergen", } - v.CredentialSubject = []interface{}{credentialSubject} + v.CredentialSubject = []map[string]any{credentialSubject} err := validator.Validate(v) @@ -203,7 +203,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("ok - multiple resources", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["resources"] = []Resource{ + v.CredentialSubject[0]["resources"] = []Resource{ { Path: "/Task/1", UserContext: false, @@ -227,7 +227,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("ok - empty resources array", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["resources"] = []Resource{} + v.CredentialSubject[0]["resources"] = []Resource{} err := validator.Validate(v) @@ -267,7 +267,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - missing credentialSubject", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject = []interface{}{} + v.CredentialSubject = []map[string]any{} err := validator.Validate(v) @@ -277,7 +277,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - missing credentialSubject.ID", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["id"] = "" + v.CredentialSubject[0]["id"] = "" err := validator.Validate(v) @@ -287,7 +287,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - invalid credentialSubject.ID", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["id"] = "unknown" + v.CredentialSubject[0]["id"] = "unknown" err := validator.Validate(v) @@ -297,7 +297,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - missing purposeOfUse", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["purposeOfUse"] = "" + v.CredentialSubject[0]["purposeOfUse"] = "" err := validator.Validate(v) @@ -307,7 +307,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - resources: missing path", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["resources"] = []Resource{{ + v.CredentialSubject[0]["resources"] = []Resource{{ Operations: []string{"read"}, }} @@ -319,7 +319,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - resources: missing operation", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["resources"] = []Resource{{ + v.CredentialSubject[0]["resources"] = []Resource{{ Path: "/composition/1", }} @@ -331,7 +331,7 @@ func TestNutsAuthorizationCredentialValidator_Validate(t *testing.T) { t.Run("failed - resources: invalid operation", func(t *testing.T) { v := test.ValidNutsAuthorizationCredential(t) - v.CredentialSubject[0].(map[string]any)["resources"] = []Resource{{ + v.CredentialSubject[0]["resources"] = []Resource{{ Path: "/composition/1", Operations: []string{"unknown"}, }} @@ -355,8 +355,8 @@ func TestDefaultCredentialValidator(t *testing.T) { t.Run("ok - credential with just ID in credentialSubject", func(t *testing.T) { // compaction replaces credentialSubject map with ID, with just the ID as string credential := test.ValidNutsAuthorizationCredential(t) - credential.CredentialSubject = []interface{}{ - map[string]interface{}{ + credential.CredentialSubject = []map[string]any{ + { "id": "did:nuts:1234", }, } diff --git a/vcr/holder/presenter_test.go b/vcr/holder/presenter_test.go index 13ec45bc00..efc47e70b8 100644 --- a/vcr/holder/presenter_test.go +++ b/vcr/holder/presenter_test.go @@ -237,7 +237,7 @@ func TestPresenter_buildPresentation(t *testing.T) { }) t.Run("error - not all VCs have the same id", func(t *testing.T) { secondCredential := testCredential - secondCredential.CredentialSubject = []interface{}{map[string]interface{}{"id": vdr.TestDIDB.String()}} + secondCredential.CredentialSubject = []map[string]any{{"id": vdr.TestDIDB.String()}} ctrl := gomock.NewController(t) @@ -252,7 +252,7 @@ func TestPresenter_buildPresentation(t *testing.T) { }) t.Run("error - not all VCs have an id", func(t *testing.T) { secondCredential := testCredential - secondCredential.CredentialSubject = []interface{}{} + secondCredential.CredentialSubject = []map[string]any{} ctrl := gomock.NewController(t) diff --git a/vcr/issuer/issuer_test.go b/vcr/issuer/issuer_test.go index a2306a4813..7dd8fc2576 100644 --- a/vcr/issuer/issuer_test.go +++ b/vcr/issuer/issuer_test.go @@ -76,7 +76,7 @@ func Test_issuer_buildAndSignVC(t *testing.T) { Type: []ssi.URI{credentialType}, Issuer: issuerID, ExpirationDate: &expirationDate, - CredentialSubject: []interface{}{map[string]interface{}{ + CredentialSubject: []map[string]any{{ "id": subjectDID, }}, } @@ -277,7 +277,7 @@ func Test_issuer_Issue(t *testing.T) { Context: []ssi.URI{credential.NutsV1ContextURI}, Type: []ssi.URI{credentialType}, Issuer: issuerDID.URI(), - CredentialSubject: []interface{}{map[string]interface{}{ + CredentialSubject: []map[string]any{{ "id": holderDID.String(), }}, } @@ -519,8 +519,8 @@ func Test_issuer_Issue(t *testing.T) { sut := issuer{keyResolver: keyResolverMock, store: mockStore, jsonldManager: jsonldManager, keyStore: nutsCryptoInstance} invalidCred := template - invalidCred.CredentialSubject = []interface{}{ - map[string]interface{}{"foo": "bar"}, + invalidCred.CredentialSubject = []map[string]any{ + {"foo": "bar"}, } result, err := sut.Issue(ctx, invalidCred, CredentialOptions{ diff --git a/vcr/issuer/network_publisher.go b/vcr/issuer/network_publisher.go index 4844c6c49a..28c29b910d 100644 --- a/vcr/issuer/network_publisher.go +++ b/vcr/issuer/network_publisher.go @@ -102,14 +102,7 @@ func (p networkPublisher) PublishCredential(ctx context.Context, verifiableCrede func (p networkPublisher) generateParticipants(verifiableCredential vc.VerifiableCredential) ([]did.DID, error) { issuer, _ := did.ParseDIDURL(verifiableCredential.Issuer.String()) participants := make([]did.DID, 0) - var ( - base []credential.BaseCredentialSubject - credentialSubjectID *did.DID - ) - err := verifiableCredential.UnmarshalCredentialSubject(&base) - if err == nil { - credentialSubjectID, err = did.ParseDID(base[0].ID) // earlier validation made sure length == 1 and ID is present - } + credentialSubjectID, err := verifiableCredential.SubjectDID() if err != nil { return nil, fmt.Errorf("failed to determine credentialSubject.ID: %w", err) } diff --git a/vcr/issuer/network_publisher_test.go b/vcr/issuer/network_publisher_test.go index c94f9696bd..5bd3a72ac6 100644 --- a/vcr/issuer/network_publisher_test.go +++ b/vcr/issuer/network_publisher_test.go @@ -109,7 +109,7 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: subjectID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectID.String()}}, } payload, _ := json.Marshal(credentialToPublish) @@ -150,7 +150,7 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: subjectID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectID.String()}}, } payload, _ := json.Marshal(credentialToPublish) @@ -204,12 +204,12 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { t.Run("invalid credentialSubject for private transaction", func(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: "abc"}}, + CredentialSubject: []map[string]any{{"id": "abc"}}, } sut := networkPublisher{} err := sut.PublishCredential(ctx, credentialToPublish, false) - assert.EqualError(t, err, "failed to determine credentialSubject.ID: invalid DID") + assert.EqualError(t, err, "failed to determine credentialSubject.ID: unable to get subject DID from VC: invalid DID") }) }) @@ -225,7 +225,7 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: subjectID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectID.String()}}, } expectedIssuerServiceURI := ssi.MustParseURI("did:nuts:123/serviceEndpoint?type=NutsComm") mockServiceResolver.EXPECT().Resolve(expectedIssuerServiceURI, 5).Return(did.Service{}, resolver.ErrServiceNotFound) @@ -242,7 +242,7 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: subjectID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectID.String()}}, } mockKeyResolver.EXPECT().ResolveKey(*issuerDID, nil, resolver.AssertionMethod).Return("", nil, errors.New("b00m!")) @@ -264,7 +264,7 @@ func Test_networkPublisher_PublishCredential(t *testing.T) { credentialToPublish := vc.VerifiableCredential{ Issuer: issuerID, IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{credential.BaseCredentialSubject{ID: subjectID.String()}}, + CredentialSubject: []map[string]any{{"id": subjectID.String()}}, } payload, _ := json.Marshal(credentialToPublish) diff --git a/vcr/issuer/openid_test.go b/vcr/issuer/openid_test.go index da9d629118..f81e25baca 100644 --- a/vcr/issuer/openid_test.go +++ b/vcr/issuer/openid_test.go @@ -47,8 +47,8 @@ const definitionsDIR = "./test/valid" var issuedVC = vc.VerifiableCredential{ Issuer: issuerDID.URI(), - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": holderDID.String(), }, }, @@ -240,8 +240,8 @@ func Test_memoryIssuer_HandleCredentialRequest(t *testing.T) { t.Run("not signed by intended wallet (DID differs)", func(t *testing.T) { otherIssuedVC := vc.VerifiableCredential{ Issuer: issuerDID.URI(), - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": "did:nuts:other-wallet", }, }, diff --git a/vcr/pe/presentation_submission_test.go b/vcr/pe/presentation_submission_test.go index b34ae84910..5388e234ef 100644 --- a/vcr/pe/presentation_submission_test.go +++ b/vcr/pe/presentation_submission_test.go @@ -201,8 +201,8 @@ func TestPresentationSubmission_Resolve(t *testing.T) { vc1 := credentialToJSONLD(vc.VerifiableCredential{ ID: &id1, ExpirationDate: &now, - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { // weird field for testing error case: parsing credentialSubject as JSON-LD Verifiable Credential // (expirationDate must be a JSON string containing a valid XML date-time) "expirationDate": "yesterday", diff --git a/vcr/revocation/statuslist2021_issuer.go b/vcr/revocation/statuslist2021_issuer.go index 6588deecd6..cced1903d6 100644 --- a/vcr/revocation/statuslist2021_issuer.go +++ b/vcr/revocation/statuslist2021_issuer.go @@ -262,7 +262,7 @@ func (cs *StatusList2021) buildAndSignVC(ctx context.Context, issuerDID did.DID, statusList2021CredentialTypeURI, }, ID: &credentialID, - CredentialSubject: []any{credSubject}, + CredentialSubject: []map[string]any{credSubject.ToMap()}, Issuer: issuerDID.URI(), IssuanceDate: iss, ExpirationDate: &exp, diff --git a/vcr/revocation/statuslist2021_verifier_test.go b/vcr/revocation/statuslist2021_verifier_test.go index 2ed018dcd6..f592a853d1 100644 --- a/vcr/revocation/statuslist2021_verifier_test.go +++ b/vcr/revocation/statuslist2021_verifier_test.go @@ -21,6 +21,7 @@ package revocation import ( "encoding/json" "errors" + test2 "github.com/nuts-foundation/nuts-node/test" "net/http" "net/http/httptest" "testing" @@ -87,8 +88,8 @@ func TestStatusList2021_Verify(t *testing.T) { // server that return StatusList2021Credential with statusPurpose == suspension statusList2021Credential := test.ValidStatusList2021Credential(t) - statusList2021Credential.CredentialSubject[0].(map[string]any)["statusPurpose"] = "suspension" - statusList2021Credential.CredentialSubject[0].(map[string]any)["id"] = ts.URL + statusList2021Credential.CredentialSubject[0]["statusPurpose"] = "suspension" + statusList2021Credential.CredentialSubject[0]["id"] = ts.URL credBytes, err := json.Marshal(statusList2021Credential) require.NoError(t, err) ts.Config.Handler = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -246,7 +247,7 @@ func TestStatusList2021_update(t *testing.T) { statusList2021Credential := test.ValidStatusList2021Credential(t) expectedExpires := time.Now().Truncate(time.Second) statusList2021Credential.ExpirationDate = &expectedExpires - statusList2021Credential.CredentialSubject[0].(map[string]any)["id"] = ts.URL + statusList2021Credential.CredentialSubject[0]["id"] = ts.URL credBytes, err := json.Marshal(statusList2021Credential) require.NoError(t, err) ts.Config.Handler = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { @@ -369,14 +370,14 @@ func TestStatusList2021_verify(t *testing.T) { }) t.Run("error - credential validation failed", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject[0].(map[string]any)["type"] = "wrong type" + cred.CredentialSubject[0]["type"] = "wrong type" credSubj, err := credentialStatusNoSignCheck.verify(cred) assert.EqualError(t, err, "credentialSubject.type 'StatusList2021' is required") assert.Nil(t, credSubj) }) t.Run("error - invalid credentialSubject.encodedList", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject[0].(map[string]any)["encodedList"] = "@" + cred.CredentialSubject[0]["encodedList"] = "@" credSubj, err := credentialStatusNoSignCheck.verify(cred) assert.EqualError(t, err, "credentialSubject.encodedList is invalid: illegal base64 data at input byte 0") @@ -470,39 +471,38 @@ func TestStatusList2021_validate(t *testing.T) { }) // CredentialSubject checks - t.Run("error - invalid credential subject", func(t *testing.T) { - cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject = []any{"{"} - _, err := cs.validate(cred) - assert.EqualError(t, err, "json: cannot unmarshal string into Go value of type revocation.StatusList2021CredentialSubject") - }) t.Run("error - wrong credential subject", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject = []any{struct{}{}} + cred.CredentialSubject = []map[string]any{ + {}, + } _, err := cs.validate(cred) assert.EqualError(t, err, "credentialSubject.type 'StatusList2021' is required") }) t.Run("error - multiple credentialSubject", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject = []any{StatusList2021CredentialSubject{}, StatusList2021CredentialSubject{}} + cred.CredentialSubject = []map[string]any{ + test2.MustRemarshalIntoMap(StatusList2021CredentialSubject{}), + test2.MustRemarshalIntoMap(StatusList2021CredentialSubject{}), + } _, err := cs.validate(cred) assert.EqualError(t, err, "single credentialSubject expected") }) t.Run("error - missing credentialSubject.type", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject[0].(map[string]any)["type"] = "" + cred.CredentialSubject[0]["type"] = "" _, err := cs.validate(cred) assert.EqualError(t, err, "credentialSubject.type 'StatusList2021' is required") }) t.Run("error - missing statusPurpose", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject[0].(map[string]any)["statusPurpose"] = "" + cred.CredentialSubject[0]["statusPurpose"] = "" _, err := cs.validate(cred) assert.EqualError(t, err, "credentialSubject.statusPurpose is required") }) t.Run("error - missing encodedList", func(t *testing.T) { cred := test.ValidStatusList2021Credential(t) - cred.CredentialSubject[0].(map[string]any)["encodedList"] = "" + cred.CredentialSubject[0]["encodedList"] = "" _, err := cs.validate(cred) assert.EqualError(t, err, "credentialSubject.encodedList is required") }) @@ -518,8 +518,8 @@ func testSetup(t testing.TB, entryIsRevoked bool) (*StatusList2021, StatusList20 t.Cleanup(func() { ts.Close() }) // credential - statusList2021Credential := test.ValidStatusList2021Credential(t) // has bit 1 set - statusList2021Credential.CredentialSubject[0].(map[string]any)["id"] = ts.URL // point to the test server + statusList2021Credential := test.ValidStatusList2021Credential(t) // has bit 1 set + statusList2021Credential.CredentialSubject[0]["id"] = ts.URL // point to the test server credBytes, err := json.Marshal(statusList2021Credential) require.NoError(t, err) diff --git a/vcr/revocation/types.go b/vcr/revocation/types.go index 4f32fb4b9b..1e20a07418 100644 --- a/vcr/revocation/types.go +++ b/vcr/revocation/types.go @@ -178,3 +178,12 @@ type StatusList2021CredentialSubject struct { // of verifiable credential status values. The uncompressed bitstring MUST be at least 16KB in size. EncodedList string `json:"encodedList"` } + +func (s StatusList2021CredentialSubject) ToMap() map[string]any { + return map[string]any{ + "id": s.ID, + "type": s.Type, + "statusPurpose": s.StatusPurpose, + "encodedList": s.EncodedList, + } +} diff --git a/vcr/search_test.go b/vcr/search_test.go index 688bace86e..e2bf1c0ec5 100644 --- a/vcr/search_test.go +++ b/vcr/search_test.go @@ -78,8 +78,7 @@ func TestVCR_Search(t *testing.T) { require.NoError(t, err) require.Len(t, searchResult, 1) - cs := searchResult[0].CredentialSubject[0] - m := cs.(map[string]interface{}) + m := searchResult[0].CredentialSubject[0] c := m["human"].(map[string]interface{}) assert.Equal(t, "fair", c["hairColour"]) }) diff --git a/vcr/store_test.go b/vcr/store_test.go index 44cc3e8aaa..ebdb9fc0b9 100644 --- a/vcr/store_test.go +++ b/vcr/store_test.go @@ -41,7 +41,7 @@ import ( func TestVcr_StoreCredential(t *testing.T) { // load VC target := test.ValidNutsOrganizationCredential(t) - holderDID := did.MustParseDID(target.CredentialSubject[0].(map[string]interface{})["id"].(string)) + holderDID := did.MustParseDID(target.CredentialSubject[0]["id"].(string)) // load pub key pke := spi.PublicKeyEntry{} @@ -109,8 +109,8 @@ func TestVcr_StoreCredential(t *testing.T) { _ = ctx.vcr.StoreCredential(target, &now) - target.CredentialSubject = []interface{}{ - map[string]interface{}{ + target.CredentialSubject = []map[string]any{ + { "name": "John Doe", "age": "42", }, diff --git a/vcr/test/credentials.go b/vcr/test/credentials.go index 2cf255822f..58816f1d50 100644 --- a/vcr/test/credentials.go +++ b/vcr/test/credentials.go @@ -55,8 +55,8 @@ func ValidNutsAuthorizationCredential(t testing.TB) vc.VerifiableCredential { Type: []ssi.URI{ssi.MustParseURI("NutsAuthorizationCredential"), vc.VerifiableCredentialTypeV1URI()}, Issuer: ssi.MustParseURI(vdr.TestDIDA.String()), IssuanceDate: issuanceDate, - CredentialSubject: []interface{}{ - map[string]any{ + CredentialSubject: []map[string]any{ + { "id": vdr.TestDIDB.String(), "purposeOfUse": "eTransfer", "resources": []any{ @@ -121,8 +121,8 @@ func ValidStatusList2021Credential(t testing.TB) vc.VerifiableCredential { IssuanceDate: issuanceDate, ExpirationDate: &expirationDate, CredentialStatus: nil, - CredentialSubject: []any{ - map[string]any{ + CredentialSubject: []map[string]any{ + { "id": "https://example-com/status/3#list", "type": "StatusList2021", "statusPurpose": "revocation", diff --git a/vcr/verifier/signature_verifier_test.go b/vcr/verifier/signature_verifier_test.go index cf8712bb87..86f86a3bb7 100644 --- a/vcr/verifier/signature_verifier_test.go +++ b/vcr/verifier/signature_verifier_test.go @@ -344,7 +344,7 @@ func testUraCredential(did string, ura string) (*vc.VerifiableCredential, error) subject := map[string]interface{}{} subject["id"] = did subject["uraNumber"] = ura - credential.CredentialSubject = []interface{}{subject} + credential.CredentialSubject = []map[string]any{subject} return credential, nil } diff --git a/vcr/verifier/verifier_test.go b/vcr/verifier/verifier_test.go index 19a224b342..458864bbc5 100644 --- a/vcr/verifier/verifier_test.go +++ b/vcr/verifier/verifier_test.go @@ -512,8 +512,8 @@ func TestVerifier_VerifyVP(t *testing.T) { // and the VC issuer must equal the VP holder. selfAssertedCredential := vc.VerifiableCredential{ Issuer: subjectDID.URI(), - CredentialSubject: []interface{}{ - map[string]interface{}{ + CredentialSubject: []map[string]any{ + { "id": subjectDID.String(), }, }, From b89e2b2346003ce215b9b9e3cd26d878b67cdf99 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Tue, 15 Apr 2025 09:57:18 +0200 Subject: [PATCH 2/2] update go-did --- go.mod | 8 ++++---- go.sum | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c403a473bb..54499310dc 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/nats-io/nats-server/v2 v2.11.1 github.com/nats-io/nats.go v1.41.1 github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b - github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926 + github.com/nuts-foundation/go-did v0.17.0 github.com/nuts-foundation/go-leia/v4 v4.1.0 github.com/nuts-foundation/go-stoabs v1.11.0 github.com/nuts-foundation/sqlite v1.0.0 @@ -106,7 +106,7 @@ require ( github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect @@ -148,8 +148,8 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/multiformats/go-base32 v0.0.3 // indirect - github.com/multiformats/go-base36 v0.1.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multihash v0.0.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 7c9d3924f7..a5dc3f54a0 100644 --- a/go.sum +++ b/go.sum @@ -169,6 +169,7 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -340,8 +341,10 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= @@ -374,6 +377,8 @@ github.com/nuts-foundation/go-did v0.15.1-0.20250318154645-e72bfe1fdc5b h1:705SS github.com/nuts-foundation/go-did v0.15.1-0.20250318154645-e72bfe1fdc5b/go.mod h1:+CKZlsyh7oXp35uXfXkWkauVKgAFmxxhvV9EFDc1Ips= github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926 h1:9E0uBk36lyEQCaXI/8ElgJO7ng/71ZCIvsHoezVllLY= github.com/nuts-foundation/go-did v0.15.1-0.20250319081900-aa2a47f72926/go.mod h1:+CKZlsyh7oXp35uXfXkWkauVKgAFmxxhvV9EFDc1Ips= +github.com/nuts-foundation/go-did v0.17.0 h1:nLmMiiKjIJwgZsfJ98ywATiCb9VHomnb3r86oWHdILw= +github.com/nuts-foundation/go-did v0.17.0/go.mod h1:8VLZhVjkFH9VgGu//3y7ICowwItpym3NWkOih1Ka1fw= github.com/nuts-foundation/go-leia/v4 v4.1.0 h1:5Jo9c5hL4G6IP4JTI/UpPfpY6BILlHbdv9+PTf4lc14= github.com/nuts-foundation/go-leia/v4 v4.1.0/go.mod h1:tYveGED8tSbQYhZNv2DVTc51c2zEWmSF+MG96PAtalY= github.com/nuts-foundation/go-stoabs v1.11.0 h1:q18jVruPdFcVhodDrnKuhq/24i0pUC/YXgzJS0glKUU=