From 6dd3aa90f378f2d03f5f98eabb1bc68dad95765a Mon Sep 17 00:00:00 2001 From: Volodymyr Kubiv Date: Tue, 25 Oct 2022 09:25:50 +0300 Subject: [PATCH] feat: suppor path_nested in presex. Signed-off-by: Volodymyr Kubiv --- pkg/doc/presexch/api.go | 44 ++++++++--- pkg/doc/presexch/api_test.go | 147 ++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 11 deletions(-) diff --git a/pkg/doc/presexch/api.go b/pkg/doc/presexch/api.go index cba427058c..b5b646c268 100644 --- a/pkg/doc/presexch/api.go +++ b/pkg/doc/presexch/api.go @@ -108,7 +108,6 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g return nil, fmt.Errorf("failed to parse descriptor map: %w", err) } - builder := gval.Full(jsonpath.PlaceholderExtension()) result := make(map[string]*verifiable.Credential) for i := range descriptorMap { @@ -121,12 +120,9 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g descriptorMapProperty, mapping.ID) } - // TODO need to revisit this logic - mapping = pd.getPathNestedIfExists(mapping) - - vc, selectErr := selectByPath(builder, typelessVP, mapping.Path, opts) + vc, selectErr := selectVC(typelessVP, mapping, opts) if selectErr != nil { - return nil, fmt.Errorf("failed to select vc from submission: %w", selectErr) + return nil, selectErr } inputDescriptor := pd.inputDescriptor(mapping.ID) @@ -151,12 +147,40 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g return result, nil } -func (pd *PresentationDefinition) getPathNestedIfExists(mapping *InputDescriptorMapping) *InputDescriptorMapping { - if mapping.PathNested != nil { - return pd.getPathNestedIfExists(mapping.PathNested) +func selectVC(typelessVerifiable interface{}, + mapping *InputDescriptorMapping, opts *MatchOptions) (*verifiable.Credential, error) { + builder := gval.Full(jsonpath.PlaceholderExtension()) + + var vc *verifiable.Credential + + var err error + + for { + vc, err = selectByPath(builder, typelessVerifiable, mapping.Path, opts) + if err != nil { + return nil, fmt.Errorf("failed to select vc from submission: %w", err) + } + + if mapping.PathNested == nil { + break + } + + mapping = mapping.PathNested + + var vcBytes []byte + + vcBytes, err = vc.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("failed to marshal vc: %w", err) + } + + err = json.Unmarshal(vcBytes, &typelessVerifiable) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal vc: %w", err) + } } - return mapping + return vc, nil } // Ensures the matched credentials meet the submission requirements. diff --git a/pkg/doc/presexch/api_test.go b/pkg/doc/presexch/api_test.go index ec969aa746..51f580e565 100644 --- a/pkg/doc/presexch/api_test.go +++ b/pkg/doc/presexch/api_test.go @@ -35,7 +35,7 @@ func TestPresentationDefinition_Match(t *testing.T) { t.Run("match one credential", func(t *testing.T) { uri := randomURI() - customType := "CustomType" + customType := "CustomType" //nolint: goconst expected := newVC([]string{uri}) expected.Types = append(expected.Types, customType) @@ -65,6 +65,94 @@ func TestPresentationDefinition_Match(t *testing.T) { require.Equal(t, expected.ID, result.ID) }) + t.Run("match one nested credential", func(t *testing.T) { + uri := randomURI() + + customType := "CustomType" + + expectedNested := newVC([]string{uri}) + expectedNested.Types = append(expectedNested.Types, customType) + expectedNested.ID = "http://test.credential.com/123456" + + expected := newVCWithCustomFld([]string{uri}, "nestedVC", expectedNested) + expected.Types = append(expected.Types, customType) + + defs := &PresentationDefinition{ + InputDescriptors: []*InputDescriptor{{ + ID: uuid.New().String(), + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", uri, customType), + }}, + }}, + } + + docLoader := createTestDocumentLoader(t, uri, customType) + + matched, err := defs.Match(newVP(t, + &PresentationSubmission{DescriptorMap: []*InputDescriptorMapping{{ + ID: defs.InputDescriptors[0].ID, + Path: "$.verifiableCredential[0]", + PathNested: &InputDescriptorMapping{ + ID: defs.InputDescriptors[0].ID, + Path: "$.nestedVC", + }, + }}}, + expected, + ), docLoader, WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(docLoader))) + require.NoError(t, err) + require.Len(t, matched, 1) + result, ok := matched[defs.InputDescriptors[0].ID] + require.True(t, ok) + require.Equal(t, expectedNested.ID, result.ID) + }) + + t.Run("match one nested jwt credential", func(t *testing.T) { + uri := randomURI() + contextLoader := createTestDocumentLoader(t, uri) + agent := newAgent(t) + + customType := "CustomType" + + expectedNested := newSignedJWTVC(t, agent, []string{uri}) + + expected := newVCWithCustomFld([]string{uri}, "nestedVC", expectedNested) + expected.Types = append(expected.Types, customType) + expected.ID = "http://test.credential.com/123456" + + defs := &PresentationDefinition{ + InputDescriptors: []*InputDescriptor{{ + ID: uuid.New().String(), + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + }}, + } + + docLoader := createTestDocumentLoader(t, uri, customType) + + matched, err := defs.Match(newVP(t, + &PresentationSubmission{DescriptorMap: []*InputDescriptorMapping{{ + ID: defs.InputDescriptors[0].ID, + Path: "$.verifiableCredential[0]", + PathNested: &InputDescriptorMapping{ + ID: defs.InputDescriptors[0].ID, + Path: "$.nestedVC", + }, + }}}, + expected, + ), docLoader, + WithCredentialOptions( + verifiable.WithJSONLDDocumentLoader(contextLoader), + verifiable.WithPublicKeyFetcher(verifiable.NewVDRKeyResolver(agent.VDRegistry()).PublicKeyFetcher()), + ), + ) + require.NoError(t, err) + require.Len(t, matched, 1) + result, ok := matched[defs.InputDescriptors[0].ID] + require.True(t, ok) + require.Equal(t, expectedNested.ID, result.ID) + }) + t.Run("match one signed credential", func(t *testing.T) { uri := randomURI() contextLoader := createTestDocumentLoader(t, uri) @@ -394,6 +482,30 @@ func newVC(ctx []string) *verifiable.Credential { return vc } +func newVCWithCustomFld(ctx []string, fldName string, fld interface{}) *verifiable.Credential { + vc := &verifiable.Credential{ + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: "http://test.credential.com/123", + Issuer: verifiable.Issuer{ID: "http://test.issuer.com"}, + Issued: &util.TimeWrapper{ + Time: time.Now(), + }, + Subject: map[string]interface{}{ + "id": uuid.New().String(), + }, + CustomFields: map[string]interface{}{ + fldName: fld, + }, + } + + if ctx != nil { + vc.Context = append(vc.Context, ctx...) + } + + return vc +} + func newSignedVC(t *testing.T, agent *context.Provider, ctx []string, ctxLoader jsonld.DocumentLoader) *verifiable.Credential { t.Helper() @@ -428,6 +540,39 @@ func newSignedVC(t *testing.T, return vc } +func newSignedJWTVC(t *testing.T, + agent *context.Provider, ctx []string) *verifiable.Credential { + t.Helper() + + vc := newVC(ctx) + + keyID, kh, err := agent.KMS().Create(kms.ED25519Type) + require.NoError(t, err) + + signer := suite.NewCryptoSigner(agent.Crypto(), kh) + + pubKey, kt, err := agent.KMS().ExportPubKeyBytes(keyID) + require.NoError(t, err) + require.Equal(t, kms.ED25519Type, kt) + + issuer, verMethod := fingerprint.CreateDIDKeyByCode(fingerprint.ED25519PubKeyMultiCodec, pubKey) + + vc.Issuer = verifiable.Issuer{ID: issuer} + + claims, err := vc.JWTClaims(false) + require.NoError(t, err) + + jwsAlgo, err := verifiable.KeyTypeToJWSAlgo(kms.ED25519Type) + require.NoError(t, err) + + jws, err := claims.MarshalJWS(jwsAlgo, signer, verMethod) + require.NoError(t, err) + + vc.JWT = jws + + return vc +} + func newVP(t *testing.T, submission *PresentationSubmission, vcs ...*verifiable.Credential) *verifiable.Presentation { vp, err := verifiable.NewPresentation(verifiable.WithCredentials(vcs...)) require.NoError(t, err)