From 797a72f9675f1fa99f22569ed0d2f88169b95a39 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Wed, 8 Nov 2023 06:57:12 +0100 Subject: [PATCH] PEX: Support matching JWT VCs --- vcr/pe/presentation_definition.go | 85 ++++-- vcr/pe/presentation_definition_test.go | 343 ++++++++++++++++++------- 2 files changed, 306 insertions(+), 122 deletions(-) diff --git a/vcr/pe/presentation_definition.go b/vcr/pe/presentation_definition.go index 4b1261641c..2769fe6beb 100644 --- a/vcr/pe/presentation_definition.go +++ b/vcr/pe/presentation_definition.go @@ -22,6 +22,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jws" "strings" "github.com/PaesslerAG/jsonpath" @@ -49,6 +51,7 @@ type PresentationContext struct { // It implements ยง5 of the Presentation Exchange specification (v2.x.x pre-Draft, 2023-07-29) (https://identity.foundation/presentation-exchange/#presentation-definition) // It supports the following: // - ldp_vc format +// - jwt_vc format // - pattern, const and enum only on string fields // - number, boolean, array and string JSON schema types // - Submission Requirements Feature @@ -221,32 +224,41 @@ func (presentationDefinition PresentationDefinition) groups() []groupCandidates } // matchFormat checks if the credential matches the Format from the presentationDefinition. -// if one of format['ldp_vc'] or format['jwt_vc'] is present, the VC must match that format. // If the VC is of the required format, the alg or proofType must also match. // vp formats are ignored. // This might not be fully interoperable, but the spec at https://identity.foundation/presentation-exchange/#presentation-definition is not clear on this. func matchFormat(format *PresentationDefinitionClaimFormatDesignations, credential vc.VerifiableCredential) bool { - if format == nil { + if format == nil || len(*format) == 0 { return true } asMap := map[string]map[string][]string(*format) - // we're only interested in the jwt_vc and ldp_vc formats - if asMap["jwt_vc"] == nil && asMap["ldp_vc"] == nil { - return true - } - - // only ldp_vc supported for now - if entry := asMap["ldp_vc"]; entry != nil { - if proofTypes := entry["proof_type"]; proofTypes != nil { - for _, proofType := range proofTypes { - if matchProofType(proofType, credential) { - return true + switch credential.Format() { + case vc.JSONLDCredentialProofFormat: + if entry := asMap["ldp_vc"]; entry != nil { + if proofTypes := entry["proof_type"]; proofTypes != nil { + for _, proofType := range proofTypes { + if matchProofType(proofType, credential) { + return true + } + } + } + } + case vc.JWTCredentialProofFormat: + // Get signing algorithm used to sign the JWT + message, _ := jws.ParseString(credential.Raw()) // can't really fail, JWT has been parsed before. + signingAlgorithm, _ := message.Signatures()[0].ProtectedHeaders().Get("alg") + // Check that the signing algorithm is specified by the presentation definition + if entry := asMap["jwt_vc"]; entry != nil { + if supportedAlgorithms := entry["alg"]; supportedAlgorithms != nil { + for _, supportedAlgorithm := range supportedAlgorithms { + if signingAlgorithm == jwa.SignatureAlgorithm(supportedAlgorithm) { + return true + } } } } } - return false } @@ -289,10 +301,26 @@ func matchCredential(descriptor InputDescriptor, credential vc.VerifiableCredent // IsHolder, SameSubject, SubjectIsIssuer, Statuses are not supported for now. // LimitDisclosure is not supported for now. func matchConstraint(constraint *Constraints, credential vc.VerifiableCredential) (bool, error) { + // jsonpath works on interfaces, so convert the VC to an interface + var credentialAsMap map[string]interface{} + var err error + switch credential.Format() { + case vc.JWTCredentialProofFormat: + // JWT-VCs marshal to a JSON string, so marshal an alias to make sure we get a JSON object with the VC properties, + // instead of a JWT string. + type Alias vc.VerifiableCredential + credentialAsMap, err = remarshalToMap(Alias(credential)) + case vc.JSONLDCredentialProofFormat: + credentialAsMap, err = remarshalToMap(credential) + } + if err != nil { + return false, err + } + // for each field in constraint.fields: // a vc must match the field for _, field := range constraint.Fields { - match, err := matchField(field, credential) + match, err := matchField(field, credentialAsMap) if err != nil { return false, err } @@ -305,18 +333,13 @@ func matchConstraint(constraint *Constraints, credential vc.VerifiableCredential // matchField matches the field against the VC. // All fields need to match unless optional is set to true and no values are found for all the paths. -func matchField(field Field, credential vc.VerifiableCredential) (bool, error) { - // jsonpath works on interfaces, so convert the VC to an interface - asJSON, _ := json.Marshal(credential) - var asInterface interface{} - _ = json.Unmarshal(asJSON, &asInterface) - +func matchField(field Field, credential map[string]interface{}) (bool, error) { // for each path in field.paths: // a vc must match one of the path var optionalInvalid int for _, path := range field.Path { // if path is not found continue - value, err := getValueAtPath(path, asInterface) + value, err := getValueAtPath(path, credential) if err != nil { return false, err } @@ -351,7 +374,10 @@ func matchField(field Field, credential vc.VerifiableCredential) (bool, error) { func getValueAtPath(path string, vcAsInterface interface{}) (interface{}, error) { value, err := jsonpath.Get(path, vcAsInterface) // jsonpath.Get returns some errors if the path is not found, or it has a different type as expected - if err != nil && (strings.HasPrefix(err.Error(), "unknown key") || strings.HasPrefix(err.Error(), "unsupported value type")) { + if err != nil && (strings.HasPrefix(err.Error(), "unknown key") || + strings.HasPrefix(err.Error(), "unsupported value type") || + // Then a JSON path points to an array, but the expression doesn't specify an index + strings.HasPrefix(err.Error(), "could not select value, invalid key: expected number but got")) { return nil, nil } return value, err @@ -457,3 +483,16 @@ func vcEqual(a, b vc.VerifiableCredential) bool { bJSON, _ := json.Marshal(b) return string(aJSON) == string(bJSON) } + +func remarshalToMap(v interface{}) (map[string]interface{}, error) { + asJSON, err := json.Marshal(v) + if err != nil { + return nil, err + } + var result map[string]interface{} + err = json.Unmarshal(asJSON, &result) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/vcr/pe/presentation_definition_test.go b/vcr/pe/presentation_definition_test.go index b935bf2c15..081d1cf06b 100644 --- a/vcr/pe/presentation_definition_test.go +++ b/vcr/pe/presentation_definition_test.go @@ -19,7 +19,12 @@ package pe import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/json" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/vcr/pe/test" @@ -32,54 +37,102 @@ import ( const testPresentationDefinition = ` { "id": "Definition requesting NutsOrganizationCredential", + "submission_requirements": [ + { + "rule": "pick", + "count": 1, + "from": "vc" + } + ], "input_descriptors": [ - { - "id": "some random ID", - "name": "Organization matcher", - "purpose": "Finding any organization in CareTown starting with 'Care'", - "constraints": { - "fields": [ - { - "path": [ - "$.credentialSubject.organization.city" - ], - "filter": { - "type": "string", - "const": "IJbergen" - } - }, - { - "path": [ - "$.credentialSubject.organization.name" - ], - "filter": { - "type": "string", - "pattern": "care" - } - }, - { - "path": [ - "$.type" - ], - "filter": { - "type": "string", - "const": "NutsOrganizationCredential" - } - } - ] - } - } + { + "id": "as_jsonld", + "group": [ + "vc" + ], + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.organization.city" + ], + "filter": { + "type": "string", + "const": "IJbergen" + } + }, + { + "path": [ + "$.credentialSubject.organization.name" + ], + "filter": { + "type": "string", + "pattern": "care" + } + }, + { + "path": [ + "$.type" + ], + "filter": { + "type": "string", + "const": "NutsOrganizationCredential" + } + } + ] + } + }, + { + "id": "as_jwt", + "group": [ + "vc" + ], + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject[0].organization.city" + ], + "filter": { + "type": "string", + "const": "IJbergen" + } + }, + { + "path": [ + "$.credentialSubject[0].organization.name" + ], + "filter": { + "type": "string", + "pattern": "care" + } + }, + { + "path": [ + "$.type" + ], + "filter": { + "type": "string", + "const": "NutsOrganizationCredential" + } + } + ] + } + } ], "format": { "jwt_vc": { - "alg": ["ES256K", "ES384"] + "alg": [ + "ES256K", + "ES384" + ] }, - "ldp_vc": { + "ldp_vc": { "proof_type": [ - "JsonWebSignature2020" - ] - } - } + "JsonWebSignature2020" + ] + } + } } ` @@ -96,32 +149,74 @@ func TestMatch(t *testing.T) { id2 := ssi.MustParseURI("2") id3 := ssi.MustParseURI("3") id4 := ssi.MustParseURI("4") - vc1 := vc.VerifiableCredential{ID: &id1} - vc2 := vc.VerifiableCredential{ID: &id2} - vc3 := vc.VerifiableCredential{ID: &id3} - vc4 := vc.VerifiableCredential{ID: &id4} + vc1 := credentialToJSONLD(vc.VerifiableCredential{ID: &id1}) + vc2 := credentialToJSONLD(vc.VerifiableCredential{ID: &id2}) + vc3 := credentialToJSONLD(vc.VerifiableCredential{ID: &id3}) + vc4 := credentialToJSONLD(vc.VerifiableCredential{ID: &id4}) t.Run("Basic", func(t *testing.T) { presentationDefinition := PresentationDefinition{} - _ = json.Unmarshal([]byte(testPresentationDefinition), &presentationDefinition) + err := json.Unmarshal([]byte(testPresentationDefinition), &presentationDefinition) + require.NoError(t, err) verifiableCredential := vc.VerifiableCredential{} - vcJSON, _ := os.ReadFile("../test/vc.json") + vcJSON, _ := os.ReadFile("../test/jsonld_vc.json") _ = json.Unmarshal(vcJSON, &verifiableCredential) - t.Run("Happy flow", func(t *testing.T) { - vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{verifiableCredential}) + t.Run("JSON-LD", func(t *testing.T) { + t.Run("Happy flow", func(t *testing.T) { + vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{verifiableCredential}) - require.NoError(t, err) - assert.Len(t, vcs, 1) - require.Len(t, mappingObjects, 1) - assert.Equal(t, "$.verifiableCredential[0]", mappingObjects[0].Path) - }) - t.Run("Only second VC matches", func(t *testing.T) { - vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{{Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential")}}, verifiableCredential}) + require.NoError(t, err) + assert.Len(t, vcs, 1) + require.Len(t, mappingObjects, 1) + assert.Equal(t, "$.verifiableCredential[0]", mappingObjects[0].Path) + }) + t.Run("Only second VC matches", func(t *testing.T) { + vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{{Type: []ssi.URI{ssi.MustParseURI("VerifiableCredential")}}, verifiableCredential}) + require.NoError(t, err) + assert.Len(t, vcs, 1) + assert.Len(t, mappingObjects, 1) + }) + }) + t.Run("JWT", func(t *testing.T) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - assert.Len(t, vcs, 1) - assert.Len(t, mappingObjects, 1) + token := jwt.New() + require.NoError(t, token.Set("vc", map[string]interface{}{ + "credentialSubject": map[string]interface{}{ + "organization": map[string]interface{}{ + "city": "IJbergen", + "name": "care", + }, + }, + "type": "NutsOrganizationCredential", + })) + t.Run("Happy flow", func(t *testing.T) { + signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES384, privateKey)) + require.NoError(t, err) + jwtCredential, err := vc.ParseVerifiableCredential(string(signedToken)) + require.NoError(t, err) + + vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{*jwtCredential}) + + require.NoError(t, err) + assert.Len(t, vcs, 1) + require.Len(t, mappingObjects, 1) + assert.Equal(t, "$.verifiableCredential[0]", mappingObjects[0].Path) + }) + t.Run("unsupported JOSE alg", func(t *testing.T) { + signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES256, privateKey)) + require.NoError(t, err) + jwtCredential, err := vc.ParseVerifiableCredential(string(signedToken)) + require.NoError(t, err) + + vcs, mappingObjects, err := presentationDefinition.Match([]vc.VerifiableCredential{*jwtCredential}) + + require.ErrorContains(t, err, "has less credentials (0) than required (1)") + assert.Empty(t, vcs) + assert.Empty(t, mappingObjects) + }) }) }) t.Run("Submission requirement feature", func(t *testing.T) { @@ -271,10 +366,6 @@ func TestMatch(t *testing.T) { } func Test_matchFormat(t *testing.T) { - verifiableCredential := vc.VerifiableCredential{} - vcJSON, _ := os.ReadFile("../test/vc.json") - _ = json.Unmarshal(vcJSON, &verifiableCredential) - t.Run("no format", func(t *testing.T) { match := matchFormat(nil, vc.VerifiableCredential{}) @@ -282,42 +373,82 @@ func Test_matchFormat(t *testing.T) { }) t.Run("countable format", func(t *testing.T) { - match := matchFormat(&PresentationDefinitionClaimFormatDesignations{}, vc.VerifiableCredential{}) + match := matchFormat(&PresentationDefinitionClaimFormatDesignations{}, credentialToJSONLD(vc.VerifiableCredential{})) assert.True(t, match) }) - t.Run("format with jwt_vc always returns false", func(t *testing.T) { - asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}} - asFormat := PresentationDefinitionClaimFormatDesignations(asMap) - match := matchFormat(&asFormat, vc.VerifiableCredential{}) + t.Run("JWT", func(t *testing.T) { + const jwtCredential = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDpleGFtcGxlOmFiZmUxM2Y3MTIxMjA0 +MzFjMjc2ZTEyZWNhYiNrZXlzLTEifQ.eyJzdWIiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxY +zI3NmUxMmVjMjEiLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsImlzc +yI6Imh0dHBzOi8vZXhhbXBsZS5jb20va2V5cy9mb28uandrIiwibmJmIjoxNTQxNDkzNzI0LCJpYXQiO +jE1NDE0OTM3MjQsImV4cCI6MTU3MzAyOTcyMywibm9uY2UiOiI2NjAhNjM0NUZTZXIiLCJ2YyI6eyJAY +29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd +3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZ +UNyZWRlbnRpYWwiLCJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCJdLCJjcmVkZW50aWFsU3ViamVjd +CI6eyJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IjxzcGFuIGxhbmc9J2ZyL +UNBJz5CYWNjYWxhdXLDqWF0IGVuIG11c2lxdWVzIG51bcOpcmlxdWVzPC9zcGFuPiJ9fX19.KLJo5GAy +BND3LDTn9H7FQokEsUEi8jKwXhGvoN3JtRa51xrNDgXDb0cq1UTYB-rK4Ft9YVmR1NI_ZOF8oGc_7wAp +8PHbF2HaWodQIoOBxxT-4WNqAxft7ET6lkH-4S6Ux3rSGAmczMohEEf8eCeN-jC8WekdPl6zKZQj0YPB +1rx6X0-xlFBs7cl6Wt8rfBP_tZ9YgVWrQmUWypSioc0MUyiphmyEbLZagTyPlUyflGlEdqrZAv6eSe6R +txJy6M1-lD7a5HTzanYTWBPAUHDZGyGKXdJw-W_x0IWChBzI8t3kpG253fg6V3tPgHeKXE94fz_QpYfg +--7kLsyBAfQGbg` + verifiableCredential, err := vc.ParseVerifiableCredential(jwtCredential) + require.NoError(t, err) + t.Run("alg match", func(t *testing.T) { + asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"RS256"}}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, *verifiableCredential) - assert.False(t, match) - }) + assert.True(t, match) + }) + t.Run("no alg match", func(t *testing.T) { + asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, *verifiableCredential) - t.Run("format with matching ldp_vc", func(t *testing.T) { - asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}, "ldp_vc": {"proof_type": {"JsonWebSignature2020"}}} - asFormat := PresentationDefinitionClaimFormatDesignations(asMap) - match := matchFormat(&asFormat, verifiableCredential) + assert.False(t, match) + }) + t.Run("missing proof_type", func(t *testing.T) { + asMap := map[string]map[string][]string{"jwt_vc": {}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, *verifiableCredential) - assert.True(t, match) + assert.False(t, match) + }) }) - t.Run("non-matching ldp_vc", func(t *testing.T) { - asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}, "ldp_vc": {"proof_type": {"Ed25519Signature2018"}}} - asFormat := PresentationDefinitionClaimFormatDesignations(asMap) - match := matchFormat(&asFormat, verifiableCredential) + t.Run("JSON-LD", func(t *testing.T) { + verifiableCredential := vc.VerifiableCredential{} + vcJSON, _ := os.ReadFile("../test/jsonld_vc.json") + _ = json.Unmarshal(vcJSON, &verifiableCredential) - assert.False(t, match) - }) + t.Run("format with matching ldp_vc", func(t *testing.T) { + asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}, "ldp_vc": {"proof_type": {"JsonWebSignature2020"}}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, verifiableCredential) - t.Run("missing proof_type", func(t *testing.T) { - asMap := map[string]map[string][]string{"ldp_vc": {}} - asFormat := PresentationDefinitionClaimFormatDesignations(asMap) - match := matchFormat(&asFormat, verifiableCredential) + assert.True(t, match) + }) - assert.False(t, match) + t.Run("non-matching ldp_vc", func(t *testing.T) { + asMap := map[string]map[string][]string{"jwt_vc": {"alg": {"ES256K", "ES384"}}, "ldp_vc": {"proof_type": {"Ed25519Signature2018"}}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, verifiableCredential) + + assert.False(t, match) + }) + + t.Run("missing proof_type", func(t *testing.T) { + asMap := map[string]map[string][]string{"ldp_vc": {}} + asFormat := PresentationDefinitionClaimFormatDesignations(asMap) + match := matchFormat(&asFormat, verifiableCredential) + + assert.False(t, match) + }) }) + } func Test_matchDescriptor(t *testing.T) { @@ -392,30 +523,31 @@ func Test_matchConstraint(t *testing.T) { } func Test_matchField(t *testing.T) { - testCredential := vc.VerifiableCredential{} - _ = json.Unmarshal([]byte(testCredentialString), &testCredential) + testCredential, err := vc.ParseVerifiableCredential(testCredentialString) + require.NoError(t, err) + testCredentialMap, _ := remarshalToMap(testCredential) t.Run("single path match", func(t *testing.T) { - match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}}, testCredential) + match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}}, testCredentialMap) require.NoError(t, err) assert.True(t, match) }) t.Run("multi path match", func(t *testing.T) { - match, err := matchField(Field{Path: []string{"$.other", "$.credentialSubject.field"}}, testCredential) + match, err := matchField(Field{Path: []string{"$.other", "$.credentialSubject.field"}}, testCredentialMap) require.NoError(t, err) assert.True(t, match) }) t.Run("no match", func(t *testing.T) { - match, err := matchField(Field{Path: []string{"$.foo", "$.bar"}}, testCredential) + match, err := matchField(Field{Path: []string{"$.foo", "$.bar"}}, testCredentialMap) require.NoError(t, err) assert.False(t, match) }) t.Run("no match, but optional", func(t *testing.T) { trueVal := true - match, err := matchField(Field{Path: []string{"$.foo", "$.bar"}, Optional: &trueVal}, testCredential) + match, err := matchField(Field{Path: []string{"$.foo", "$.bar"}, Optional: &trueVal}, testCredentialMap) require.NoError(t, err) assert.True(t, match) @@ -423,21 +555,21 @@ func Test_matchField(t *testing.T) { t.Run("invalid match and optional", func(t *testing.T) { trueVal := true stringVal := "bar" - match, err := matchField(Field{Path: []string{"$.credentialSubject.field", "$.foo"}, Optional: &trueVal, Filter: &Filter{Const: &stringVal}}, testCredential) + match, err := matchField(Field{Path: []string{"$.credentialSubject.field", "$.foo"}, Optional: &trueVal, Filter: &Filter{Const: &stringVal}}, testCredentialMap) require.NoError(t, err) assert.False(t, match) }) t.Run("valid match with Filter", func(t *testing.T) { stringVal := "value" - match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredential) + match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredentialMap) require.NoError(t, err) assert.True(t, match) }) t.Run("match on type", func(t *testing.T) { stringVal := "VerifiableCredential" - match, err := matchField(Field{Path: []string{"$.type"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredential) + match, err := matchField(Field{Path: []string{"$.type"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredentialMap) require.NoError(t, err) assert.True(t, match) @@ -450,9 +582,9 @@ func Test_matchField(t *testing.T) { "field": "value" } }` - _ = json.Unmarshal([]byte(testCredentialString), &testCredential) + _ = json.Unmarshal([]byte(testCredentialString), &testCredentialMap) stringVal := "VerifiableCredential" - match, err := matchField(Field{Path: []string{"$.type"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredential) + match, err := matchField(Field{Path: []string{"$.type"}, Filter: &Filter{Type: "string", Const: &stringVal}}, testCredentialMap) require.NoError(t, err) assert.True(t, match) @@ -460,14 +592,14 @@ func Test_matchField(t *testing.T) { t.Run("errors", func(t *testing.T) { t.Run("invalid path", func(t *testing.T) { - match, err := matchField(Field{Path: []string{"$$"}}, testCredential) + match, err := matchField(Field{Path: []string{"$$"}}, testCredentialMap) require.Error(t, err) assert.False(t, match) }) t.Run("invalid pattern", func(t *testing.T) { pattern := "[" - match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}, Filter: &Filter{Type: "string", Pattern: &pattern}}, testCredential) + match, err := matchField(Field{Path: []string{"$.credentialSubject.field"}, Filter: &Filter{Type: "string", Pattern: &pattern}}, testCredentialMap) require.Error(t, err) assert.False(t, match) @@ -554,3 +686,16 @@ func Test_matchFilter(t *testing.T) { }) }) } + +func credentialToJSONLD(credential vc.VerifiableCredential) vc.VerifiableCredential { + bytes, err := credential.MarshalJSON() + if err != nil { + panic(err) + } + var result vc.VerifiableCredential + err = json.Unmarshal(bytes, &result) + if err != nil { + panic(err) + } + return result +}