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 3, 2023
1 parent 5306cf3 commit e8f2a55
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 0 deletions.
73 changes: 73 additions & 0 deletions vcr/pe/submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ package pe

import (
"encoding/json"
"errors"
"fmt"
"github.com/PaesslerAG/jsonpath"
"github.com/nuts-foundation/go-did/vc"
v2 "github.com/nuts-foundation/nuts-node/vcr/pe/schema/v2"
)

Expand All @@ -37,3 +41,72 @@ func ParsePresentationSubmission(raw []byte) (*PresentationSubmission, error) {
}
return &result, 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.
func (s PresentationSubmission) Validate(presentation vc.VerifiablePresentation, definition PresentationDefinition) (map[string]vc.VerifiableCredential, error) {
expectedSubmission, _, err := definition.Match(presentation.VerifiableCredential)
if err != nil {
return nil, err
}
expectedCredentialMap, err := expectedSubmission.Credentials(presentation)
if err != nil {
return nil, err
}
actualCredentialMap, err := s.Credentials(presentation)
if err != nil {
return nil, err
}
if len(expectedCredentialMap) != len(actualCredentialMap) {
return nil, errors.New("credential submission is invalid (invalid number of input descriptors)")
}
for inputDescriptorId, actualCredential := range actualCredentialMap {
expectedCredential, exists := expectedCredentialMap[inputDescriptorId]
if !exists {
return nil, fmt.Errorf("credential submission is invalid (descriptor '%s' is mapped to unmatching VC)", inputDescriptorId)
}
if expectedCredential.Raw() != actualCredential.Raw() {
return nil, fmt.Errorf("credential submission is invalid (descriptor '%s' is mapped to %s, but maps to %s)", inputDescriptorId, actualCredential.ID, expectedCredential.ID)
}
}
return expectedCredentialMap, nil
}

// Credentials maps the submission to the presentation and returns the credentials.
// If one or more InputDescriptors can't be mapped to a Verifiable Credential, it returns an error.
// The credentials will be returned as map with the InputDescriptor.Id as key.
func (s PresentationSubmission) Credentials(presentation vc.VerifiablePresentation) (map[string]vc.VerifiableCredential, error) {
presentationJSON, err := presentation.MarshalJSON()
if err != nil {
return nil, err
}
var presentationAsMap map[string]interface{}
_ = json.Unmarshal(presentationJSON, &presentationAsMap)
// Handle compacted array of JSON-LD VP: presentation submission indexes VerifiablePresentation.credentialSubject,
// which only works if it's actually an array.
verifiableCredentials := presentationAsMap["verifiableCredential"]
if verifiableCredentials != nil {
_, isSlice := verifiableCredentials.([]interface{})
if !isSlice {
presentationAsMap["verifiableCredential"] = []interface{}{verifiableCredentials}
}
}

var credentials = make(map[string]vc.VerifiableCredential, len(s.DescriptorMap))
for _, inputDescriptor := range s.DescriptorMap {
credentialAsInterf, err := jsonpath.Get(inputDescriptor.Path, presentationAsMap)
if err != nil {
return nil, fmt.Errorf("descriptor '%s' evaluation failed: %w", inputDescriptor.Id, err)
}
if credentialAsInterf == nil || err != nil {
return nil, fmt.Errorf("descriptor '%s' does not evaluate to a VC in the VP", inputDescriptor.Id)
}
credentialAsJSON, _ := json.Marshal(credentialAsInterf) // can only fail when credentialAsInterf is a func(), which is impossible
var credential vc.VerifiableCredential
if err = json.Unmarshal(credentialAsJSON, &credential); err != nil {
return nil, fmt.Errorf("descriptor '%s' evaluates to an invalid VC in the VP: %w", inputDescriptor.Id, err)
}
credentials[inputDescriptor.Id] = credential
}
return credentials, nil
}
173 changes: 173 additions & 0 deletions vcr/pe/submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
package pe

import (
"encoding/json"
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/vc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
Expand All @@ -35,3 +38,173 @@ func TestParsePresentationSubmission(t *testing.T) {
assert.ErrorContains(t, err, `missing properties: "id"`)
})
}

func TestPresentationSubmission_Credentials(t *testing.T) {
credential1ID := ssi.MustParseURI("cred-1")
credential1 := vc.VerifiableCredential{
ID: &credential1ID,
}
credential2ID := ssi.MustParseURI("cred-2")
credential2 := vc.VerifiableCredential{
ID: &credential2ID,
}

t.Run("1 input descriptor", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "$.verifiableCredential[0]",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

require.NoError(t, err)
assert.Equal(t, []vc.VerifiableCredential{credential1}, credentials)
})
t.Run("2 input descriptors", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1, credential2},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "$.verifiableCredential[0]",
"format": "ldp_vc"
},
{
"id": "2",
"path": "$.verifiableCredential[1]",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

require.NoError(t, err)
assert.Equal(t, []vc.VerifiableCredential{credential1, credential2}, credentials)
})
t.Run("input descriptor does not match", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "$.verifiableCredential[1]",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

assert.EqualError(t, err, "descriptor '1' does not evaluate to a VC in the VP")
assert.Nil(t, credentials)
})
t.Run("input descriptors with the same path", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1, credential2},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "$.verifiableCredential[0]",
"format": "ldp_vc"
},
{
"id": "2",
"path": "$.verifiableCredential[0]",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

require.NoError(t, err)
assert.Equal(t, []vc.VerifiableCredential{credential1}, credentials)
})
t.Run("no input descriptors", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1, credential2},
}
submissionJSON := `{"descriptor_map": []}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

assert.NoError(t, err)
assert.Empty(t, credentials)
})
t.Run("invalid input descriptor path (empty)", func(t *testing.T) {
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{credential1, credential2},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

assert.EqualError(t, err, "descriptor '1' evaluation failed: parsing error: \t - 1:1 unexpected EOF while scanning extensions")
assert.Empty(t, credentials)
})
t.Run("input descriptor does not point to a verifiable credential", func(t *testing.T) {
vp := vc.VerifiablePresentation{
Proof: []interface{}{
"test",
},
VerifiableCredential: []vc.VerifiableCredential{credential1, credential2},
}
submissionJSON := `
{
"descriptor_map": [
{
"id": "1",
"path": "$.proof",
"format": "ldp_vc"
}
]
}`
var submission PresentationSubmission
_ = json.Unmarshal([]byte(submissionJSON), &submission)

credentials, err := submission.Credentials(vp)

assert.EqualError(t, err, "descriptor '1' evaluates to an invalid VC in the VP: json: cannot unmarshal string into Go value of type map[string]interface {}")
assert.Empty(t, credentials)
})
}

0 comments on commit e8f2a55

Please sign in to comment.