Skip to content

Commit

Permalink
PEX: Validate presentation submission
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Nov 10, 2023
1 parent ec09115 commit 90f63c1
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 1 deletion.
2 changes: 1 addition & 1 deletion vcr/pe/presentation_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (presentationDefinition PresentationDefinition) matchSubmissionRequirements
}
for _, group := range presentationDefinition.groups() {
if _, ok := availableGroups[group.Name]; !ok {
return nil, nil, fmt.Errorf("group %s is required but not available", group.Name)
return nil, nil, fmt.Errorf("group '%s' is required but not available", group.Name)
}
}

Expand Down
45 changes: 45 additions & 0 deletions vcr/pe/presentation_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ 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/log"
v2 "github.com/nuts-foundation/nuts-node/vcr/pe/schema/v2"
"reflect"
)

// ParsePresentationSubmission validates the given JSON and parses it into a PresentationSubmission.
Expand Down Expand Up @@ -137,3 +140,45 @@ func (b *PresentationSubmissionBuilder) Build(format string) (PresentationSubmis

return presentationSubmission, signInstructions, nil
}

// 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)
}
if len(expectedCredentials) == 0 {
return nil, errors.New("credential submission is invalid, credentials does not match the presentation definition")
}
// 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 {
return nil, err
}
var actual interface{}
if err := remarshal(s.DescriptorMap, &actual); err != nil {
return nil, err
}
if !reflect.DeepEqual(expected, actual) {
expectedJSON, _ := json.Marshal(expected)
actualJSON, _ := json.Marshal(actual)
log.Logger().Infof("Input descriptor mapping seems incorrect.\n Got: %s\n Expected: %s", string(actualJSON), string(expectedJSON))
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]
}
return credentialMap, nil
}

func remarshal(v interface{}, target any) error {
result, err := json.Marshal(v)
if err != nil {
return err
}
return json.Unmarshal(result, target)
}
138 changes: 138 additions & 0 deletions vcr/pe/presentation_submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,141 @@ func TestPresentationSubmissionBuilder_Build(t *testing.T) {
assert.Equal(t, "$.verifiableCredential[0]", submission.DescriptorMap[1].PathNested[0].Path)
})
}

func TestPresentationSubmission_Validate(t *testing.T) {
vcID := ssi.MustParseURI("first-vc")
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{
{ID: &vcID},
},
}

t.Run("ok", func(t *testing.T) {
constant := vcID.String()
definition := PresentationDefinition{
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1",
Path: "$.verifiableCredential[0]",
},
},
}

credentials, err := submission.Validate(vp, definition)

assert.NoError(t, err, "credential submission is invalid, credentials does not match the presentation definition")
assert.Len(t, credentials, 1)
assert.Equal(t, vcID.String(), credentials["1"].ID.String())
})
t.Run("credentials don't match input descriptors", func(t *testing.T) {
constant := "incorrect ID"
definition := PresentationDefinition{
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1",
Path: "$.verifiableCredential[0].id",
},
},
}

credentials, err := submission.Validate(vp, definition)

assert.EqualError(t, err, "credential submission is invalid, credentials does not match the presentation definition")
assert.Empty(t, credentials)
})
t.Run("credentials match wrong input descriptors", func(t *testing.T) {
incorrectID := "incorrect ID"
correctID := vcID.String()
count := 1
definition := PresentationDefinition{
SubmissionRequirements: []*SubmissionRequirement{
{
Count: &count,
From: "any",
Rule: "pick",
},
},
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Group: []string{"any"},
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &incorrectID,
},
},
},
},
},
{
Id: "2",
Group: []string{"any"},
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &correctID,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1", // actually maps to input descriptor 2, so should cause an error
Path: "$.verifiableCredential[0]",
},
},
}

credentials, err := submission.Validate(vp, definition)

assert.EqualError(t, err, "credential submission is invalid, input descriptor mapping looks invalid")
assert.Empty(t, credentials)
})
}

0 comments on commit 90f63c1

Please sign in to comment.