From 3d0b87b8eed653c16f8cc144e5f3a473366a4ac7 Mon Sep 17 00:00:00 2001 From: Stas Dmytryshyn Date: Fri, 20 Sep 2024 11:46:09 +0200 Subject: [PATCH] feat: add support for eddsa-rdfc-2022 & ecdsa-rdfc-2019 (#62) * feat: add support for eddsa-2022 * feat: add support for ecdsa-rdfc-2019 * fix: lint --- dataintegrity/suite/ecdsa2019/ecdsa2019.go | 17 +- .../suite/ecdsa2019/integration_test.go | 21 ++ dataintegrity/suite/eddsa2022/eddsa2022.go | 316 ++++++++++++++++++ .../suite/eddsa2022/eddsa2022_test.go | 300 +++++++++++++++++ .../suite/eddsa2022/integration_test.go | 127 +++++++ .../eddsa2022/testdata/invalid_jsonld.jsonld | 15 + .../testdata/valid_credential.jsonld | 19 ++ 7 files changed, 812 insertions(+), 3 deletions(-) create mode 100644 dataintegrity/suite/eddsa2022/eddsa2022.go create mode 100644 dataintegrity/suite/eddsa2022/eddsa2022_test.go create mode 100644 dataintegrity/suite/eddsa2022/integration_test.go create mode 100644 dataintegrity/suite/eddsa2022/testdata/invalid_jsonld.jsonld create mode 100644 dataintegrity/suite/eddsa2022/testdata/valid_credential.jsonld diff --git a/dataintegrity/suite/ecdsa2019/ecdsa2019.go b/dataintegrity/suite/ecdsa2019/ecdsa2019.go index 36b1fa6..dba4f23 100644 --- a/dataintegrity/suite/ecdsa2019/ecdsa2019.go +++ b/dataintegrity/suite/ecdsa2019/ecdsa2019.go @@ -32,6 +32,9 @@ const ( // implementing ecdsa signatures with RDF canonicalization as per this // spec:https://www.w3.org/TR/vc-di-ecdsa/#ecdsa-2019 SuiteType = "ecdsa-2019" + + // SuiteTypeNew "ecdsa-rdfc-2019" is the data integrity Type identifier for the suite + SuiteTypeNew = "ecdsa-rdfc-2019" ) // SignerGetter returns a Signer, which must sign with the private key matching @@ -182,6 +185,10 @@ const ( // CreateProof implements the ecdsa-2019 cryptographic suite for Add Proof: // https://www.w3.org/TR/vc-di-ecdsa/#add-proof-ecdsa-2019 func (s *Suite) CreateProof(doc []byte, opts *models.ProofOptions) (*models.Proof, error) { + if opts.SuiteType == "" { + opts.SuiteType = SuiteType + } + docHash, vmKey, _, err := s.transformAndHash(doc, opts) if err != nil { return nil, err @@ -199,7 +206,7 @@ func (s *Suite) CreateProof(doc []byte, opts *models.ProofOptions) (*models.Proo p := &models.Proof{ Type: models.DataIntegrityProof, - CryptoSuite: SuiteType, + CryptoSuite: opts.SuiteType, ProofPurpose: opts.Purpose, Domain: opts.Domain, Challenge: opts.Challenge, @@ -212,6 +219,10 @@ func (s *Suite) CreateProof(doc []byte, opts *models.ProofOptions) (*models.Proo } func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte, *pubkey.PublicKey, Verifier, error) { + if opts.SuiteType == "" { + opts.SuiteType = SuiteType + } + docData := make(map[string]interface{}) err := json.Unmarshal(doc, &docData) @@ -245,7 +256,7 @@ func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte, confData := proofConfig(docData[ldCtxKey], opts) - if opts.ProofType != "DataIntegrityProof" || opts.SuiteType != SuiteType { + if opts.ProofType != "DataIntegrityProof" || (opts.SuiteType != SuiteType && opts.SuiteType != SuiteTypeNew) { return nil, nil, nil, suite.ErrProofTransformation } @@ -315,7 +326,7 @@ func proofConfig(docCtx interface{}, opts *models.ProofOptions) map[string]inter return map[string]interface{}{ ldCtxKey: docCtx, "type": models.DataIntegrityProof, - "cryptosuite": SuiteType, + "cryptosuite": opts.SuiteType, "verificationMethod": opts.VerificationMethodID, "created": opts.Created.Format(models.DateTimeFormat), "proofPurpose": opts.Purpose, diff --git a/dataintegrity/suite/ecdsa2019/integration_test.go b/dataintegrity/suite/ecdsa2019/integration_test.go index 2464525..e48344e 100644 --- a/dataintegrity/suite/ecdsa2019/integration_test.go +++ b/dataintegrity/suite/ecdsa2019/integration_test.go @@ -14,6 +14,7 @@ import ( "github.com/trustbloc/did-go/doc/did" "github.com/trustbloc/did-go/doc/ld/documentloader" kmsapi "github.com/trustbloc/kms-go/spi/kms" + "github.com/trustbloc/vc-go/internal/testutil/kmscryptoutil" "github.com/trustbloc/vc-go/dataintegrity/models" @@ -71,6 +72,26 @@ func TestIntegration(t *testing.T) { require.NoError(t, err) }) + t.Run("P-256 key with new Suite", func(t *testing.T) { + proofOpts := &models.ProofOptions{ + VerificationMethod: p256VM, + VerificationMethodID: p256VM.ID, + SuiteType: SuiteTypeNew, + Purpose: "assertionMethod", + ProofType: models.DataIntegrityProof, + Created: time.Now(), + MaxAge: 100, + } + + proof, err := signer.CreateProof(validCredential, proofOpts) + require.NoError(t, err) + + err = verifier.VerifyProof(validCredential, proof, proofOpts) + require.NoError(t, err) + + require.EqualValues(t, SuiteTypeNew, proof.CryptoSuite) + }) + t.Run("P-384 key", func(t *testing.T) { proofOpts := &models.ProofOptions{ VerificationMethod: p384VM, diff --git a/dataintegrity/suite/eddsa2022/eddsa2022.go b/dataintegrity/suite/eddsa2022/eddsa2022.go new file mode 100644 index 0000000..2df0e38 --- /dev/null +++ b/dataintegrity/suite/eddsa2022/eddsa2022.go @@ -0,0 +1,316 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package eddsa2022 + +import ( + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "hash" + + "github.com/multiformats/go-multibase" + "github.com/piprate/json-gold/ld" + "github.com/trustbloc/did-go/doc/ld/processor" + "github.com/trustbloc/kms-go/doc/jose/jwk" + "github.com/trustbloc/kms-go/spi/kms" + wrapperapi "github.com/trustbloc/kms-go/wrapper/api" + + "github.com/trustbloc/vc-go/crypto-ext/pubkey" + "github.com/trustbloc/vc-go/crypto-ext/verifiers/ed25519" + "github.com/trustbloc/vc-go/dataintegrity/models" + "github.com/trustbloc/vc-go/dataintegrity/suite" +) + +const ( + // SuiteType "eddsa-rdfc-2022" is the data integrity Type identifier for the suite + // implementing eddsa signatures with RDF canonicalization as per this + // spec:https://w3c.github.io/vc-di-eddsa/#verify-proof-eddsa-rdfc-2022 + SuiteType = "eddsa-rdfc-2022" +) + +// SignerGetter returns a Signer, which must sign with the private key matching +// the public key provided in models.ProofOptions.VerificationMethod. +type SignerGetter func(pub *jwk.JWK) (Signer, error) + +// WithStaticSigner sets the Suite to use a fixed Signer, with externally-chosen signing key. +// +// Use when a signing Suite is initialized for a single signature, then thrown away. +func WithStaticSigner(signer Signer) SignerGetter { + return func(*jwk.JWK) (Signer, error) { + return signer, nil + } +} + +// WithKMSCryptoWrapper provides a SignerGetter using the kmscrypto wrapper. +// +// This SignerGetter assumes that the public key JWKs provided were received +// from the same kmscrypto.KMSCrypto implementation. +func WithKMSCryptoWrapper(kmsCrypto wrapperapi.KMSCryptoSigner) SignerGetter { + return func(pub *jwk.JWK) (Signer, error) { + return kmsCrypto.FixedKeySigner(pub) + } +} + +// A KMSSigner is able to sign messages. +type KMSSigner interface { // TODO note: only used by deprecated function + // Sign will sign msg using a matching signature primitive in kh key handle of a private key + // returns: + // signature in []byte + // error in case of errors + Sign(msg []byte, kh interface{}) ([]byte, error) +} + +// A Signer is able to sign messages. +type Signer interface { + // Sign will sign msg using a private key internal to the Signer. + // returns: + // signature in []byte + // error in case of errors + Sign(msg []byte) ([]byte, error) +} + +// A Verifier is able to verify messages. +type Verifier interface { + // Verify will verify a signature for the given msg using a matching signature primitive in kh key handle of + // a public key + // returns: + // error in case of errors or nil if signature verification was successful + Verify(signature, msg []byte, pubKey *pubkey.PublicKey) error +} + +// Suite implements the eddsa-2022 data integrity cryptographic suite. +type Suite struct { + ldLoader ld.DocumentLoader + signerGetter SignerGetter + eD25519Verifier Verifier +} + +// Options provides initialization options for Suite. +type Options struct { + LDDocumentLoader ld.DocumentLoader + ED25519Verifier Verifier + SignerGetter SignerGetter +} + +// SuiteInitializer is the initializer for Suite. +type SuiteInitializer func() (suite.Suite, error) + +// New constructs an initializer for Suite. +func New(options *Options) SuiteInitializer { + return func() (suite.Suite, error) { + return &Suite{ + ldLoader: options.LDDocumentLoader, + eD25519Verifier: options.ED25519Verifier, + signerGetter: options.SignerGetter, + }, nil + } +} + +type initializer SuiteInitializer + +// Signer private, implements suite.SignerInitializer. +func (i initializer) Signer() (suite.Signer, error) { + return i() +} + +// Verifier private, implements suite.VerifierInitializer. +func (i initializer) Verifier() (suite.Verifier, error) { + return i() +} + +// Type private, implements suite.SignerInitializer and +// suite.VerifierInitializer. +func (i initializer) Type() string { + return SuiteType +} + +// SignerInitializerOptions provides options for a SignerInitializer. +type SignerInitializerOptions struct { + LDDocumentLoader ld.DocumentLoader + SignerGetter SignerGetter +} + +// NewSignerInitializer returns a suite.SignerInitializer that initializes an eddsa-2022 +// signing Suite with the given SignerInitializerOptions. +func NewSignerInitializer(options *SignerInitializerOptions) suite.SignerInitializer { + return initializer(New(&Options{ + LDDocumentLoader: options.LDDocumentLoader, + SignerGetter: options.SignerGetter, + })) +} + +// VerifierInitializerOptions provides options for a VerifierInitializer. +type VerifierInitializerOptions struct { + LDDocumentLoader ld.DocumentLoader // required + Ed25519Verifier Verifier // optional +} + +// NewVerifierInitializer returns a suite.VerifierInitializer that initializes an +// eddsa-2022 verification Suite with the given VerifierInitializerOptions. +func NewVerifierInitializer(options *VerifierInitializerOptions) suite.VerifierInitializer { + ed25519Verifier := options.Ed25519Verifier + + if ed25519Verifier == nil { + ed25519Verifier = ed25519.New() + } + + return initializer(New(&Options{ + LDDocumentLoader: options.LDDocumentLoader, + ED25519Verifier: ed25519Verifier, + })) +} + +const ( + ldCtxKey = "@context" +) + +// CreateProof implements the eddsa-2022 cryptographic suite for Add Proof. +func (s *Suite) CreateProof(doc []byte, opts *models.ProofOptions) (*models.Proof, error) { + docHash, vmKey, _, err := s.transformAndHash(doc, opts) + if err != nil { + return nil, err + } + + sig, err := sign(docHash, vmKey.JWK, s.signerGetter) + if err != nil { + return nil, err + } + + sigStr, err := multibase.Encode(multibase.Base58BTC, sig) + if err != nil { + return nil, err + } + + p := &models.Proof{ + Type: models.DataIntegrityProof, + CryptoSuite: SuiteType, + ProofPurpose: opts.Purpose, + Domain: opts.Domain, + Challenge: opts.Challenge, + VerificationMethod: opts.VerificationMethod.ID, + ProofValue: sigStr, + Created: opts.Created.Format(models.DateTimeFormat), + } + + return p, nil +} + +func (s *Suite) transformAndHash(doc []byte, opts *models.ProofOptions) ([]byte, *pubkey.PublicKey, Verifier, error) { + docData := make(map[string]interface{}) + + err := json.Unmarshal(doc, &docData) + if err != nil { + return nil, nil, nil, fmt.Errorf("eddsa-2022 suite expects JSON-LD payload: %w", err) + } + + vmKey := opts.VerificationMethod.JSONWebKey() + if vmKey == nil { + return nil, nil, nil, errors.New("verification method needs JWK") + } + + var ( + keyType kms.KeyType + h hash.Hash + verifier Verifier + ) + + verifier = s.eD25519Verifier + keyType = kms.ED25519Type + h = sha256.New() + + confData := proofConfig(docData[ldCtxKey], opts) + + if opts.ProofType != "DataIntegrityProof" || opts.SuiteType != SuiteType { + return nil, nil, nil, suite.ErrProofTransformation + } + + canonDoc, err := canonicalize(docData, s.ldLoader) + if err != nil { + return nil, nil, nil, err + } + + canonConf, err := canonicalize(confData, s.ldLoader) + if err != nil { + return nil, nil, nil, err + } + + docHash := hashData(canonDoc, canonConf, h) + + return docHash, &pubkey.PublicKey{Type: keyType, JWK: vmKey}, verifier, nil +} + +// VerifyProof implements the eddsa-2022 cryptographic suite for CheckJWTProof Proof. +func (s *Suite) VerifyProof(doc []byte, proof *models.Proof, opts *models.ProofOptions) error { + message, vmKey, verifier, err := s.transformAndHash(doc, opts) + if err != nil { + return err + } + + _, signature, err := multibase.Decode(proof.ProofValue) + if err != nil { + return fmt.Errorf("decoding proofValue: %w", err) + } + + err = verifier.Verify(signature, message, vmKey) + if err != nil { + return fmt.Errorf("failed to verify eddsa-2022 DI proof: %w", err) + } + + return nil +} + +// RequiresCreated returns false, as the eddsa-2022 cryptographic suite does not +// require the use of the models.Proof.Created field. +func (s *Suite) RequiresCreated() bool { + return false +} + +func canonicalize(data map[string]interface{}, loader ld.DocumentLoader) ([]byte, error) { + out, err := processor.Default().GetCanonicalDocument(data, processor.WithDocumentLoader(loader)) + if err != nil { + return nil, fmt.Errorf("canonicalizing signature base data: %w", err) + } + + return out, nil +} + +func hashData(transformedDoc, confData []byte, h hash.Hash) []byte { + h.Write(transformedDoc) + docHash := h.Sum(nil) + + h.Reset() + h.Write(confData) + result := h.Sum(docHash) + + return result +} + +func proofConfig(docCtx interface{}, opts *models.ProofOptions) map[string]interface{} { + return map[string]interface{}{ + ldCtxKey: docCtx, + "type": models.DataIntegrityProof, + "cryptosuite": SuiteType, + "verificationMethod": opts.VerificationMethodID, + "created": opts.Created.Format(models.DateTimeFormat), + "proofPurpose": opts.Purpose, + } +} + +func sign(sigBase []byte, key *jwk.JWK, signerGetter SignerGetter) ([]byte, error) { + signer, err := signerGetter(key) + if err != nil { + return nil, err + } + + sig, err := signer.Sign(sigBase) + if err != nil { + return nil, err + } + + return sig, nil +} diff --git a/dataintegrity/suite/eddsa2022/eddsa2022_test.go b/dataintegrity/suite/eddsa2022/eddsa2022_test.go new file mode 100644 index 0000000..fb2a76f --- /dev/null +++ b/dataintegrity/suite/eddsa2022/eddsa2022_test.go @@ -0,0 +1,300 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package eddsa2022 + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + _ "embed" + "errors" + "testing" + "time" + + "github.com/multiformats/go-multibase" + "github.com/stretchr/testify/require" + "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/did-go/doc/ld/documentloader" + "github.com/trustbloc/kms-go/doc/jose/jwk" + "github.com/trustbloc/kms-go/doc/jose/jwk/jwksupport" + mockwrapper "github.com/trustbloc/kms-go/mock/wrapper" + + "github.com/trustbloc/vc-go/crypto-ext/pubkey" + "github.com/trustbloc/vc-go/dataintegrity/models" + "github.com/trustbloc/vc-go/dataintegrity/suite" +) + +const ( + fooBar = "foo bar" +) + +func TestNew(t *testing.T) { + docLoader, err := documentloader.NewDocumentLoader(createMockProvider()) + require.NoError(t, err) + + kc := &mockwrapper.MockKMSCrypto{} + + signerGetter := WithKMSCryptoWrapper(kc) + + t.Run("signer success", func(t *testing.T) { + sigInit := NewSignerInitializer(&SignerInitializerOptions{ + LDDocumentLoader: docLoader, + SignerGetter: signerGetter, + }) + + signer, err := sigInit.Signer() + require.NoError(t, err) + require.NotNil(t, signer) + require.False(t, signer.RequiresCreated()) + }) + + t.Run("verifier success", func(t *testing.T) { + verInit := NewVerifierInitializer(&VerifierInitializerOptions{ + LDDocumentLoader: docLoader, + }) + + verifier, err := verInit.Verifier() + require.NoError(t, err) + require.NotNil(t, verifier) + require.False(t, verifier.RequiresCreated()) + }) +} + +type testCase struct { + signer *mockwrapper.MockKMSCrypto + docLoader *documentloader.DocumentLoader + proofOpts *models.ProofOptions + proof *models.Proof + ed25519Verifier Verifier + document []byte + errIs error + errStr string +} + +func successCase(t *testing.T) *testCase { + t.Helper() + + _, mockVM := getVMWithJWK(t) + + docLoader, err := documentloader.NewDocumentLoader(createMockProvider()) + require.NoError(t, err) + + signer := &mockwrapper.MockKMSCrypto{} + + proofCreated := time.Now() + + proofOpts := &models.ProofOptions{ + VerificationMethod: mockVM, + VerificationMethodID: mockVM.ID, + SuiteType: SuiteType, + Purpose: "assertionMethod", + ProofType: models.DataIntegrityProof, + Created: proofCreated, + MaxAge: 100, + } + + mockSig, err := multibase.Encode(multibase.Base58BTC, []byte("mock signature")) + require.NoError(t, err) + + proof := &models.Proof{ + Type: models.DataIntegrityProof, + CryptoSuite: SuiteType, + ProofPurpose: "assertionMethod", + VerificationMethod: mockVM.ID, + Created: proofCreated.Format(models.DateTimeFormat), + ProofValue: mockSig, + } + + return &testCase{ + signer: signer, + docLoader: docLoader, + proofOpts: proofOpts, + proof: proof, + document: validCredential, + errIs: nil, + errStr: "", + } +} + +func testSign(t *testing.T, tc *testCase) { + sigInit := NewSignerInitializer(&SignerInitializerOptions{ + LDDocumentLoader: tc.docLoader, + SignerGetter: WithKMSCryptoWrapper(tc.signer), + }) + + signer, err := sigInit.Signer() + require.NoError(t, err) + + proof, err := signer.CreateProof(tc.document, tc.proofOpts) + + if tc.errStr == "" && tc.errIs == nil { + require.NoError(t, err) + require.NotNil(t, proof) + } else { + require.Error(t, err) + require.Nil(t, proof) + + if tc.errStr != "" { + require.Contains(t, err.Error(), tc.errStr) + } + + if tc.errIs != nil { + require.ErrorIs(t, err, tc.errIs) + } + } +} + +type mockVerifier struct { + err error +} + +func (mv *mockVerifier) Verify(_, _ []byte, _ *pubkey.PublicKey) error { + return mv.err +} + +func testVerify(t *testing.T, tc *testCase) { + verInit := NewVerifierInitializer(&VerifierInitializerOptions{ + LDDocumentLoader: tc.docLoader, + Ed25519Verifier: tc.ed25519Verifier, + }) + + verifier, err := verInit.Verifier() + require.NoError(t, err) + + err = verifier.VerifyProof(tc.document, tc.proof, tc.proofOpts) + + if tc.errStr == "" && tc.errIs == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + + if tc.errStr != "" { + require.Contains(t, err.Error(), tc.errStr) + } + + if tc.errIs != nil { + require.ErrorIs(t, err, tc.errIs) + } + } +} + +func TestSuite_CreateProof(t *testing.T) { + t.Run("success", func(t *testing.T) { + t.Run("ED25519 key", func(t *testing.T) { + tc := successCase(t) + + testSign(t, tc) + }) + }) + + t.Run("failure", func(t *testing.T) { + t.Run("signer sign error", func(t *testing.T) { + tc := successCase(t) + + errExpected := errors.New("expected error") + + tc.signer.SignErr = errExpected + tc.errIs = errExpected + + testSign(t, tc) + }) + }) +} + +func TestSuite_VerifyProof(t *testing.T) { + t.Run("success", func(t *testing.T) { + t.Run("ED25519 key", func(t *testing.T) { + tc := successCase(t) + tc.ed25519Verifier = &mockVerifier{} + + testVerify(t, tc) + }) + }) + + t.Run("failure", func(t *testing.T) { + t.Run("decode proof signature", func(t *testing.T) { + tc := successCase(t) + + tc.proof.ProofValue = "!%^@^@#%&#%#@" + tc.errStr = "decoding proofValue" + + testVerify(t, tc) + }) + + t.Run("crypto verify", func(t *testing.T) { + tc := successCase(t) + + errExpected := errors.New("expected error") + + tc.ed25519Verifier = &mockVerifier{err: errExpected} + tc.errIs = errExpected + + testVerify(t, tc) + }) + }) +} + +func TestSharedFailures(t *testing.T) { + t.Run("unmarshal doc", func(t *testing.T) { + tc := successCase(t) + + tc.document = []byte("not JSON!") + tc.errStr = "expects JSON-LD payload" + + testSign(t, tc) + }) + + t.Run("no jwk in vm", func(t *testing.T) { + tc := successCase(t) + + tc.proofOpts.VerificationMethod = &did.VerificationMethod{ + ID: tc.proofOpts.VerificationMethodID, + Value: []byte(fooBar), + } + tc.errStr = "verification method needs JWK" + + testSign(t, tc) + }) + + t.Run("invalid proof/suite type", func(t *testing.T) { + tc := successCase(t) + + tc.proofOpts.ProofType = fooBar + tc.errIs = suite.ErrProofTransformation + + testSign(t, tc) + + tc.proofOpts.ProofType = models.DataIntegrityProof + tc.proofOpts.SuiteType = fooBar + + testSign(t, tc) + }) + + t.Run("canonicalize doc", func(t *testing.T) { + tc := successCase(t) + + tc.document = invalidJSONLD + tc.errStr = "canonicalizing signature base data" + + testSign(t, tc) + }) +} + +func getVMWithJWK(t *testing.T) (*jwk.JWK, *models.VerificationMethod) { + t.Helper() + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + jwkPriv, err := jwksupport.JWKFromKey(priv) + require.NoError(t, err) + + mockVM, err := did.NewVerificationMethodFromJWK("#key-1", "JsonWebKey2020", "did:foo:bar", jwkPriv) + require.NoError(t, err) + + return jwkPriv, mockVM +} diff --git a/dataintegrity/suite/eddsa2022/integration_test.go b/dataintegrity/suite/eddsa2022/integration_test.go new file mode 100644 index 0000000..645d51f --- /dev/null +++ b/dataintegrity/suite/eddsa2022/integration_test.go @@ -0,0 +1,127 @@ +package eddsa2022 + +import ( + _ "embed" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/trustbloc/did-go/doc/did" + "github.com/trustbloc/did-go/doc/ld/documentloader" + mockldstore "github.com/trustbloc/did-go/doc/ld/mock" + "github.com/trustbloc/did-go/doc/ld/store" + kmsapi "github.com/trustbloc/kms-go/spi/kms" + + "github.com/trustbloc/vc-go/dataintegrity/models" + "github.com/trustbloc/vc-go/internal/testutil/kmscryptoutil" +) + +var ( + //go:embed testdata/valid_credential.jsonld + validCredential []byte + //go:embed testdata/invalid_jsonld.jsonld + invalidJSONLD []byte +) + +func TestIntegration(t *testing.T) { + docLoader, err := documentloader.NewDocumentLoader(createMockProvider()) + require.NoError(t, err) + + kmsCrypto := kmscryptoutil.LocalKMSCrypto(t) + + signerInit := NewSignerInitializer(&SignerInitializerOptions{ + LDDocumentLoader: docLoader, + SignerGetter: WithKMSCryptoWrapper(kmsCrypto), + }) + + signer, err := signerInit.Signer() + require.NoError(t, err) + + verifierInit := NewVerifierInitializer(&VerifierInitializerOptions{ + LDDocumentLoader: docLoader, + }) + + verifier, err := verifierInit.Verifier() + require.NoError(t, err) + + ed25519JWK, err := kmsCrypto.Create(kmsapi.ED25519) + require.NoError(t, err) + + ed25519VM, err := did.NewVerificationMethodFromJWK("#key-1", "JsonWebKey2020", "did:foo:bar", ed25519JWK) + require.NoError(t, err) + + ed25519JWK2, err := kmsCrypto.Create(kmsapi.ED25519) + require.NoError(t, err) + + ed25519VM2, err := did.NewVerificationMethodFromJWK("#key-1", "JsonWebKey2020", "did:foo:bar", ed25519JWK2) + require.NoError(t, err) + + t.Run("success", func(t *testing.T) { + t.Run("ED25519 key", func(t *testing.T) { + proofOpts := &models.ProofOptions{ + VerificationMethod: ed25519VM, + VerificationMethodID: ed25519VM.ID, + SuiteType: SuiteType, + Purpose: "assertionMethod", + ProofType: models.DataIntegrityProof, + Created: time.Now(), + MaxAge: 100, + } + + proof, err := signer.CreateProof(validCredential, proofOpts) + require.NoError(t, err) + + err = verifier.VerifyProof(validCredential, proof, proofOpts) + require.NoError(t, err) + }) + }) + + t.Run("failure", func(t *testing.T) { + t.Run("wrong key", func(t *testing.T) { + signOpts := &models.ProofOptions{ + VerificationMethod: ed25519VM, + VerificationMethodID: ed25519VM.ID, + SuiteType: SuiteType, + Purpose: "assertionMethod", + ProofType: models.DataIntegrityProof, + Created: time.Now(), + } + + verifyOpts := &models.ProofOptions{ + VerificationMethod: ed25519VM2, + VerificationMethodID: ed25519VM2.ID, + SuiteType: SuiteType, + Purpose: "assertionMethod", + ProofType: models.DataIntegrityProof, + MaxAge: 100, + } + + proof, err := signer.CreateProof(validCredential, signOpts) + require.NoError(t, err) + + err = verifier.VerifyProof(validCredential, proof, verifyOpts) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to verify eddsa-2022 DI proof") + }) + }) +} + +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(), + } +} diff --git a/dataintegrity/suite/eddsa2022/testdata/invalid_jsonld.jsonld b/dataintegrity/suite/eddsa2022/testdata/invalid_jsonld.jsonld new file mode 100644 index 0000000..1a77980 --- /dev/null +++ b/dataintegrity/suite/eddsa2022/testdata/invalid_jsonld.jsonld @@ -0,0 +1,15 @@ +{ + "@context": 3.1, + "id": "http://example.edu/credentials/1872", + "type": "VerifiableCredential", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuer": { + "id": "did:example:76e12ec712ebc6f1c221ebfeb1f", + "name": "Example University", + "image": "data:image/png;base64,iVBOR" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2020-01-01T19:23:24Z" +} diff --git a/dataintegrity/suite/eddsa2022/testdata/valid_credential.jsonld b/dataintegrity/suite/eddsa2022/testdata/valid_credential.jsonld new file mode 100644 index 0000000..6cca7de --- /dev/null +++ b/dataintegrity/suite/eddsa2022/testdata/valid_credential.jsonld @@ -0,0 +1,19 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/jws/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.edu/credentials/1872", + "type": "VerifiableCredential", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21" + }, + "issuer": { + "id": "did:example:76e12ec712ebc6f1c221ebfeb1f", + "name": "Example University", + "image": "data:image/png;base64,iVBOR" + }, + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2020-01-01T19:23:24Z" +}