diff --git a/vcr/credential/test.go b/vcr/credential/test.go index 363567b84e..41cf48022b 100644 --- a/vcr/credential/test.go +++ b/vcr/credential/test.go @@ -20,8 +20,14 @@ package credential import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/json" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/nuts-foundation/nuts-node/vcr/assets" + "github.com/stretchr/testify/require" "testing" "time" @@ -68,6 +74,26 @@ func ValidNutsOrganizationCredential(t *testing.T) vc.VerifiableCredential { return inputVC } +func JWTNutsOrganizationCredential(t *testing.T) vc.VerifiableCredential { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + 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", + })) + signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES384, privateKey)) + require.NoError(t, err) + jwtVC, err := vc.ParseVerifiableCredential(string(signedToken)) + require.NoError(t, err) + return *jwtVC +} + func stringToURI(input string) ssi.URI { return ssi.MustParseURI(input) } diff --git a/vcr/pe/presentation_definition.go b/vcr/pe/presentation_definition.go index cfd2a84d26..3edbeefd94 100644 --- a/vcr/pe/presentation_definition.go +++ b/vcr/pe/presentation_definition.go @@ -233,7 +233,7 @@ func matchFormat(format *PresentationDefinitionClaimFormatDesignations, credenti asMap := map[string]map[string][]string(*format) switch credential.Format() { case vc.JSONLDCredentialProofFormat: - if entry := asMap["ldp_vc"]; entry != nil { + if entry := asMap[vc.JSONLDCredentialProofFormat]; entry != nil { if proofTypes := entry["proof_type"]; proofTypes != nil { for _, proofType := range proofTypes { if matchProofType(proofType, credential) { @@ -245,10 +245,10 @@ func matchFormat(format *PresentationDefinitionClaimFormatDesignations, credenti 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") + signingAlgorithm, _ := message.Signatures()[0].ProtectedHeaders().Get(jws.AlgorithmKey) // Check that the signing algorithm is specified by the presentation definition - if entry := asMap["jwt_vc"]; entry != nil { - if supportedAlgorithms := entry["alg"]; supportedAlgorithms != nil { + if entry := asMap[vc.JWTCredentialProofFormat]; entry != nil { + if supportedAlgorithms := entry[jws.AlgorithmKey]; supportedAlgorithms != nil { for _, supportedAlgorithm := range supportedAlgorithms { if signingAlgorithm == jwa.SignatureAlgorithm(supportedAlgorithm) { return true diff --git a/vcr/pe/presentation_definition_test.go b/vcr/pe/presentation_definition_test.go index 3ecd2fd6b4..9e850deb8e 100644 --- a/vcr/pe/presentation_definition_test.go +++ b/vcr/pe/presentation_definition_test.go @@ -49,6 +49,7 @@ var testFiles embed.FS var pdJSONLD PresentationDefinition var pdJSONLDorJWT PresentationDefinition +var pdJSONLDorJWTWithPick PresentationDefinition var pdJWT PresentationDefinition func init() { @@ -66,6 +67,13 @@ func init() { panic(err) } } + if data, err := testFiles.ReadFile("test/pd_jsonld_jwt_pick.json"); err != nil { + panic(err) + } else { + if err = json.Unmarshal(data, &pdJSONLDorJWTWithPick); err != nil { + panic(err) + } + } if data, err := testFiles.ReadFile("test/pd_jwt.json"); err != nil { panic(err) } else { @@ -76,26 +84,8 @@ func init() { } func TestMatch(t *testing.T) { - // Create a JSON-LD VC jsonldVC := credential.ValidNutsOrganizationCredential(t) - - // Create a JWT VC - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - require.NoError(t, err) - 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", - })) - signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES384, privateKey)) - require.NoError(t, err) - jwtVC, err := vc.ParseVerifiableCredential(string(signedToken)) - require.NoError(t, err) + jwtVC := credential.JWTNutsOrganizationCredential(t) t.Run("Basic", func(t *testing.T) { t.Run("JSON-LD", func(t *testing.T) { @@ -117,13 +107,15 @@ func TestMatch(t *testing.T) { }) t.Run("JWT", func(t *testing.T) { t.Run("Happy flow", func(t *testing.T) { - vcs, mappingObjects, err := pdJWT.Match([]vc.VerifiableCredential{*jwtVC}) + vcs, mappingObjects, err := pdJWT.Match([]vc.VerifiableCredential{jwtVC}) 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) { + token := jwt.New() + privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) signedToken, err := jwt.Sign(token, jwt.WithKey(jwa.ES256, privateKey)) require.NoError(t, err) jwtCredential, err := vc.ParseVerifiableCredential(string(signedToken)) @@ -158,15 +150,29 @@ func TestMatch(t *testing.T) { assert.Len(t, vcs, 1) assert.Len(t, mappingObjects, 1) }) - t.Run("pick JSON-LD", func(t *testing.T) { + t.Run("choose JSON-LD (with multiple 'path's)", func(t *testing.T) { vcs, mappingObjects, err := pdJSONLDorJWT.Match([]vc.VerifiableCredential{jsonldVC}) require.NoError(t, err) assert.Len(t, vcs, 1) require.Len(t, mappingObjects, 1) }) - t.Run("pick JWT", func(t *testing.T) { - vcs, mappingObjects, err := pdJSONLDorJWT.Match([]vc.VerifiableCredential{*jwtVC}) + t.Run("choose JWT(with multiple 'path's)", func(t *testing.T) { + vcs, mappingObjects, err := pdJSONLDorJWT.Match([]vc.VerifiableCredential{jwtVC}) + + require.NoError(t, err) + assert.Len(t, vcs, 1) + require.Len(t, mappingObjects, 1) + }) + t.Run("choose JSON-LD (with 'pick')", func(t *testing.T) { + vcs, mappingObjects, err := pdJSONLDorJWTWithPick.Match([]vc.VerifiableCredential{jsonldVC}) + + require.NoError(t, err) + assert.Len(t, vcs, 1) + require.Len(t, mappingObjects, 1) + }) + t.Run("choose JWT (with 'pick')", func(t *testing.T) { + vcs, mappingObjects, err := pdJSONLDorJWTWithPick.Match([]vc.VerifiableCredential{jwtVC}) require.NoError(t, err) assert.Len(t, vcs, 1) @@ -320,6 +326,7 @@ func Test_matchFormat(t *testing.T) { }) t.Run("JWT", func(t *testing.T) { + // JWT example credential taken from VC data model (expired) const jwtCredential = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDpleGFtcGxlOmFiZmUxM2Y3MTIxMjA0 MzFjMjc2ZTEyZWNhYiNrZXlzLTEifQ.eyJzdWIiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxY zI3NmUxMmVjMjEiLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsImlzc diff --git a/vcr/pe/test/pd_jsonld_jwt.json b/vcr/pe/test/pd_jsonld_jwt.json index 3d0010b359..45f77f383a 100644 --- a/vcr/pe/test/pd_jsonld_jwt.json +++ b/vcr/pe/test/pd_jsonld_jwt.json @@ -1,59 +1,12 @@ { "id": "Definition requesting NutsOrganizationCredential", - "submission_requirements": [ - { - "rule": "pick", - "count": 1, - "from": "vc" - } - ], "input_descriptors": [ { - "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.organization.city", "$.credentialSubject[0].organization.city" ], "filter": { @@ -63,6 +16,7 @@ }, { "path": [ + "$.credentialSubject.organization.name", "$.credentialSubject[0].organization.name" ], "filter": { diff --git a/vcr/pe/test/pd_jsonld_jwt_pick.json b/vcr/pe/test/pd_jsonld_jwt_pick.json new file mode 100644 index 0000000000..3d0010b359 --- /dev/null +++ b/vcr/pe/test/pd_jsonld_jwt_pick.json @@ -0,0 +1,99 @@ +{ + "id": "Definition requesting NutsOrganizationCredential", + "submission_requirements": [ + { + "rule": "pick", + "count": 1, + "from": "vc" + } + ], + "input_descriptors": [ + { + "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" + ] + }, + "ldp_vc": { + "proof_type": [ + "JsonWebSignature2020" + ] + } + } +} \ No newline at end of file