diff --git a/vcr/credential/resolver.go b/vcr/credential/resolver.go index fd4ec48ac8..acab842d4e 100644 --- a/vcr/credential/resolver.go +++ b/vcr/credential/resolver.go @@ -20,7 +20,11 @@ package credential import ( + "errors" + "fmt" + "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" + "github.com/nuts-foundation/nuts-node/vcr/signature/proof" ) // FindValidator finds the Validator the provided credential based on its Type @@ -52,3 +56,34 @@ func ExtractTypes(credential vc.VerifiableCredential) []string { return vcTypes } + +func PresentationSigner(presentation vc.VerifiablePresentation) (*did.DID, error) { + switch presentation.Format() { + case vc.JWTPresentationProofFormat: + token := presentation.JWT() + issuer := token.Issuer() + if issuer == "" { + return nil, errors.New("JWT presentation does not have 'iss' claim") + } + return did.ParseDID(issuer) + 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) + } + 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()) + if err != nil { + return nil, fmt.Errorf("invalid verification method for JSON-LD presentation: %w", err) + } else { + if verificationMethod.DID.Empty() { + return nil, fmt.Errorf("verification method for JSON-LD presentation does not have a DID") + } + return &verificationMethod.DID, nil + } + } +} diff --git a/vcr/pe/presentation_submission.go b/vcr/pe/presentation_submission.go index 423283f903..d64e46ec12 100644 --- a/vcr/pe/presentation_submission.go +++ b/vcr/pe/presentation_submission.go @@ -20,11 +20,11 @@ package pe import ( "encoding/json" - "errors" "fmt" "github.com/google/uuid" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" + "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vcr/log" v2 "github.com/nuts-foundation/nuts-node/vcr/pe/schema/v2" "reflect" @@ -144,18 +144,23 @@ func (b *PresentationSubmissionBuilder) Build(format string) (PresentationSubmis // Validate validates the Presentation Submission to the Verifiable Presentation and Presentation Definition and returns the mapped credentials. // The credentials will be returned as map with the InputDescriptor.Id as key. // It assumes credentials of the presentation only map in 1 way to the input descriptors. -func (s PresentationSubmission) Validate(presentation vc.VerifiablePresentation, definition PresentationDefinition) (map[string]vc.VerifiableCredential, error) { - expectedCredentials, expectedDescriptorMap, err := definition.Match(presentation.VerifiableCredential) - if err != nil { - return nil, fmt.Errorf("credential submission is invalid: %w", err) +func (s PresentationSubmission) Validate(presentations []vc.VerifiablePresentation, definition PresentationDefinition) (map[string]vc.VerifiableCredential, error) { + builder := definition.PresentationSubmissionBuilder() + for _, presentation := range presentations { + signer, err := credential.PresentationSigner(presentation) + if err != nil { + return nil, fmt.Errorf("unable to derive presentation signer: %w", err) + } + builder.AddWallet(*signer, presentation.VerifiableCredential) } - if len(expectedCredentials) == 0 { - return nil, errors.New("credential submission is invalid, credentials do not match the presentation definition") + presentationSubmission, signInstructions, err := builder.Build("") + if err != nil { + return nil, err } // Marshal, then unmarshal descriptor mappings into interface{}, to make sure ordering and zero-handling is the same for both. // Then, they can simply be compared to check that the submission var expected interface{} - if err := remarshal(expectedDescriptorMap, &expected); err != nil { + if err := remarshal(presentationSubmission.DescriptorMap, &expected); err != nil { return nil, err } var actual interface{} @@ -169,9 +174,12 @@ func (s PresentationSubmission) Validate(presentation vc.VerifiablePresentation, return nil, fmt.Errorf("credential submission is invalid, input descriptor mapping looks invalid") } credentialMap := make(map[string]vc.VerifiableCredential, len(s.DescriptorMap)) - for i, inputDescriptor := range s.DescriptorMap { - credentialMap[inputDescriptor.Id] = expectedCredentials[i] + for _, signInstruction := range signInstructions { + for i, inputDescriptor := range signInstruction.inputDescriptorMappingObjects { + credentialMap[inputDescriptor.Id] = signInstruction.VerifiableCredentials[i] + } } + return credentialMap, nil } diff --git a/vcr/pe/presentation_submission_test.go b/vcr/pe/presentation_submission_test.go index fedd7e9ce0..41b534b03b 100644 --- a/vcr/pe/presentation_submission_test.go +++ b/vcr/pe/presentation_submission_test.go @@ -24,6 +24,7 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/vcr/pe/test" + "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" @@ -88,14 +89,17 @@ func TestPresentationSubmissionBuilder_Build(t *testing.T) { } func TestPresentationSubmission_Validate(t *testing.T) { - vcID := ssi.MustParseURI("first-vc") + vcID := ssi.MustParseURI("did:example:123#first-vc") vp := vc.VerifiablePresentation{ VerifiableCredential: []vc.VerifiableCredential{ {ID: &vcID}, }, + Proof: []interface{}{ + proof.LDProof{VerificationMethod: vcID}, + }, } - t.Run("ok", func(t *testing.T) { + t.Run("ok - 1 presentation", func(t *testing.T) { constant := vcID.String() definition := PresentationDefinition{ InputDescriptors: []*InputDescriptor{ @@ -124,7 +128,70 @@ func TestPresentationSubmission_Validate(t *testing.T) { }, } - credentials, err := submission.Validate(vp, definition) + credentials, err := submission.Validate([]vc.VerifiablePresentation{vp}, definition) + + assert.NoError(t, err) + assert.Len(t, credentials, 1) + assert.Equal(t, vcID.String(), credentials["1"].ID.String()) + }) + t.Run("ok - 2 presentations", func(t *testing.T) { + constant1 := vcID.String() + secondVCID := ssi.MustParseURI("did:example:123#second-vc") + constant2 := secondVCID.String() + secondVP := vc.VerifiablePresentation{ + VerifiableCredential: []vc.VerifiableCredential{ + {ID: &secondVCID}, + }, + Proof: []interface{}{ + proof.LDProof{VerificationMethod: vcID}, + }, + } + definition := PresentationDefinition{ + InputDescriptors: []*InputDescriptor{ + { + Id: "1", + Constraints: &Constraints{ + Fields: []Field{ + { + Path: []string{"$.id"}, + Filter: &Filter{ + Type: "string", + Const: &constant1, + }, + }, + }, + }, + }, + { + Id: "2", + Constraints: &Constraints{ + Fields: []Field{ + { + Path: []string{"$.id"}, + Filter: &Filter{ + Type: "string", + Const: &constant2, + }, + }, + }, + }, + }, + }, + } + submission := PresentationSubmission{ + DescriptorMap: []InputDescriptorMappingObject{ + { + Id: "1", + Path: "$.verifiableCredential[0]", + }, + { + Id: "2", + Path: "$.verifiableCredential[1]", + }, + }, + } + + credentials, err := submission.Validate([]vc.VerifiablePresentation{vp, secondVP}, definition) assert.NoError(t, err, "credential submission is invalid, credentials does not match the presentation definition") assert.Len(t, credentials, 1) @@ -151,7 +218,7 @@ func TestPresentationSubmission_Validate(t *testing.T) { }, } - credentials, err := PresentationSubmission{}.Validate(vp, definition) + credentials, err := PresentationSubmission{}.Validate([]vc.VerifiablePresentation{vp}, definition) assert.EqualError(t, err, "credential submission is invalid, credentials do not match the presentation definition") assert.Empty(t, credentials) @@ -210,7 +277,7 @@ func TestPresentationSubmission_Validate(t *testing.T) { }, } - credentials, err := submission.Validate(vp, definition) + credentials, err := submission.Validate([]vc.VerifiablePresentation{vp}, definition) assert.EqualError(t, err, "credential submission is invalid, input descriptor mapping looks invalid") assert.Empty(t, credentials)