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

Commit

Permalink
feat: data-integrity support for ecdsa-2019 suite (#3612)
Browse files Browse the repository at this point in the history
Signed-off-by: Filip Burlacu <Filip.Burlacu@gendigital.com>
  • Loading branch information
Moopli authored Aug 2, 2023
1 parent 020b60b commit cfa2433
Show file tree
Hide file tree
Showing 15 changed files with 1,704 additions and 235 deletions.
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 @@ import (

"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{}
}

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 @@ func (s *Signer) AddProof(doc []byte, opts *models.ProofOptions) ([]byte, error)
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 @@ func (s *Signer) AddProof(doc []byte, opts *models.ProofOptions) ([]byte, error)

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

0 comments on commit cfa2433

Please sign in to comment.