diff --git a/auth/api/iam/s2s_vptoken.go b/auth/api/iam/s2s_vptoken.go index cab7c19853..939877f830 100644 --- a/auth/api/iam/s2s_vptoken.go +++ b/auth/api/iam/s2s_vptoken.go @@ -20,7 +20,6 @@ package iam import ( "context" - "encoding/json" "errors" "fmt" "net/url" @@ -66,7 +65,7 @@ func (s Wrapper) handleS2SAccessTokenRequest(issuer did.DID, params map[string]s if err != nil { return nil, oauth.OAuth2Error{ Code: oauth.InvalidRequest, - Description: "assertion parameter is invalid: invalid query escaping", + Description: "assertion parameter is invalid", InternalError: err, } } @@ -83,62 +82,33 @@ func (s Wrapper) handleS2SAccessTokenRequest(issuer did.DID, params map[string]s if err != nil { return nil, oauth.OAuth2Error{ Code: oauth.InvalidRequest, - Description: "presentation_submission parameter is invalid: invalid query escaping", + Description: "presentation_submission parameter is invalid", InternalError: err, } } submission, err := pe.ParsePresentationSubmission([]byte(submissionDecoded)) - if err = json.Unmarshal([]byte(submissionDecoded), &submission); err != nil { + if err != nil { return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: "presentation_submission parameter is invalid: invalid JSON", - InternalError: err, + Code: oauth.InvalidRequest, + Description: fmt.Sprintf("invalid presentation submission: %s", err.Error()), } } for _, presentation := range pexEnvelope.Presentations { - // Presenter should be credential holder - err = credential.VerifyPresenterIsCredentialSubject(presentation) - if err != nil { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: fmt.Sprintf("verifiable presentation is invalid: %s", err.Error()), - } - } - // Presentation should not be valid for too long - created := credential.PresentationIssuanceDate(presentation) - expires := credential.PresentationExpirationDate(presentation) - if created == nil || expires == nil { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: "assertion parameter is invalid: missing creation or expiration date", - } + if err := validatePresentationValidity(presentation); err != nil { + return nil, err } - if expires.Sub(*created) > maxPresentationValidity { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: fmt.Sprintf("assertion parameter is invalid: presentation is valid for too long (max %s)", maxPresentationValidity), - } + if err := validatePresentationSigner(presentation); err != nil { + return nil, err } } - - // Validate the presentation submission: - // 1. Resolve presentation definition for the requested scope - // 2. Check submission against presentation and definition - definition := s.auth.PresentationDefinitions().ByScope(scope) - if definition == nil { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidScope, - Description: fmt.Sprintf("unsupported scope for presentation exchange: %s", scope), - } + var definition *PresentationDefinition + if definition, err = s.validatePresentationSubmission(scope, submission, pexEnvelope); err != nil { + return nil, err } - - _, err = submission.Validate(*pexEnvelope, *definition) - if err != nil { - return nil, oauth.OAuth2Error{ - Code: oauth.InvalidRequest, - Description: "presentation submission does not conform to Presentation Definition", - InternalError: err, + for _, presentation := range pexEnvelope.Presentations { + if err := s.validatePresentationNonce(presentation); err != nil { + return nil, err } } @@ -148,7 +118,7 @@ func (s Wrapper) handleS2SAccessTokenRequest(issuer did.DID, params map[string]s if err != nil { return nil, oauth.OAuth2Error{ Code: oauth.InvalidRequest, - Description: "verifiable presentation is invalid", + Description: "presentation(s) or contained credential(s) are invalid", InternalError: err, } } @@ -228,6 +198,108 @@ func (r Wrapper) createAccessToken(issuer did.DID, issueTime time.Time, presenta }, nil } +func (s Wrapper) validatePresentationSubmission(scope string, submission *pe.PresentationSubmission, pexEnvelope *pe.Envelope) (*PresentationDefinition, error) { + // Validate the presentation submission: + // 1. Resolve presentation definition for the requested scope + // 2. Check submission against presentation and definition + definition := s.auth.PresentationDefinitions().ByScope(scope) + if definition == nil { + return nil, oauth.OAuth2Error{ + Code: oauth.InvalidScope, + Description: fmt.Sprintf("unsupported scope for presentation exchange: %s", scope), + } + } + + _, err := submission.Validate(*pexEnvelope, *definition) + if err != nil { + return nil, oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: "presentation submission does not conform to Presentation Definition", + InternalError: err, + } + } + return definition, err +} + +func validatePresentationValidity(presentation vc.VerifiablePresentation) error { + // Presentation should not be valid for too long + created := credential.PresentationIssuanceDate(presentation) + expires := credential.PresentationExpirationDate(presentation) + if created == nil || expires == nil { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: "presentation is missing creation or expiration date", + } + } + if expires.Sub(*created) > maxPresentationValidity { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: fmt.Sprintf("presentation is valid for too long (max %s)", maxPresentationValidity), + } + } + return nil +} + +// validatePresentationSigner checks if the presenter of the VP is the same as the subject of the VCs being presented. +func validatePresentationSigner(presentation vc.VerifiablePresentation) error { + ok, err := credential.PresenterIsCredentialSubject(presentation) + if err != nil { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: err.Error(), + } + } + if !ok { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: "presentation signer is not credential subject", + } + } + return nil +} + +// validatePresentationNonce checks if the nonce has been used before; 'jti' claim for JWTs or LDProof's 'nonce' for JSON-LD. +func (s Wrapper) validatePresentationNonce(presentation vc.VerifiablePresentation) error { + var nonce string + switch presentation.Format() { + case vc.JWTPresentationProofFormat: + nonce = presentation.JWT().JwtID() + if nonce == "" { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: "presentation is missing jti", + } + } + case vc.JSONLDPresentationProofFormat: + proof, err := credential.ParseLDProof(presentation) + if err != nil || proof.Nonce == nil { + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + InternalError: err, + Description: "presentation has invalid proof or nonce", + } + } + nonce = *proof.Nonce + } + + nonceStore := s.storageEngine.GetSessionDatabase().GetStore(maxPresentationValidity, "s2s", "nonce") + err := nonceStore.Get(nonce, new(bool)) + if !errors.Is(err, storage.ErrNotFound) { + if err != nil { + // unable to check nonce + return err + } + return oauth.OAuth2Error{ + Code: oauth.InvalidRequest, + Description: "presentation nonce has already been used", + } + } + if err := nonceStore.Put(nonce, true); err != nil { + return fmt.Errorf("unable to store nonce: %w", err) + } + return nil +} + func (r Wrapper) s2sAccessTokenStore() storage.SessionStore { return r.storageEngine.GetSessionDatabase().GetStore(accessTokenValidity, "s2s", "accesstoken") } diff --git a/auth/api/iam/s2s_vptoken_test.go b/auth/api/iam/s2s_vptoken_test.go index 6e6807527f..aa6de4bdb8 100644 --- a/auth/api/iam/s2s_vptoken_test.go +++ b/auth/api/iam/s2s_vptoken_test.go @@ -37,7 +37,6 @@ import ( "github.com/nuts-foundation/nuts-node/jsonld" "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vcr/pe" - "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "github.com/nuts-foundation/nuts-node/vcr/test" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/stretchr/testify/assert" @@ -193,7 +192,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - require.EqualError(t, err, "invalid_request - assertion parameter is invalid: missing creation or expiration date") + require.EqualError(t, err, "invalid_request - presentation is missing creation or expiration date") }) t.Run("missing presentation not before date", func(t *testing.T) { ctx := newTestClient(t) @@ -209,7 +208,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - require.EqualError(t, err, "invalid_request - assertion parameter is invalid: missing creation or expiration date") + require.EqualError(t, err, "invalid_request - presentation is missing creation or expiration date") }) t.Run("missing presentation valid for too long", func(t *testing.T) { ctx := newTestClient(t) @@ -225,7 +224,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - require.EqualError(t, err, "invalid_request - assertion parameter is invalid: presentation is valid for too long (max 10s)") + require.EqualError(t, err, "invalid_request - presentation is valid for too long (max 10s)") }) t.Run("JWT VP", func(t *testing.T) { ctx := newTestClient(t) @@ -248,6 +247,22 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { assert.Equal(t, int(accessTokenValidity.Seconds()), *tokenResponse.ExpiresIn) assert.NotEmpty(t, tokenResponse.AccessToken) }) + t.Run("replay attack (nonce is reused)", func(t *testing.T) { + ctx := newTestClient(t) + ctx.verifier.EXPECT().VerifyVP(presentation, true, true, gomock.Any()).Return(presentation.VerifiableCredential, nil) + params := map[string]string{ + "assertion": url.QueryEscape(presentation.Raw()), + "presentation_submission": url.QueryEscape(string(submissionJSON)), + "scope": requestedScope, + } + + _, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) + require.NoError(t, err) + + resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) + assert.EqualError(t, err, "invalid_request - presentation nonce has already been used") + assert.Nil(t, resp) + }) t.Run("VP is not valid JSON", func(t *testing.T) { ctx := newTestClient(t) params := map[string]string{ @@ -273,37 +288,15 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - assert.EqualError(t, err, "invalid_request - invalid - verifiable presentation is invalid") + assert.EqualError(t, err, "invalid_request - invalid - presentation(s) or contained credential(s) are invalid") assert.Nil(t, resp) }) t.Run("proof of ownership", func(t *testing.T) { - t.Run("VP has no proof", func(t *testing.T) { - ctx := newTestClient(t) - verifiablePresentation := vc.VerifiablePresentation{ - VerifiableCredential: []vc.VerifiableCredential{verifiableCredential}, - } - verifiablePresentationJSON, _ := verifiablePresentation.MarshalJSON() - params := map[string]string{ - "assertion": url.QueryEscape(string(verifiablePresentationJSON)), - "presentation_submission": url.QueryEscape(string(submissionJSON)), - "scope": requestedScope, - } - - resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - - assert.EqualError(t, err, `invalid_request - verifiable presentation is invalid: presentation should have exactly 1 proof, got 0`) - assert.Nil(t, resp) - }) t.Run("VC without credentialSubject.id", func(t *testing.T) { ctx := newTestClient(t) - verifiablePresentation := vc.VerifiablePresentation{ - VerifiableCredential: []vc.VerifiableCredential{ - { - CredentialSubject: []interface{}{map[string]string{}}, - }, - }, - Proof: []interface{}{proof.LDProof{Type: ssi.JsonWebSignature2020}}, - } + verifiablePresentation := test.CreateJSONLDPresentation(t, *subjectDID, vc.VerifiableCredential{ + CredentialSubject: []interface{}{map[string]string{}}, + }) verifiablePresentationJSON, _ := verifiablePresentation.MarshalJSON() params := map[string]string{ "assertion": url.QueryEscape(string(verifiablePresentationJSON)), @@ -313,20 +306,16 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - assert.EqualError(t, err, `invalid_request - verifiable presentation is invalid: invalid verification method for JSON-LD presentation: %!w()`) + assert.EqualError(t, err, `invalid_request - unable to get subject DID from VC: credential subjects have no ID`) assert.Nil(t, resp) }) t.Run("signing key is not owned by credentialSubject.id", func(t *testing.T) { ctx := newTestClient(t) - otherKeyID := ssi.MustParseURI("did:example:other#1") + invalidProof := presentation.Proof[0].(map[string]interface{}) + invalidProof["verificationMethod"] = "did:example:other#1" verifiablePresentation := vc.VerifiablePresentation{ VerifiableCredential: []vc.VerifiableCredential{verifiableCredential}, - Proof: []interface{}{ - proof.LDProof{ - Type: ssi.JsonWebSignature2020, - VerificationMethod: otherKeyID, - }, - }, + Proof: []interface{}{invalidProof}, } verifiablePresentationJSON, _ := verifiablePresentation.MarshalJSON() params := map[string]string{ @@ -337,7 +326,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - assert.EqualError(t, err, `invalid_request - verifiable presentation is invalid: not all VC credentialSubject.id match VP signer`) + assert.EqualError(t, err, `invalid_request - presentation signer is not credential subject`) assert.Nil(t, resp) }) }) @@ -351,7 +340,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, params) - assert.EqualError(t, err, `invalid_request - invalid character 'o' in literal null (expecting 'u') - presentation_submission parameter is invalid: invalid JSON`) + assert.EqualError(t, err, `invalid_request - invalid presentation submission: invalid character 'o' in literal null (expecting 'u')`) assert.Nil(t, resp) }) t.Run("unsupported scope", func(t *testing.T) { diff --git a/vcr/credential/resolver.go b/vcr/credential/resolver.go index eda669ff84..0a431d36bc 100644 --- a/vcr/credential/resolver.go +++ b/vcr/credential/resolver.go @@ -73,17 +73,27 @@ func PresentationSigner(presentation vc.VerifiablePresentation) (*did.DID, error case vc.JSONLDCredentialProofFormat: fallthrough default: - var proofs []proof.LDProof - if err := presentation.UnmarshalProofValue(&proofs); err != nil { - return nil, fmt.Errorf("invalid LD-proof for presentation: %w", err) + proof, err := ParseLDProof(presentation) + if err != nil { + return nil, err } - if len(proofs) != 1 { - return nil, fmt.Errorf("presentation should have exactly 1 proof, got %d", len(proofs)) - } - verificationMethod, err := did.ParseDIDURL(proofs[0].VerificationMethod.String()) + verificationMethod, err := did.ParseDIDURL(proof.VerificationMethod.String()) if err != nil || verificationMethod.DID.Empty() { return nil, fmt.Errorf("invalid verification method for JSON-LD presentation: %w", err) } return &verificationMethod.DID, nil } } + +// ParseLDProof parses the LinkedData proof from the presentation. +// It returns an error if the presentation does not have exactly 1 proof. +func ParseLDProof(presentation vc.VerifiablePresentation) (*proof.LDProof, error) { + var proofs []proof.LDProof + if err := presentation.UnmarshalProofValue(&proofs); err != nil { + return nil, fmt.Errorf("invalid LD-proof for presentation: %w", err) + } + if len(proofs) != 1 { + return nil, fmt.Errorf("presentation should have exactly 1 proof, got %d", len(proofs)) + } + return &proofs[0], nil +} diff --git a/vcr/credential/util.go b/vcr/credential/util.go index 844a768102..3a00f35f06 100644 --- a/vcr/credential/util.go +++ b/vcr/credential/util.go @@ -22,7 +22,6 @@ import ( "errors" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" - "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "time" ) @@ -45,24 +44,22 @@ func ResolveSubjectDID(credentials ...vc.VerifiableCredential) (*did.DID, error) return &subjectID, nil } -// VerifyPresenterIsCredentialSubject checks if the presenter of the VP is the same as the subject of the VCs being presented. -// It returns an error when: -// - the VP proof is unsupported. -// - the VP signer is not the same as the subject of the VCs. -// If the check succeeds, it returns nil. -func VerifyPresenterIsCredentialSubject(vp vc.VerifiablePresentation) error { +// PresenterIsCredentialSubject checks if the presenter of the VP is the same as the subject of the VCs being presented. +// If the presentation signer or credential subject can't be resolved, it returns an error. +// If parsing succeeds and the signer DID is the same as the credential subject DID, it returns true. +func PresenterIsCredentialSubject(vp vc.VerifiablePresentation) (bool, error) { signerDID, err := PresentationSigner(vp) if err != nil { - return err + return false, err } credentialSubjectID, err := ResolveSubjectDID(vp.VerifiableCredential...) if err != nil { - return err + return false, err } if !credentialSubjectID.Equals(*signerDID) { - return errors.New("not all VC credentialSubject.id match VP signer") + return false, nil } - return nil + return true, nil } // PresentationIssuanceDate returns the date at which the presentation was issued. @@ -78,14 +75,11 @@ func PresentationIssuanceDate(presentation vc.VerifiablePresentation) *time.Time result = jwt.NotBefore() } case vc.JSONLDPresentationProofFormat: - var proofs []proof.LDProof - if err := presentation.UnmarshalProofValue(&proofs); err != nil { - return nil - } - if len(proofs) == 0 { + ldProof, err := ParseLDProof(presentation) + if err != nil { return nil } - result = proofs[0].Created + result = ldProof.Created } if result.IsZero() { return nil @@ -103,16 +97,11 @@ func PresentationExpirationDate(presentation vc.VerifiablePresentation) *time.Ti case vc.JWTPresentationProofFormat: result = presentation.JWT().Expiration() case vc.JSONLDPresentationProofFormat: - var proofs []proof.LDProof - if err := presentation.UnmarshalProofValue(&proofs); err != nil { + ldProof, err := ParseLDProof(presentation) + if err != nil || ldProof.Expires == nil { return nil } - if len(proofs) == 0 { - return nil - } - if proofs[0].Expires != nil { - result = *proofs[0].Expires - } + result = *ldProof.Expires } if result.IsZero() { return nil diff --git a/vcr/credential/util_test.go b/vcr/credential/util_test.go index f2e6fe1e16..958b63e01a 100644 --- a/vcr/credential/util_test.go +++ b/vcr/credential/util_test.go @@ -66,7 +66,7 @@ func TestResolveSubjectDID(t *testing.T) { } -func TestVerifyPresenterIsCredentialSubject(t *testing.T) { +func TestPresenterIsCredentialSubject(t *testing.T) { subjectDID := ssi.MustParseURI("did:test:123") keyID := ssi.MustParseURI("did:test:123#1") t.Run("ok", func(t *testing.T) { @@ -83,13 +83,15 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { }, }, } - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.NoError(t, err) + assert.True(t, is) }) t.Run("no proof", func(t *testing.T) { vp := vc.VerifiablePresentation{} - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "presentation should have exactly 1 proof, got 0") + assert.False(t, is) }) t.Run("no VC subject", func(t *testing.T) { vp := vc.VerifiablePresentation{ @@ -103,8 +105,9 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { {}, }, } - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "unable to get subject DID from VC: there must be at least 1 credentialSubject") + assert.False(t, is) }) t.Run("no VC subject ID", func(t *testing.T) { vp := vc.VerifiablePresentation{ @@ -120,8 +123,9 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { }, }, } - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "unable to get subject DID from VC: credential subjects have no ID") + assert.False(t, is) }) t.Run("proof verification method does not equal VC subject ID", func(t *testing.T) { vp := vc.VerifiablePresentation{ @@ -137,8 +141,9 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { }, }, } - err := VerifyPresenterIsCredentialSubject(vp) - assert.EqualError(t, err, "not all VC credentialSubject.id match VP signer") + is, err := PresenterIsCredentialSubject(vp) + assert.NoError(t, err) + assert.False(t, is) }) t.Run("proof type is unsupported", func(t *testing.T) { vp := vc.VerifiablePresentation{ @@ -151,8 +156,9 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { }, }, } - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "invalid LD-proof for presentation: json: cannot unmarshal bool into Go value of type proof.LDProof") + assert.False(t, is) }) t.Run("too many proofs", func(t *testing.T) { vp := vc.VerifiablePresentation{ @@ -166,8 +172,9 @@ func TestVerifyPresenterIsCredentialSubject(t *testing.T) { }, }, } - err := VerifyPresenterIsCredentialSubject(vp) + is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "presentation should have exactly 1 proof, got 2") + assert.False(t, is) }) } diff --git a/vcr/pe/util_test.go b/vcr/pe/util_test.go index 244b183167..921ada4c3e 100644 --- a/vcr/pe/util_test.go +++ b/vcr/pe/util_test.go @@ -29,7 +29,7 @@ import ( func TestParseEnvelope(t *testing.T) { t.Run("JWT", func(t *testing.T) { - presentation := test.CreateJWTPresentation(t, did.MustParseDID("did:example:1"), credential.ValidNutsOrganizationCredential(t)) + presentation := test.CreateJWTPresentation(t, did.MustParseDID("did:example:1"), nil, credential.ValidNutsOrganizationCredential(t)) envelope, err := ParseEnvelope([]byte(presentation.Raw())) require.NoError(t, err) require.Equal(t, presentation.ID.String(), envelope.Interface.(map[string]interface{})["id"]) diff --git a/vcr/test/test.go b/vcr/test/test.go index 7a02a7dc5d..b60d1b6c62 100644 --- a/vcr/test/test.go +++ b/vcr/test/test.go @@ -29,6 +29,7 @@ import ( ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" + "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "github.com/stretchr/testify/require" "testing" @@ -69,6 +70,7 @@ func CreateJWTPresentation(t *testing.T, subjectDID did.DID, tokenVisitor func(t func CreateJSONLDPresentation(t *testing.T, subjectDID did.DID, verifiableCredential ...vc.VerifiableCredential) vc.VerifiablePresentation { id := ssi.MustParseURI(subjectDID.String() + "#" + uuid.NewString()) exp := time.Now().Add(5 * time.Second) + nonce := crypto.GenerateNonce() vp := vc.VerifiablePresentation{ ID: &id, VerifiableCredential: verifiableCredential, @@ -80,6 +82,7 @@ func CreateJSONLDPresentation(t *testing.T, subjectDID did.DID, verifiableCreden Created: time.Now(), Expires: &exp, }, + Nonce: &nonce, }, }, } diff --git a/vcr/verifier/verifier.go b/vcr/verifier/verifier.go index e1b60fce94..3ce3ffacc0 100644 --- a/vcr/verifier/verifier.go +++ b/vcr/verifier/verifier.go @@ -345,12 +345,10 @@ func (v *verifier) validateJSONLDPresentation(presentation vc.VerifiablePresenta return newVerificationError("exactly 1 proof is expected") } // Make sure the proofs are LD-proofs - var ldProofs []proof.LDProof - err := presentation.UnmarshalProofValue(&ldProofs) + ldProof, err := credential.ParseLDProof(presentation) if err != nil { return newVerificationError("unsupported proof type: %w", err) } - ldProof := ldProofs[0] // Validate signing time if !v.validateAtTime(ldProof.Created, ldProof.Expires, validAt) { diff --git a/vcr/verifier/verifier_test.go b/vcr/verifier/verifier_test.go index 57746924dc..0bc043db26 100644 --- a/vcr/verifier/verifier_test.go +++ b/vcr/verifier/verifier_test.go @@ -793,7 +793,7 @@ func TestVerifier_VerifyVP(t *testing.T) { vcs, err := ctx.verifier.VerifyVP(vp, false, false, validAt) - assert.EqualError(t, err, "verification error: unsupported proof type: json: cannot unmarshal string into Go value of type proof.LDProof") + assert.EqualError(t, err, "verification error: unsupported proof type: invalid LD-proof for presentation: json: cannot unmarshal string into Go value of type proof.LDProof") assert.Empty(t, vcs) }) t.Run("error - no proof", func(t *testing.T) {