Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

feat: data-integrity support for ecdsa-2019 suite #3612

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions component/models/dataintegrity/dataintegrity.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,31 @@ SPDX-License-Identifier: Apache-2.0

package dataintegrity

import "errors"
import (
"errors"

"github.com/hyperledger/aries-framework-go/component/models/did"
spivdr "github.com/hyperledger/aries-framework-go/spi/vdr"
)

var (
// ErrUnsupportedSuite is returned when a Signer or Verifier is required to use a cryptographic suite for which it
// doesn't have a suite.Signer or suite.Verifier (respectively) initialized.
// ErrUnsupportedSuite is returned when a Signer or Verifier is required to use
// a cryptographic suite for which it doesn't have a suite.Signer or
// suite.Verifier (respectively) initialized.
ErrUnsupportedSuite = errors.New("data integrity proof requires unsupported cryptographic suite")
// ErrNoResolver is returned when a Signer or Verifier needs to resolve a
// verification method but has no DID resolver.
ErrNoResolver = errors.New("either did resolver or both verification method and verification relationship must be provided") //nolint:lll
// ErrVMResolution is returned when a Signer or Verifier needs to resolve a
// verification method but this fails.
ErrVMResolution = errors.New("failed to resolve verification method")
)

type didResolver interface {
Resolve(did string, opts ...spivdr.DIDMethodOption) (*did.DocResolution, error)
}

// Options contains initialization parameters for Data Integrity Signer and Verifier.
type Options struct {
DIDResolver didResolver
}
197 changes: 197 additions & 0 deletions component/models/dataintegrity/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package dataintegrity

import (
_ "embed"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/component/kmscrypto/crypto/tinkcrypto"
"github.com/hyperledger/aries-framework-go/component/kmscrypto/doc/util/jwkkid"
"github.com/hyperledger/aries-framework-go/component/kmscrypto/kms/localkms"
mockkms "github.com/hyperledger/aries-framework-go/component/kmscrypto/mock/kms"
"github.com/hyperledger/aries-framework-go/component/kmscrypto/secretlock/noop"
"github.com/hyperledger/aries-framework-go/component/models/dataintegrity/models"
"github.com/hyperledger/aries-framework-go/component/models/dataintegrity/suite/ecdsa2019"
"github.com/hyperledger/aries-framework-go/component/models/did"
"github.com/hyperledger/aries-framework-go/component/models/ld/documentloader"
mockldstore "github.com/hyperledger/aries-framework-go/component/models/ld/mock"
"github.com/hyperledger/aries-framework-go/component/models/ld/store"
mockstorage "github.com/hyperledger/aries-framework-go/component/storageutil/mock/storage"
kmsapi "github.com/hyperledger/aries-framework-go/spi/kms"
)

var (
//go:embed suite/ecdsa2019/testdata/valid_credential.jsonld
validCredential []byte
)

const (
mockVMID2 = "#key-2"
mockKID2 = mockDID + mockVMID2
)

func TestIntegration(t *testing.T) {
signerOpts := suiteOptions(t)

signerInit := ecdsa2019.NewSigner(signerOpts)

verifierInit := ecdsa2019.NewVerifier(suiteOptions(t))

_, p256Bytes, err := signerOpts.KMS.CreateAndExportPubKeyBytes(kmsapi.ECDSAP256IEEEP1363)
require.NoError(t, err)

p256JWK, err := jwkkid.BuildJWK(p256Bytes, kmsapi.ECDSAP256IEEEP1363)
require.NoError(t, err)

_, p384Bytes, err := signerOpts.KMS.CreateAndExportPubKeyBytes(kmsapi.ECDSAP384IEEEP1363)
require.NoError(t, err)

p384JWK, err := jwkkid.BuildJWK(p384Bytes, kmsapi.ECDSAP384IEEEP1363)
require.NoError(t, err)

p256VM, err := did.NewVerificationMethodFromJWK(mockVMID, "JsonWebKey2020", mockDID, p256JWK)
require.NoError(t, err)

p384VM, err := did.NewVerificationMethodFromJWK(mockVMID2, "JsonWebKey2020", mockDID, p384JWK)
require.NoError(t, err)

resolver := resolveFunc(func(id string) (*did.DocResolution, error) {
switch id {
case mockKID:
return makeMockDIDResolution(id, p256VM, did.AssertionMethod), nil
case mockKID2:
return makeMockDIDResolution(id, p384VM, did.AssertionMethod), nil
}

return nil, ErrVMResolution
})

signer, err := NewSigner(&Options{DIDResolver: resolver}, signerInit)
require.NoError(t, err)

verifier, err := NewVerifier(&Options{DIDResolver: resolver}, verifierInit)
require.NoError(t, err)

t.Run("success", func(t *testing.T) {
t.Run("P-256 key", func(t *testing.T) {
proofOpts := &models.ProofOptions{
VerificationMethod: p256VM,
VerificationMethodID: p256VM.ID,
SuiteType: ecdsa2019.SuiteType,
Purpose: "assertionMethod",
VerificationRelationship: "assertionMethod",
ProofType: models.DataIntegrityProof,
Created: time.Now(),
MaxAge: 100,
}

signedCred, err := signer.AddProof(validCredential, proofOpts)
require.NoError(t, err)

err = verifier.VerifyProof(signedCred, proofOpts)
require.NoError(t, err)
})

t.Run("P-384 key", func(t *testing.T) {
proofOpts := &models.ProofOptions{
VerificationMethod: p384VM,
VerificationMethodID: p384VM.ID,
SuiteType: ecdsa2019.SuiteType,
Purpose: "assertionMethod",
VerificationRelationship: "assertionMethod",
ProofType: models.DataIntegrityProof,
Created: time.Now(),
MaxAge: 100,
}

signedCred, err := signer.AddProof(validCredential, proofOpts)
require.NoError(t, err)

err = verifier.VerifyProof(signedCred, proofOpts)
require.NoError(t, err)
})
})

t.Run("failure", func(t *testing.T) {
t.Run("wrong key", func(t *testing.T) {
signOpts := &models.ProofOptions{
VerificationMethod: p256VM,
VerificationMethodID: p256VM.ID,
SuiteType: ecdsa2019.SuiteType,
Purpose: "assertionMethod",
VerificationRelationship: "assertionMethod",
ProofType: models.DataIntegrityProof,
Created: time.Now(),
}

verifyOpts := &models.ProofOptions{
VerificationMethod: p384VM,
VerificationMethodID: p384VM.ID,
SuiteType: ecdsa2019.SuiteType,
Purpose: "assertionMethod",
VerificationRelationship: "assertionMethod",
ProofType: models.DataIntegrityProof,
MaxAge: 100,
}

signedCred, err := signer.AddProof(validCredential, signOpts)
require.NoError(t, err)

err = verifier.VerifyProof(signedCred, verifyOpts)
require.Error(t, err)
require.Contains(t, err.Error(), "failed to verify ecdsa-2019 DI proof")
})
})
}

func suiteOptions(t *testing.T) *ecdsa2019.Options {
t.Helper()

docLoader, err := documentloader.NewDocumentLoader(createMockProvider())
require.NoError(t, err)

storeProv := mockstorage.NewMockStoreProvider()

kmsProv, err := mockkms.NewProviderForKMS(storeProv, &noop.NoLock{})
require.NoError(t, err)

kms, err := localkms.New("local-lock://custom/master/key/", kmsProv)
require.NoError(t, err)

cr, err := tinkcrypto.New()
require.NoError(t, err)

return &ecdsa2019.Options{
LDDocumentLoader: docLoader,
Crypto: cr,
KMS: kms,
}
}

type provider struct {
ContextStore store.ContextStore
RemoteProviderStore store.RemoteProviderStore
}

func (p *provider) JSONLDContextStore() store.ContextStore {
return p.ContextStore
}

func (p *provider) JSONLDRemoteProviderStore() store.RemoteProviderStore {
return p.RemoteProviderStore
}

func createMockProvider() *provider {
return &provider{
ContextStore: mockldstore.NewMockContextStore(),
RemoteProviderStore: mockldstore.NewMockRemoteProviderStore(),
}
}
39 changes: 23 additions & 16 deletions component/models/dataintegrity/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ SPDX-License-Identifier: Apache-2.0

package models

import "time"
import (
"time"

// TODO integrate VerificationMethod model with the did doc VM model
// (can we just use did.VerificationMethod directly)?
"github.com/hyperledger/aries-framework-go/component/models/did"
)

const (
// DataIntegrityProof is the type property on proofs created using data
// integrity cryptographic suites.
DataIntegrityProof = "DataIntegrityProof"
)

// VerificationMethod implements the data integrity verification method model:
// https://www.w3.org/TR/vc-data-integrity/#verification-methods
type VerificationMethod struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
Fields map[string]interface{}
}
type VerificationMethod = did.VerificationMethod

// Proof implements the data integrity proof model:
// https://www.w3.org/TR/vc-data-integrity/#proofs
type Proof struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
CryptoSuite string `json:"cryptosuite,omitempty"`
ProofPurpose string `json:"proofPurpose"`
VerificationMethod string `json:"verificationMethod"`
Created string `json:"created,omitempty"`
Expand All @@ -36,13 +39,17 @@ type Proof struct {

// ProofOptions provides options for signing or verifying a data integrity proof.
type ProofOptions struct {
Purpose string
VerificationMethod *VerificationMethod
SuiteType string
Domain string
Challenge string
MaxAge int64
CustomFields map[string]interface{}
Purpose string
VerificationMethodID string
VerificationMethod *VerificationMethod
VerificationRelationship string
ProofType string
SuiteType string
Domain string
Challenge string
Created time.Time
MaxAge int64
CustomFields map[string]interface{}
}

// DateTimeFormat is the date-time format used by the data integrity
Expand Down
39 changes: 36 additions & 3 deletions component/models/dataintegrity/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@

"github.com/tidwall/sjson"

"github.com/hyperledger/aries-framework-go/component/models/jwt/didsignjwt"

"github.com/hyperledger/aries-framework-go/component/models/dataintegrity/models"
"github.com/hyperledger/aries-framework-go/component/models/dataintegrity/suite"
)

// Signer implements the Add Proof algorithm of the verifiable credential data
// integrity specification, using a set of provided cryptographic suites.
type Signer struct {
suites map[string]suite.Signer
suites map[string]suite.Signer
resolver didResolver
}

// NewSigner initializes a Signer that supports using the provided cryptographic
// suites to perform data integrity signing.
func NewSigner(suites ...suite.SignerInitializer) (*Signer, error) {
func NewSigner(opts *Options, suites ...suite.SignerInitializer) (*Signer, error) {
if opts == nil {
opts = &Options{}
}

Check warning on line 33 in component/models/dataintegrity/signer.go

View check run for this annotation

Codecov / codecov/patch

component/models/dataintegrity/signer.go#L32-L33

Added lines #L32 - L33 were not covered by tests

signer := &Signer{
suites: map[string]suite.Signer{},
suites: map[string]suite.Signer{},
resolver: opts.DIDResolver,
}

for _, initializer := range suites {
Expand Down Expand Up @@ -67,6 +75,11 @@
return nil, ErrUnsupportedSuite
}

err := resolveVM(opts, s.resolver)
if err != nil {
return nil, err
}

proof, err := signerSuite.CreateProof(doc, opts)
if err != nil {
return nil, ErrProofGeneration
Expand Down Expand Up @@ -100,3 +113,23 @@

return out, nil
}

func resolveVM(opts *models.ProofOptions, resolver didResolver) error {
if opts.VerificationMethod == nil || opts.VerificationRelationship == "" {
if resolver == nil {
return ErrNoResolver
}

vm, vmID, rel, err := didsignjwt.ResolveSigningVMWithRelationship(opts.VerificationMethodID, resolver)
if err != nil {
// TODO update linter to use go 1.20: https://github.com/hyperledger/aries-framework-go/issues/3613
return errors.Join(ErrVMResolution, err) // nolint:typecheck
}

opts.VerificationMethodID = vmID
opts.VerificationMethod = vm
opts.VerificationRelationship = rel
}

return nil
}
Loading
Loading