From cbe209a404f8a2ee533820bb21fa450001cce7d0 Mon Sep 17 00:00:00 2001 From: Baha Shaaban Date: Wed, 9 Oct 2019 14:44:17 -0400 Subject: [PATCH] refactor: Crypter/Wallet crypto operations The current wallet is calling Pack/Unpack This behavior has now changed and delegated to a newer Packager interface In addition, operations involving private keys have been moved to the wallet. Namely, DerviveKEK() is the only function in the crypto functionality that deals with private keys Aries framework has been updated with new context functions to support the new structure. In addtion to WithWallet(), 2 new functions are added to the context provider: WithCrypter() and WithPackager(). JWE Crypto functionality depends on these providers in the following order: 1. WithWallet() 2. WithCrypter() 3. WithPackager() Also, minor refactoring of function names in the crypter was done for better readability. Finally the recipient key is no longer needed in Crypter.Decrypt() as it will be matched with the recipients's list in the envelope and the wallet's first key found. Signed-off-by: Baha Shaaban --- pkg/didcomm/crypto/crypter.go | 36 +- pkg/didcomm/crypto/crypter_test.go | 23 -- pkg/didcomm/crypto/jwe/authcrypt/authcrypt.go | 36 +- .../crypto/jwe/authcrypt/authcrypt_test.go | 189 +++++----- pkg/didcomm/crypto/jwe/authcrypt/common.go | 61 ---- pkg/didcomm/crypto/jwe/authcrypt/decrypt.go | 58 ++- .../crypto/jwe/authcrypt/decrypt_jwk.go | 18 +- .../crypto/jwe/authcrypt/decrypt_jwk_test.go | 28 +- pkg/didcomm/crypto/jwe/authcrypt/encrypt.go | 96 +++-- .../crypto/jwe/authcrypt/encrypt_jwk.go | 28 +- .../crypto/jwe/authcrypt/encrypt_jwk_test.go | 23 +- .../crypto/legacy/authcrypt/authcrypt.go | 8 +- .../crypto/legacy/authcrypt/authcrypt_test.go | 18 +- .../crypto/legacy/authcrypt/decrypt.go | 6 +- .../crypto/legacy/authcrypt/encrypt.go | 6 +- pkg/didcomm/dispatcher/api.go | 4 +- pkg/didcomm/dispatcher/outbound.go | 10 +- pkg/didcomm/dispatcher/outbound_test.go | 18 +- pkg/didcomm/envelope/api.go | 51 +++ pkg/didcomm/envelope/package_test.go | 188 ++++++++++ pkg/didcomm/envelope/packager.go | 70 ++++ pkg/didcomm/transport/http/inbound.go | 7 +- pkg/didcomm/transport/http/inbound_test.go | 26 +- pkg/didcomm/transport/transport_interface.go | 6 +- pkg/framework/aries/api/protocol.go | 4 + pkg/framework/aries/api/wallet.go | 1 - pkg/framework/aries/default.go | 31 +- pkg/framework/aries/framework.go | 62 +++- pkg/framework/aries/framework_test.go | 18 +- pkg/framework/context/context.go | 31 +- pkg/framework/context/context_test.go | 28 +- .../mock/didcomm/envelope/mock_packager.go | 27 ++ pkg/internal/mock/didcomm/mock_authcrypt.go | 16 +- pkg/internal/mock/provider/mock_provider.go | 7 + pkg/internal/mock/wallet/mock_wallet.go | 22 +- .../mock/wallet/mock_wallet_provider.go | 59 ++++ pkg/wallet/api.go | 65 +--- pkg/wallet/utils.go | 110 ++++++ pkg/wallet/utils_test.go | 60 ++++ pkg/wallet/wallet.go | 128 +++---- pkg/wallet/wallet_test.go | 329 +++++++----------- 41 files changed, 1231 insertions(+), 781 deletions(-) delete mode 100644 pkg/didcomm/crypto/crypter_test.go create mode 100644 pkg/didcomm/envelope/api.go create mode 100644 pkg/didcomm/envelope/package_test.go create mode 100644 pkg/didcomm/envelope/packager.go create mode 100644 pkg/internal/mock/didcomm/envelope/mock_packager.go create mode 100644 pkg/internal/mock/wallet/mock_wallet_provider.go create mode 100644 pkg/wallet/utils.go create mode 100644 pkg/wallet/utils_test.go diff --git a/pkg/didcomm/crypto/crypter.go b/pkg/didcomm/crypto/crypter.go index af0978b361..ac6a236ce3 100644 --- a/pkg/didcomm/crypto/crypter.go +++ b/pkg/didcomm/crypto/crypter.go @@ -6,6 +6,16 @@ SPDX-License-Identifier: Apache-2.0 package crypto +import "github.com/hyperledger/aries-framework-go/pkg/wallet" + +// Provider interface for Crypter ctx +type Provider interface { + CryptoWallet() wallet.Crypto +} + +// CrypterCreator method to create new crypter service +type CrypterCreator func(prov Provider) (Crypter, error) + // Crypter is an Aries envelope encrypter to support // secure DIDComm exchange of envelopes between Aries agents // TODO create a higher-level crypto that switches implementations based on the algorithm - Issue #273 @@ -16,29 +26,13 @@ type Crypter interface { // []byte containing the encrypted envelope // error if encryption failed // TODO add key type of recipients and sender keys to be validated by the implementation - Issue #272 - Encrypt(payload []byte, sender KeyPair, recipients [][]byte) ([]byte, error) - // Decrypt an envelope in an Aries compliant format with the recipient's private key - // and the recipient's public key both set in recipientKeyPair + Encrypt(payload []byte, senderKey []byte, recipients [][]byte) ([]byte, error) + // Decrypt an envelope in an Aries compliant format. + // The recipient's key will be matched from the wallet with the list of recipients in the envelope + // // returns: // []byte containing the decrypted payload // error if decryption failed // TODO add key type of recipients keys to be validated by the implementation - Issue #272 - Decrypt(envelope []byte, recipientKeyPair KeyPair) ([]byte, error) -} - -// KeyPair represents a private/public key pair each with 32 bytes in size -type KeyPair struct { - // Priv is a private key - Priv []byte - // Pub is a public key - Pub []byte -} - -// IsKeyPairValid is a utility function that validates a KeyPair -func IsKeyPairValid(kp KeyPair) bool { - if kp.Priv == nil || kp.Pub == nil { - return false - } - - return true + Decrypt(envelope []byte) ([]byte, error) } diff --git a/pkg/didcomm/crypto/crypter_test.go b/pkg/didcomm/crypto/crypter_test.go deleted file mode 100644 index 7cd236d8b2..0000000000 --- a/pkg/didcomm/crypto/crypter_test.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package crypto - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsKeyPairValid(t *testing.T) { - require.False(t, IsKeyPairValid(KeyPair{})) - pubKey := []byte("testpublickey") - privKey := []byte("testprivatekey") - - require.False(t, IsKeyPairValid(KeyPair{Priv: privKey, Pub: nil})) - require.False(t, IsKeyPairValid(KeyPair{Priv: nil, Pub: pubKey})) - require.True(t, IsKeyPairValid(KeyPair{Priv: privKey, Pub: pubKey})) -} diff --git a/pkg/didcomm/crypto/jwe/authcrypt/authcrypt.go b/pkg/didcomm/crypto/jwe/authcrypt/authcrypt.go index bf7efbf362..31c5677b33 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/authcrypt.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/authcrypt.go @@ -11,6 +11,9 @@ import ( "errors" chacha "golang.org/x/crypto/chacha20poly1305" + + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // This package deals with Authcrypt encryption for Packing/Unpacking DID Comm exchange @@ -30,18 +33,6 @@ const XC20P = ContentEncryption("XC20P") // XChacha20 encryption + Poly1305 auth //nolint:gochecknoglobals var randReader = rand.Reader -// errEmptyRecipients is used when recipients list is empty -var errEmptyRecipients = errors.New("empty recipients") - -// errInvalidKeypair is used when a keypair is invalid -var errInvalidKeypair = errors.New("invalid keypair") - -// errInvalidKey is used when a key is invalid -var errInvalidKey = errors.New("invalid key") - -// errRecipientNotFound is used when a recipient is not found -var errRecipientNotFound = errors.New("recipient not found") - // errUnsupportedAlg is used when a bad encryption algorithm is used var errUnsupportedAlg = errors.New("algorithm not supported") @@ -49,6 +40,7 @@ var errUnsupportedAlg = errors.New("algorithm not supported") type Crypter struct { alg ContentEncryption nonceSize int + wallet wallet.Crypto } // Envelope represents a JWE envelope as per the Aries Encryption envelope specs @@ -106,7 +98,8 @@ type jwk struct { // C20P (chacha20-poly1305 ietf) // XC20P (xchacha20-poly1305 ietf) // The returned crypter contains all the information required to encrypt payloads. -func New(alg ContentEncryption) (*Crypter, error) { +func New(ctx crypto.Provider, alg ContentEncryption) (*Crypter, error) { + w := ctx.CryptoWallet() var nonceSize int switch alg { case C20P: @@ -117,16 +110,9 @@ func New(alg ContentEncryption) (*Crypter, error) { return nil, errUnsupportedAlg } - c := &Crypter{ - alg, - nonceSize, - } - - return c, nil -} - -// IsChachaKeyValid will return true if key size is the same as chacha20poly1305.keySize -// false otherwise -func IsChachaKeyValid(key []byte) bool { - return len(key) == chacha.KeySize + return &Crypter{ + alg: alg, + nonceSize: nonceSize, + wallet: w, + }, nil } diff --git a/pkg/didcomm/crypto/jwe/authcrypt/authcrypt_test.go b/pkg/didcomm/crypto/jwe/authcrypt/authcrypt_test.go index e27ca780ab..a628f21d37 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/authcrypt_test.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/authcrypt_test.go @@ -17,7 +17,8 @@ import ( chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" - jwecrypto "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" + mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) func TestEncrypt(t *testing.T) { @@ -27,102 +28,106 @@ func TestEncrypt(t *testing.T) { // create temporary keys for testing ecKeyPub, ecKeyPriv, err = box.GenerateKey(randReader) require.NoError(t, err) - sendEcKey := jwecrypto.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} - t.Logf("sender key pub: %v", base64.RawURLEncoding.EncodeToString(sendEcKey.Pub)) - t.Logf("sender key priv: %v", base64.RawURLEncoding.EncodeToString(sendEcKey.Priv)) + senderKp := wallet.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} + t.Logf("sender key pub: %v", base64.RawURLEncoding.EncodeToString(senderKp.Pub)) + t.Logf("sender key priv: %v", base64.RawURLEncoding.EncodeToString(senderKp.Priv)) ecKeyPub, ecKeyPriv, err = box.GenerateKey(randReader) require.NoError(t, err) - recipient1Key := jwecrypto.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} - t.Logf("recipient1Key pub: %v", base64.RawURLEncoding.EncodeToString(recipient1Key.Pub)) - t.Logf("recipient1Key priv: %v", base64.RawURLEncoding.EncodeToString(recipient1Key.Priv)) + recipient1Kp := wallet.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} + t.Logf("recipient1Kp pub: %v", base64.RawURLEncoding.EncodeToString(recipient1Kp.Pub)) + t.Logf("recipient1Kp priv: %v", base64.RawURLEncoding.EncodeToString(recipient1Kp.Priv)) ecKeyPub, ecKeyPriv, err = box.GenerateKey(randReader) require.NoError(t, err) - recipient2Key := jwecrypto.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} - t.Logf("recipient2Key pub: %v", base64.RawURLEncoding.EncodeToString(recipient2Key.Pub)) - t.Logf("recipient2Key priv: %v", base64.RawURLEncoding.EncodeToString(recipient2Key.Priv)) + recipient2Kp := wallet.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} + t.Logf("recipient2Kp pub: %v", base64.RawURLEncoding.EncodeToString(recipient2Kp.Pub)) + t.Logf("recipient2Kp priv: %v", base64.RawURLEncoding.EncodeToString(recipient2Kp.Priv)) ecKeyPub, ecKeyPriv, err = box.GenerateKey(randReader) require.NoError(t, err) - recipient3Key := jwecrypto.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} - t.Logf("recipient3Key pub: %v", base64.RawURLEncoding.EncodeToString(recipient3Key.Pub)) - t.Logf("recipient3Key priv: %v", base64.RawURLEncoding.EncodeToString(recipient3Key.Priv)) - - badKey := jwecrypto.KeyPair{ + recipient3Kp := wallet.KeyPair{Priv: ecKeyPriv[:], Pub: ecKeyPub[:]} + t.Logf("recipient3Kp pub: %v", base64.RawURLEncoding.EncodeToString(recipient3Kp.Pub)) + t.Logf("recipient3Kp priv: %v", base64.RawURLEncoding.EncodeToString(recipient3Kp.Priv)) + senderWalletProvider, err := mockwallet.NewMockProvider(senderKp) + require.NoError(t, err) + senderAndRec1WalletProvider, err := mockwallet.NewMockProvider(senderKp, recipient1Kp) + require.NoError(t, err) + recipient1WalletProvider, err := mockwallet.NewMockProvider(recipient1Kp) + require.NoError(t, err) + recipient2WalletProvider, err := mockwallet.NewMockProvider(recipient2Kp) + require.NoError(t, err) + recipient3WalletProvider, err := mockwallet.NewMockProvider(recipient3Kp) + require.NoError(t, err) + badKey := wallet.KeyPair{ Pub: nil, Priv: nil, } t.Run("Error test case: Create a new AuthCrypter with bad encryption algorithm", func(t *testing.T) { - _, e := New("BAD") + _, e := New(senderWalletProvider, "BAD") require.Error(t, e) require.EqualError(t, e, errUnsupportedAlg.Error()) }) - t.Run("Error test case: Create a new AuthCrypter and use a bad sender key for encryption", func(t *testing.T) { - crypter, e := New(XC20P) + t.Run("Error test case: Create a new AuthCrypter and use an empty keys for encryption", func(t *testing.T) { + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) + badKey.Pub = []byte{} enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - badKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) - require.EqualError(t, e, "failed to encrypt message: invalid keypair") + badKey.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) + require.EqualError(t, e, "failed to encrypt message: empty sender key") require.Empty(t, enc) }) - t.Run("Error test case: Create a new AuthCrypter and use a bad key size for encryption", func(t *testing.T) { - crypter, e := New(XC20P) + t.Run("Error test case: Create a new AuthCrypter and use a bad key for encryption", func(t *testing.T) { + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) - // test bad sender public key size - badKey.Pub = []byte("badkeysize") - badKey.Priv = append(badKey.Priv, sendEcKey.Priv...) + // test bad sender public key + badKey.Pub = []byte("badkey") + enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - badKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) - require.EqualError(t, e, "failed to encrypt message: invalid key") - require.Empty(t, enc) - // test bad sender private key size - badKey.Pub = append([]byte{}, sendEcKey.Pub...) - badKey.Priv = []byte("badkeysize") - enc, e = crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - badKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) - require.EqualError(t, e, "failed to encrypt message: invalid key") + badKey.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) + require.EqualError(t, e, "failed from getKey: key not found") require.Empty(t, enc) + // reset badKey badKey.Pub = nil - badKey.Priv = nil + // test bad recipient 1 public key size enc, e = crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{[]byte("badkeysize"), recipient2Key.Pub, recipient3Key.Pub}) + senderKp.Pub, [][]byte{[]byte("badkeysize"), recipient2Kp.Pub, recipient3Kp.Pub}) require.EqualError(t, e, "failed to encrypt message: invalid key - for recipient 1") require.Empty(t, enc) // test bad recipient 2 public key size enc, e = crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{recipient1Key.Pub, []byte("badkeysize"), recipient3Key.Pub}) + senderKp.Pub, [][]byte{recipient1Kp.Pub, []byte("badkeysize"), recipient3Kp.Pub}) require.EqualError(t, e, "failed to encrypt message: invalid key - for recipient 2") require.Empty(t, enc) // test bad recipient 3 publick key size enc, e = crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, []byte("badkeysize")}) + senderKp.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, []byte("badkeysize")}) require.EqualError(t, e, "failed to encrypt message: invalid key - for recipient 3") require.Empty(t, enc) }) t.Run("Error test case: Create a new AuthCrypter and use an empty recipient keys list for encryption", func(t *testing.T) { //nolint:lll - crypter, e := New("XC20P") + crypter, e := New(senderWalletProvider, "XC20P") require.NoError(t, e) require.NotEmpty(t, crypter) - enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), sendEcKey, [][]byte{}) - require.Error(t, e) + enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), senderKp.Pub, [][]byte{}) + require.EqualError(t, e, "failed to encrypt message: empty recipients") require.Empty(t, enc) }) t.Run("Success test case: Create a valid AuthCrypter for ChachaPoly1305 encryption (alg: C20P)", func(t *testing.T) { - crypter, e := New(C20P) + crypter, e := New(senderWalletProvider, C20P) require.NoError(t, e) require.NotEmpty(t, crypter) enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + senderKp.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -132,11 +137,11 @@ func TestEncrypt(t *testing.T) { }) t.Run("Success test case: Create a valid AuthCrypter for XChachaPoly1305 encryption (alg: XC20P)", func(t *testing.T) { - crypter, e := New(XC20P) + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) enc, e := crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + senderKp.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -147,18 +152,20 @@ func TestEncrypt(t *testing.T) { t.Run("Error test Case: use a valid AuthCrypter but scramble the nonce size", func(t *testing.T) { crypter.nonceSize = 0 _, err = crypter.Encrypt([]byte("lorem ipsum dolor sit amet"), - sendEcKey, [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + senderKp.Pub, [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.Error(t, err) }) }) t.Run("Success test case: Decrypting a message (with the same crypter)", func(t *testing.T) { - crypter, e := New(XC20P) + // not a real life scenario, the wallet is using both sender and recipient1 key pairs + // senderAndRec1WalletProvider is used here for testing purposes only + crypter, e := New(senderAndRec1WalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) pld := []byte("lorem ipsum dolor sit amet") - enc, e := crypter.Encrypt(pld, sendEcKey, - [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + enc, e := crypter.Encrypt(pld, senderKp.Pub, + [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -167,20 +174,21 @@ func TestEncrypt(t *testing.T) { t.Logf("Encryption with unescaped XC20P: %s", enc) t.Logf("Encryption with XC20P: %s", m) - // decrypt for recipient1 - dec, e := crypter.Decrypt(enc, recipient1Key) + // decrypt for recipient1 (as found in wallet) + dec, e := crypter.Decrypt(enc) require.NoError(t, e) require.NotEmpty(t, dec) require.EqualValues(t, dec, pld) }) - t.Run("Success test case: Decrypting a message with two Crypter instances to simulate two agents", func(t *testing.T) { - crypter, e := New(XC20P) + t.Run("Success test case: Decrypting a message with two Crypter instances to simulate two agents", func(t *testing.T) { //nolint:lll + // encrypt with sender + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) pld := []byte("lorem ipsum dolor sit amet") - enc, e := crypter.Encrypt(pld, sendEcKey, - [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + enc, e := crypter.Encrypt(pld, senderKp.Pub, + [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -190,17 +198,17 @@ func TestEncrypt(t *testing.T) { t.Logf("Encryption with XC20P: %s", m) // now decrypt with recipient3 - crypter1, e := New(XC20P) + crypter1, e := New(recipient3WalletProvider, XC20P) require.NoError(t, e) - dec, e := crypter1.Decrypt(enc, recipient3Key) + dec, e := crypter1.Decrypt(enc) require.NoError(t, e) require.NotEmpty(t, dec) require.EqualValues(t, dec, pld) // now try decrypting with recipient2 - crypter2, e := New(XC20P) + crypter2, e := New(recipient2WalletProvider, XC20P) require.NoError(t, e) - dec, e = crypter2.Decrypt(enc, recipient2Key) + dec, e = crypter2.Decrypt(enc) require.NoError(t, e) require.NotEmpty(t, dec) require.EqualValues(t, dec, pld) @@ -208,11 +216,11 @@ func TestEncrypt(t *testing.T) { }) t.Run("Failure test case: Decrypting a message with an unauthorized (recipient2) agent", func(t *testing.T) { - crypter, e := New(XC20P) + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) pld := []byte("lorem ipsum dolor sit amet") - enc, e := crypter.Encrypt(pld, sendEcKey, [][]byte{recipient1Key.Pub, recipient3Key.Pub}) + enc, e := crypter.Encrypt(pld, senderKp.Pub, [][]byte{recipient1Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -222,27 +230,20 @@ func TestEncrypt(t *testing.T) { t.Logf("Encryption with XC20P: %s", m) // decrypting for recipient 2 (unauthorized) - crypter1, e := New(XC20P) + crypter1, e := New(recipient2WalletProvider, XC20P) require.NoError(t, e) - dec, e := crypter1.Decrypt(enc, recipient2Key) - require.Error(t, e) - require.Empty(t, dec) - - // now try to decrypt with an invalid recipient who's trying to use another agent's key - crypter1, e = New(XC20P) - require.NoError(t, e) - dec, e = crypter1.Decrypt(enc, jwecrypto.KeyPair{Priv: recipient2Key.Priv, Pub: recipient1Key.Pub}) + dec, e := crypter1.Decrypt(enc) require.Error(t, e) require.Empty(t, dec) }) t.Run("Failure test case: Decrypting a message but scramble JWE beforehand", func(t *testing.T) { - crypter, e := New(XC20P) + crypter, e := New(senderWalletProvider, XC20P) require.NoError(t, e) require.NotEmpty(t, crypter) pld := []byte("lorem ipsum dolor sit amet") - enc, e := crypter.Encrypt(pld, sendEcKey, - [][]byte{recipient1Key.Pub, recipient2Key.Pub, recipient3Key.Pub}) + enc, e := crypter.Encrypt(pld, senderKp.Pub, + [][]byte{recipient1Kp.Pub, recipient2Kp.Pub, recipient3Kp.Pub}) require.NoError(t, e) require.NotEmpty(t, enc) @@ -254,10 +255,8 @@ func TestEncrypt(t *testing.T) { jwe := &Envelope{} deepCopy(jwe, validJwe) - // test decrypting with empty recipient key - dec, e := crypter.Decrypt(enc, jwecrypto.KeyPair{Priv: recipient1Key.Priv, Pub: []byte{}}) - require.EqualError(t, e, fmt.Sprintf("failed to decrypt message: %s", errRecipientNotFound.Error())) - require.Empty(t, dec) + // create a new crypter for recipient1 for testing decryption + crypter, e = New(recipient1WalletProvider, XC20P) // test bad jwe format enc = []byte("{badJWE}") @@ -267,7 +266,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad nonce format - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e := crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt message: illegal base64 data at input byte 12") require.Empty(t, dec) jwe.CipherText = validJwe.CipherText @@ -277,7 +276,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad nonce format - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt message: illegal base64 data at input byte 5") require.Empty(t, dec) jwe.IV = validJwe.IV @@ -287,7 +286,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag format - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt message: illegal base64 data at input byte 6") require.Empty(t, dec) jwe.Tag = validJwe.Tag @@ -297,7 +296,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt sender key: bad SPK format") require.Empty(t, dec) jwe.Recipients[0].Header.SPK = validJwe.Recipients[0].Header.SPK @@ -307,7 +306,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: illegal base64 data at input byte 6") require.Empty(t, dec) jwe.Recipients[0].Header.Tag = validJwe.Recipients[0].Header.Tag @@ -317,7 +316,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: illegal base64 data at input byte 5") require.Empty(t, dec) jwe.Recipients[0].Header.IV = validJwe.Recipients[0].Header.IV @@ -327,7 +326,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: bad nonce size") require.Empty(t, dec) jwe.Recipients[0].Header.IV = validJwe.Recipients[0].Header.IV @@ -337,7 +336,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: illegal base64 data at input byte 6") require.Empty(t, dec) jwe.Recipients[0].Header.APU = validJwe.Recipients[0].Header.APU @@ -347,8 +346,8 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) - require.EqualError(t, e, fmt.Sprintf("failed to decrypt message: %s", errRecipientNotFound.Error())) + dec, e = crypter.Decrypt(enc) + require.EqualError(t, e, fmt.Sprintf("failed to decrypt message: %s", wallet.ErrKeyNotFound.Error())) require.Empty(t, dec) jwe.Recipients[0].Header.KID = validJwe.Recipients[0].Header.KID @@ -357,7 +356,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: illegal base64 data at input byte 15") require.Empty(t, dec) jwe.Recipients[0].EncryptedKey = validJwe.Recipients[0].EncryptedKey @@ -367,7 +366,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: chacha20poly1305: message authentication failed") require.Empty(t, dec) jwe.Recipients[0].EncryptedKey = validJwe.Recipients[0].EncryptedKey @@ -378,7 +377,7 @@ func TestEncrypt(t *testing.T) { require.NoError(t, e) // decrypt with bad nonce value require.PanicsWithValue(t, "chacha20poly1305: bad nonce length passed to Open", func() { - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) }) require.Empty(t, dec) jwe.IV = validJwe.IV @@ -388,7 +387,7 @@ func TestEncrypt(t *testing.T) { enc, e = json.Marshal(jwe) require.NoError(t, e) // decrypt with bad tag - dec, e = crypter.Decrypt(enc, recipient1Key) + dec, e = crypter.Decrypt(enc) require.EqualError(t, e, "failed to decrypt shared key: chacha20poly1305: message authentication failed") require.Empty(t, dec) jwe.Recipients[0].Header.IV = validJwe.Recipients[0].Header.IV @@ -441,6 +440,10 @@ func TestRefEncrypt(t *testing.T) { recipientPub, err := base64.RawURLEncoding.DecodeString(recipientPubStr) require.NoError(t, err) + // create mockwallet provider with the above keys + mockWalletProvider, err := mockwallet.NewMockProvider(wallet.KeyPair{Pub: recipientPub, Priv: recipientPriv}) + require.NoError(t, err) + // refJWE created by executing PHP test code at: // https://github.com/gamringer/php-authcrypt/blob/master/examples/1-crypt.php //nolint:lll @@ -464,11 +467,11 @@ func TestRefEncrypt(t *testing.T) { "ciphertext": "qQyzvajdvCDJbwxM" }` - crypter, err := New(XC20P) + crypter, err := New(mockWalletProvider, XC20P) require.NoError(t, err) require.NotNil(t, crypter) - dec, err := crypter.Decrypt([]byte(refJWE), jwecrypto.KeyPair{Priv: recipientPriv, Pub: recipientPub}) + dec, err := crypter.Decrypt([]byte(refJWE)) require.NoError(t, err) require.NotEmpty(t, dec) } diff --git a/pkg/didcomm/crypto/jwe/authcrypt/common.go b/pkg/didcomm/crypto/jwe/authcrypt/common.go index 2fe6156965..4863d83424 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/common.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/common.go @@ -7,14 +7,10 @@ SPDX-License-Identifier: Apache-2.0 package authcrypt import ( - "crypto" "crypto/cipher" - "encoding/binary" "errors" - josecipher "github.com/square/go-jose/v3/cipher" chacha "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/curve25519" ) // createCipher will create and return a new Chacha20Poly1305 cipher for the given nonceSize and symmetric key @@ -28,60 +24,3 @@ func createCipher(nonceSize int, symKey []byte) (cipher.AEAD, error) { return nil, errors.New("cipher cannot be created with bad nonce size and shared symmetric Key combo") } } - -// lengthPrefix array with a bigEndian uint32 value of array's length -func lengthPrefix(array []byte) []byte { - arrInfo := make([]byte, 4+len(array)) - binary.BigEndian.PutUint32(arrInfo, uint32(len(array))) - copy(arrInfo[4:], array) - return arrInfo -} - -// deriveKEK will derive an ephemeral symmetric key (kek) from privKey and pubKey to -// be used for encrypting a cek. This function assumes both privKey and pubKey are curve25519. -// it will return this new key along with the corresponding APU or an error if it fails. -func (c *Crypter) deriveKEK(alg, apu []byte, privKey, pubKey *[chacha.KeySize]byte) ([]byte, error) { - if privKey == nil || pubKey == nil { - return nil, errInvalidKey - } - // generating Z is inspired by sodium_crypto_scalarmult() - // https://github.com/gamringer/php-authcrypt/blob/master/src/Crypt.php#L80 - - // with z being a basePoint of a curve25519 - z := new([chacha.KeySize]byte) - // do ScalarMult of the sender's private key with the recipient key to get a derived Z point - // ( equivalent to derive an EC key ) - curve25519.ScalarMult(z, privKey, pubKey) - - // inspired by: github.com/square/go-jose/v3@v3.0.0-20190722231519-723929d55157/cipher/ecdh_es.go - // -> DeriveECDHES() call - // suppPubInfo is the encoded length of the recipient shared key output size in bits - supPubInfo := make([]byte, 4) - // since we're using chacha20poly1305 keys, keySize is known - binary.BigEndian.PutUint32(supPubInfo, uint32(chacha.KeySize)*8) - - // as per https://tools.ietf.org/html/rfc7518#section-4.6.2 - // concatKDF requires info data to be length prefixed with BigEndian 32 bits type - // length prefix alg - algInfo := lengthPrefix(alg) - - // length prefix apu - apuInfo := lengthPrefix(apu) - - // length prefix apv (empty) - apvInfo := lengthPrefix(nil) - - // get a Concat KDF stream for z, encryption algorithm, api, supPubInfo and empty supPrivInfo using sha256 - reader := josecipher.NewConcatKDF(crypto.SHA256, z[:], algInfo, apuInfo, apvInfo, supPubInfo, []byte{}) - - // kek is the recipient specific encryption key used to encrypt the sharedSymKey - kek := make([]byte, chacha.KeySize) - - // Read on the KDF will never fail - _, err := reader.Read(kek) - if err != nil { - return nil, err - } - - return kek, nil -} diff --git a/pkg/didcomm/crypto/jwe/authcrypt/decrypt.go b/pkg/didcomm/crypto/jwe/authcrypt/decrypt.go index 99623c8743..d4de10e7e8 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/decrypt.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/decrypt.go @@ -7,7 +7,6 @@ SPDX-License-Identifier: Apache-2.0 package authcrypt import ( - "bytes" "encoding/base64" "encoding/json" "errors" @@ -15,8 +14,6 @@ import ( "github.com/btcsuite/btcutil/base58" chacha "golang.org/x/crypto/chacha20poly1305" - - jwecrypto "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" ) // Decrypt will JWE decode the envelope argument for the recipientPrivKey and validates @@ -25,34 +22,29 @@ import ( // encrypted CEK. // The current recipient is the one with the sender's encrypted key that successfully // decrypts with recipientKeyPair.Priv Key. -func (c *Crypter) Decrypt(envelope []byte, recipientKeyPair jwecrypto.KeyPair) ([]byte, error) { //nolint:lll,funlen - if !jwecrypto.IsKeyPairValid(recipientKeyPair) { - return nil, errInvalidKeypair - } - +func (c *Crypter) Decrypt(envelope []byte) ([]byte, error) { jwe := &Envelope{} err := json.Unmarshal(envelope, jwe) if err != nil { return nil, fmt.Errorf("failed to decrypt message: %w", err) } - pubK := new([chacha.KeySize]byte) - copy(pubK[:], recipientKeyPair.Pub) - recipient, err := c.findRecipient(jwe.Recipients, pubK) + + recipientPubKey, recipient, err := c.findRecipient(jwe.Recipients) if err != nil { return nil, fmt.Errorf("failed to decrypt message: %w", err) } - senderKey, err := c.decryptSPK(recipientKeyPair, recipient.Header.SPK) + senderKey, err := c.decryptSPK(recipientPubKey, recipient.Header.SPK) if err != nil { return nil, fmt.Errorf("failed to decrypt sender key: %w", err) } // senderKey must not be empty to proceed if senderKey != nil { - var senderPubKey [chacha.KeySize]byte + senderPubKey := new([chacha.KeySize]byte) copy(senderPubKey[:], senderKey) - sharedKey, er := c.decryptSharedKey(recipientKeyPair, &senderPubKey, recipient) + sharedKey, er := c.decryptCEK(recipientPubKey, senderPubKey, recipient) if er != nil { return nil, fmt.Errorf("failed to decrypt shared key: %w", er) } @@ -69,7 +61,7 @@ func (c *Crypter) Decrypt(envelope []byte, recipientKeyPair jwecrypto.KeyPair) ( } func (c *Crypter) decryptPayload(cek []byte, jwe *Envelope) ([]byte, error) { - crypter, er := createCipher(c.nonceSize, cek) + cipher, er := createCipher(c.nonceSize, cek) if er != nil { return nil, er } @@ -88,21 +80,28 @@ func (c *Crypter) decryptPayload(cek []byte, jwe *Envelope) ([]byte, error) { return nil, er } payload = append(payload, tag...) - return crypter.Open(nil, nonce, payload, []byte(pldAAD)) + return cipher.Open(nil, nonce, payload, []byte(pldAAD)) } -// findRecipient will loop through jweRecipients and returns the first matching key from recipients -func (c *Crypter) findRecipient(jweRecipients []Recipient, recipientPubKey *[chacha.KeySize]byte) (*Recipient, error) { +// findRecipient will loop through jweRecipients and returns the first matching key from the wallet +func (c *Crypter) findRecipient(jweRecipients []Recipient) (*[chacha.KeySize]byte, *Recipient, error) { + var recipientsKeys []string for _, recipient := range jweRecipients { - recipient := recipient // pin! - if bytes.Equal(recipientPubKey[:], base58.Decode(recipient.Header.KID)) { - return &recipient, nil - } + recipientsKeys = append(recipientsKeys, recipient.Header.KID) } - return nil, errRecipientNotFound + + i, err := c.wallet.FindVerKey(recipientsKeys) + if err != nil { + return nil, nil, err + } + + pubK := new([chacha.KeySize]byte) + copy(pubK[:], base58.Decode(recipientsKeys[i])) + return pubK, &jweRecipients[i], nil } -func (c *Crypter) decryptSharedKey(recipientKp jwecrypto.KeyPair, senderPubKey *[chacha.KeySize]byte, recipient *Recipient) ([]byte, error) { //nolint:lll +// decryptCEK will decrypt the CEK found in recipient using recipientKp's private key and senderPubKey +func (c *Crypter) decryptCEK(recipientPubKey, senderPubKey *[chacha.KeySize]byte, recipient *Recipient) ([]byte, error) { //nolint:lll apu, err := base64.RawURLEncoding.DecodeString(recipient.Header.APU) if err != nil { return nil, err @@ -120,27 +119,24 @@ func (c *Crypter) decryptSharedKey(recipientKp jwecrypto.KeyPair, senderPubKey * if err != nil { return nil, err } - sharedEncryptedKey, err := base64.RawURLEncoding.DecodeString(recipient.EncryptedKey) + encryptedCEK, err := base64.RawURLEncoding.DecodeString(recipient.EncryptedKey) if err != nil { return nil, err } - privK := new([chacha.KeySize]byte) - copy(privK[:], recipientKp.Priv) - // derive an ephemeral key for the recipient - kek, err := c.deriveKEK([]byte(c.alg), apu, privK, senderPubKey) + kek, err := c.wallet.DeriveKEK([]byte(c.alg), apu, recipientPubKey[:], senderPubKey[:]) if err != nil { return nil, err } - // create a new (chacha20poly1305) cipher with this new key to encrypt the shared key (cek) + // create a new (chacha20poly1305) cipher with this new key to decrypt the cek cipher, err := createCipher(c.nonceSize, kek) if err != nil { return nil, err } - cipherText := sharedEncryptedKey + cipherText := encryptedCEK cipherText = append(cipherText, tag...) return cipher.Open(nil, nonce, cipherText, nil) diff --git a/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk.go b/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk.go index a7af51d7be..32db8bd85a 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk.go @@ -13,20 +13,12 @@ import ( "strings" chacha "golang.org/x/crypto/chacha20poly1305" - - jwecrypto "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" ) // decryptSPK will decrypt a recipient's encrypted SPK (in the case of this package, it is represented as // the sender's public key as a jwk). It uses the recipent's private/public keypair for decryption // the returned decrypted value is the sender's public key -func (c *Crypter) decryptSPK(recipientKeyPair jwecrypto.KeyPair, spk string) ([]byte, error) { - var recPubKey [chacha.KeySize]byte - copy(recPubKey[:], recipientKeyPair.Pub) - - recPrivKey := new([chacha.KeySize]byte) - copy(recPrivKey[:], recipientKeyPair.Priv) - +func (c *Crypter) decryptSPK(recipientPubKey *[chacha.KeySize]byte, spk string) ([]byte, error) { jwe := strings.Split(spk, ".") if len(jwe) != 5 { return nil, fmt.Errorf("bad SPK format") @@ -66,7 +58,7 @@ func (c *Crypter) decryptSPK(recipientKeyPair jwecrypto.KeyPair, spk string) ([] return nil, err } - sharedKey, err := c.decryptJWKSharedKey(cipherKEK, headersJSON, recPrivKey) + sharedKey, err := c.decryptJWKSharedKey(cipherKEK, headersJSON, recipientPubKey[:]) if err != nil { return nil, err } @@ -77,15 +69,13 @@ func (c *Crypter) decryptSPK(recipientKeyPair jwecrypto.KeyPair, spk string) ([] // decryptJWKSharedKey will decrypt the cek using recPrivKey for decryption and rebuild the cipher text, nonce // kek from headersJSON, the result is the sharedKey to be used for decrypting the sender JWK -func (c *Crypter) decryptJWKSharedKey(cipherKEK []byte, headersJSON *recipientSPKJWEHeaders, recPrivKey *[chacha.KeySize]byte) ([]byte, error) { //nolint:lll +func (c *Crypter) decryptJWKSharedKey(cipherKEK []byte, headersJSON *recipientSPKJWEHeaders, recPubKey []byte) ([]byte, error) { //nolint:lll epk, err := base64.RawURLEncoding.DecodeString(headersJSON.EPK.X) if err != nil { return nil, err } - epKey := new([chacha.KeySize]byte) - copy(epKey[:], epk) - kek, err := c.deriveKEK([]byte(c.alg+"KW"), nil, recPrivKey, epKey) + kek, err := c.wallet.DeriveKEK([]byte(c.alg+"KW"), nil, recPubKey, epk) if err != nil { return nil, err } diff --git a/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk_test.go b/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk_test.go index f03f1a1e8c..eaa62c53fd 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk_test.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/decrypt_jwk_test.go @@ -13,55 +13,59 @@ import ( "github.com/stretchr/testify/require" chacha "golang.org/x/crypto/chacha20poly1305" - jwecrypto "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" + mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" ) //nolint:lll func TestNilDecryptSenderJwk(t *testing.T) { - crypter, err := New(XC20P) + mockWalletProvider, err := mockwallet.NewMockProvider() require.NoError(t, err) - spk, err := crypter.decryptSPK(jwecrypto.KeyPair{}, "!-.t.t.t.t") + crypter, err := New(mockWalletProvider, XC20P) + require.NoError(t, err) + + spk, err := crypter.decryptSPK(nil, "!-.t.t.t.t") require.Error(t, err) require.Empty(t, spk) - spk, err = crypter.decryptSPK(jwecrypto.KeyPair{}, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.!-.t.t.t") + spk, err = crypter.decryptSPK(nil, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.!-.t.t.t") require.Error(t, err) require.Empty(t, spk) - spk, err = crypter.decryptSPK(jwecrypto.KeyPair{}, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.!-.t.t") + spk, err = crypter.decryptSPK(nil, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.!-.t.t") require.Error(t, err) require.Empty(t, spk) - spk, err = crypter.decryptSPK(jwecrypto.KeyPair{}, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.aigDJrko05dw-9Hk4LQbfOCCG9Dzskw6.!-.t") + spk, err = crypter.decryptSPK(nil, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.aigDJrko05dw-9Hk4LQbfOCCG9Dzskw6.!-.t") require.Error(t, err) require.Empty(t, spk) - spk, err = crypter.decryptSPK(jwecrypto.KeyPair{}, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.aigDJrko05dw-9Hk4LQbfOCCG9Dzskw6.tY10QY9fXvqV_vfhzBKkqw.!-") + spk, err = crypter.decryptSPK(nil, "eyJ0eXAiOiJqb3NlIiwiY3R5IjoiandrK2pzb24iLCJhbGciOiJFQ0RILUVTK1hDMjBQS1ciLCJlbmMiOiJYQzIwUCIsIml2IjoiNWhwNEVrWGtqSHR0SFlmY1IySXQ4d2dnZndjanNQaWwiLCJ0YWciOiJuMjg1OGplTXhZVE0tYzRZc2J0ZlBRIiwiZXBrIjp7Imt0eSI6Ik9LUCIsImNydiI6IlgyNTUxOSIsIngiOiJ3OW1EZ1FENnJVdWkyLVMyRjV6SVNqZXBua1FOZWEwMGtvTnRBOUhEeUIwIn19.U-AXyneFJ5x4QayrZ3GcuDCg1yHYHC9Kn1s8gtd7O4c.aigDJrko05dw-9Hk4LQbfOCCG9Dzskw6.tY10QY9fXvqV_vfhzBKkqw.!-") require.Error(t, err) require.Empty(t, spk) headersJSON := &recipientSPKJWEHeaders{EPK: jwk{ X: "test", }} - spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, nil) + someKey := new([chacha.KeySize]byte) + spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey[:]) require.Error(t, err) require.Empty(t, spk) headersJSON.EPK.X = "!-" - spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, nil) + spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey[:]) require.Error(t, err) require.Empty(t, spk) headersJSON.EPK.X = "test" headersJSON.Tag = "!-" - someKey := new([chacha.KeySize]byte) - spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey) + + spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey[:]) require.Error(t, err) require.Empty(t, spk) headersJSON.Tag = "test" headersJSON.IV = "!-" - spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey) + spk, err = crypter.decryptJWKSharedKey([]byte(""), headersJSON, someKey[:]) require.Error(t, err) require.Empty(t, spk) diff --git a/pkg/didcomm/crypto/jwe/authcrypt/encrypt.go b/pkg/didcomm/crypto/jwe/authcrypt/encrypt.go index 95341db3be..1dbacc0d99 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/encrypt.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/encrypt.go @@ -18,24 +18,29 @@ import ( chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/poly1305" - jwecrypto "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // Encrypt will JWE encode the payload argument for the sender and recipients // Using (X)Chacha20 encryption algorithm and Poly1305 authenticator // It will encrypt using the sender's keypair and the list of recipients arguments -func (c *Crypter) Encrypt(payload []byte, sender jwecrypto.KeyPair, recipients [][]byte) ([]byte, error) { //nolint:lll,funlen - err := verifyKeys(sender, recipients) - if err != nil { - return nil, fmt.Errorf("failed to encrypt message: %w", err) +func (c *Crypter) Encrypt(payload, senderKey []byte, recipients [][]byte) ([]byte, error) { //nolint:funlen + if len(senderKey) == 0 { + return nil, fmt.Errorf("failed to encrypt message: empty sender key") } + senderPubKey := &[chacha.KeySize]byte{} + copy(senderPubKey[:], senderKey) + headers := jweHeaders{ Typ: "prs.hyperledger.aries-auth-message", Alg: "ECDH-SS+" + string(c.alg) + "KW", Enc: string(c.alg), } + if len(recipients) == 0 { + return nil, fmt.Errorf("failed to encrypt message: empty recipients") + } chachaRecipients, err := convertRecipients(recipients) if err != nil { return nil, fmt.Errorf("failed to encrypt message: %w", err) @@ -69,20 +74,20 @@ func (c *Crypter) Encrypt(payload []byte, sender jwecrypto.KeyPair, recipients [ } // create a cipher for the given nonceSize and generated cek above - crypter, err := createCipher(c.nonceSize, cek[:]) + cipher, err := createCipher(c.nonceSize, cek[:]) if err != nil { return nil, err } // encrypt payload using generated nonce, payload and its AAD // the output is a []byte containing the cipherText + tag - symOutput := crypter.Seal(nil, nonce, payload, []byte(pldAAD)) + symOutput := cipher.Seal(nil, nonce, payload, []byte(pldAAD)) tagEncoded := extractTag(symOutput) cipherTextEncoded := extractCipherText(symOutput) // now build, encode recipients and include the encrypted cek (with a recipient's ephemeral key) - encRec, err := c.encodeRecipients(cek, chachaRecipients, sender) + encRec, err := c.encodeRecipients(cek, chachaRecipients, senderPubKey) if err != nil { return nil, err } @@ -95,27 +100,13 @@ func (c *Crypter) Encrypt(payload []byte, sender jwecrypto.KeyPair, recipients [ return jwe, nil } -func verifyKeys(sender jwecrypto.KeyPair, recipients [][]byte) error { - if len(recipients) == 0 { - return errEmptyRecipients - } - - if !jwecrypto.IsKeyPairValid(sender) { - return errInvalidKeypair - } - - if !IsChachaKeyValid(sender.Priv) || !IsChachaKeyValid(sender.Pub) { - return errInvalidKey - } - return nil -} - +// convertRecipients is a utility function that converts keys from [][]byte type into []*[chacha.KeySize]byte type func convertRecipients(recipients [][]byte) ([]*[chacha.KeySize]byte, error) { var chachaRecipients []*[chacha.KeySize]byte for i, r := range recipients { - if !IsChachaKeyValid(r) { - return nil, fmt.Errorf("%w - for recipient %d", errInvalidKey, i+1) + if !wallet.IsChachaKeyValid(r) { + return nil, fmt.Errorf("%w - for recipient %d", wallet.ErrInvalidKey, i+1) } chachaRec := new([chacha.KeySize]byte) @@ -125,7 +116,7 @@ func convertRecipients(recipients [][]byte) ([]*[chacha.KeySize]byte, error) { return chachaRecipients, nil } -// extractTag extracts the base64UrlEncoded tag sub slice from symOutput returned by cipher.Seal +// extractTag is a utility function that extracts base64UrlEncoded tag sub-slice from symOutput returned by cipher.Seal func extractTag(symOutput []byte) string { // symOutput has a length of len(clear msg) + poly1305.TagSize // fetch the tag from the tail of symOutput @@ -135,7 +126,8 @@ func extractTag(symOutput []byte) string { return base64.RawURLEncoding.EncodeToString(tag) } -// extractCipherText extracts the base64UrlEncoded cipherText sub slice from symOutput returned by cipher.Seal +// extractCipherText is a utility function that extracts base64UrlEncoded cipherText sub-slice +// from symOutput returned by cipher.Seal func extractCipherText(symOutput []byte) string { // fetch the cipherText from the head of symOutput (0:up to the trailing tag) cipherText := symOutput[0 : len(symOutput)-poly1305.TagSize] @@ -146,7 +138,7 @@ func extractCipherText(symOutput []byte) string { // buildJWE builds the JSON object representing the JWE output of the encryption // and returns its marshaled []byte representation -func (c *Crypter) buildJWE(headers string, recipients []Recipient, aad, iv, tag, cipherText string) ([]byte, error) { +func (c *Crypter) buildJWE(headers string, recipients []Recipient, aad, iv, tag, cipherText string) ([]byte, error) { //nolint:lll jwe := Envelope{ Protected: headers, Recipients: recipients, @@ -164,7 +156,7 @@ func (c *Crypter) buildJWE(headers string, recipients []Recipient, aad, iv, tag, return jweBytes, nil } -// buildAAD to build the Additional Authentication Data for the AEAD (chach20poly1305) cipher. +// buildAAD is a utility function to build the Additional Authentication Data for the AEAD (chach20poly1305) cipher. // the build takes the list of recipients keys base58 encoded and sorted then SHA256 hash // the concatenation of these keys with a '.' separator func buildAAD(recipients []*[chacha.KeySize]byte) []byte { @@ -183,12 +175,12 @@ func hashAAD(keys []string) []byte { return sha[:] } -// encodeRecipients will encode the sharedKey (cek) for each recipient -// and return a list of encoded recipient keys -func (c *Crypter) encodeRecipients(sharedSymKey *[chacha.KeySize]byte, recipients []*[chacha.KeySize]byte, senderKp jwecrypto.KeyPair) ([]Recipient, error) { //nolint:lll +// encodeRecipients is a utility function that will encrypt the cek (content encryption key) for each recipient +// and return a list of encoded recipient keys in a JWE compliant format ([]Recipient) +func (c *Crypter) encodeRecipients(cek *[chacha.KeySize]byte, recipients []*[chacha.KeySize]byte, senderPubKey *[chacha.KeySize]byte) ([]Recipient, error) { //nolint:lll var encodedRecipients []Recipient for _, e := range recipients { - rec, err := c.encodeRecipient(sharedSymKey, e, senderKp) + rec, err := c.encodeRecipient(cek, e, senderPubKey) if err != nil { return nil, err } @@ -197,9 +189,10 @@ func (c *Crypter) encodeRecipients(sharedSymKey *[chacha.KeySize]byte, recipient return encodedRecipients, nil } -// encodeRecipient will encode the sharedKey (cek) with recipientKey -// by generating a new ephemeral key to be used by the recipient to decrypt the cek -func (c *Crypter) encodeRecipient(sharedSymKey, recipientKey *[chacha.KeySize]byte, senderKp jwecrypto.KeyPair) (*Recipient, error) { //nolint:lll +// encodeRecipient will encrypt the cek (content encryption key) with a recipientKey +// by generating a new ephemeral key to be used by the recipient to later decrypt it +// it returns a JWE compliant Recipient +func (c *Crypter) encodeRecipient(cek, recipientPubKey, senderPubKey *[chacha.KeySize]byte) (*Recipient, error) { //nolint:lll // generate a random APU value (Agreement PartyUInfo: https://tools.ietf.org/html/rfc7518#section-4.6.1.2) apu := make([]byte, 64) _, err := randReader.Read(apu) @@ -207,32 +200,27 @@ func (c *Crypter) encodeRecipient(sharedSymKey, recipientKey *[chacha.KeySize]by return nil, err } - privK := new([chacha.KeySize]byte) - copy(privK[:], senderKp.Priv) - // derive an ephemeral key for the recipient - kek, err := c.deriveKEK([]byte(c.alg), apu, privK, recipientKey) + // derive an ephemeral key for the sender and recipient + kek, err := c.wallet.DeriveKEK([]byte(c.alg), apu, senderPubKey[:], recipientPubKey[:]) if err != nil { return nil, err } - sharedKeyCipher, tag, nonce, err := c.encryptSymKey(kek, sharedSymKey[:]) + sharedKeyCipher, tag, nonce, err := c.encryptCEK(kek, cek[:]) if err != nil { return nil, err } - pubK := new([chacha.KeySize]byte) - copy(pubK[:], senderKp.Pub) - - return c.buildRecipient(sharedKeyCipher, apu, nonce, tag, pubK, recipientKey) -} - -// buildRecipient will build a proper JSON formatted Recipient -func (c *Crypter) buildRecipient(key string, apu []byte, nonceEncoded, tagEncoded string, senderPubKey, recipientKey *[chacha.KeySize]byte) (*Recipient, error) { //nolint:lll - spkEncoded, err := c.generateSPK(recipientKey, senderPubKey) + spk, err := c.generateSPK(recipientPubKey, senderPubKey) if err != nil { return nil, err } + return c.buildRecipient(sharedKeyCipher, apu, spk, nonce, tag, recipientPubKey) +} + +// buildRecipient will build a proper JSON formatted and JWE compliant Recipient +func (c *Crypter) buildRecipient(key string, apu []byte, spkEncoded, nonceEncoded, tagEncoded string, recipientKey *[chacha.KeySize]byte) (*Recipient, error) { //nolint:lll recipientHeaders := RecipientHeaders{ APU: base64.RawURLEncoding.EncodeToString(apu), IV: nonceEncoded, @@ -249,14 +237,14 @@ func (c *Crypter) buildRecipient(key string, apu []byte, nonceEncoded, tagEncode return recipient, nil } -// encryptSymKey will encrypt symKey with the given kek and a newly generated nonce +// encryptCEK will encrypt symKey with the given kek and a newly generated nonce // returns: // encrypted cipher of symKey // resulting tag of the encryption // generated nonce used by the encryption // error in case of failure -func (c *Crypter) encryptSymKey(kek, symKey []byte) (string, string, string, error) { - crypter, err := createCipher(c.nonceSize, kek) +func (c *Crypter) encryptCEK(kek, cek []byte) (string, string, string, error) { + cipher, err := createCipher(c.nonceSize, kek) if err != nil { return "", "", "", err } @@ -269,7 +257,7 @@ func (c *Crypter) encryptSymKey(kek, symKey []byte) (string, string, string, err } // encrypt symmetric shared key using the key encryption key (kek) - kekOutput := crypter.Seal(nil, nonce, symKey, nil) + kekOutput := cipher.Seal(nil, nonce, cek, nil) symKeyCipherEncoded := extractCipherText(kekOutput) tagEncoded := extractTag(kekOutput) diff --git a/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk.go b/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk.go index b35d5868b0..3bb1e1f6ca 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk.go @@ -12,6 +12,8 @@ import ( chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" + + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // generateSPK will encrypt a msg (in the case of this package, it will be @@ -19,7 +21,7 @@ import ( // a compact JWE wrapping a JWK containing the (encrypted) sender's public key func (c *Crypter) generateSPK(recipientPubKey, senderPubKey *[chacha.KeySize]byte) (string, error) { if recipientPubKey == nil { - return "", errInvalidKey + return "", wallet.ErrInvalidKey } // generate ephemeral asymmetric keys @@ -28,20 +30,20 @@ func (c *Crypter) generateSPK(recipientPubKey, senderPubKey *[chacha.KeySize]byt return "", err } - // derive an ephemeral key for the recipient - kek, err := c.deriveKEK([]byte(c.alg+"KW"), nil, esk, recipientPubKey) + // derive an ephemeral key for the recipient and an ephemeral secret key (esk) + kek, err := wallet.Derive25519KEK([]byte(c.alg+"KW"), nil, esk, recipientPubKey) if err != nil { return "", err } - // generate a sharedSymKey for encryption - sharedSymKey := &[chacha.KeySize]byte{} - _, err = randReader.Read(sharedSymKey[:]) + // generate a cek for encryption + cek := &[chacha.KeySize]byte{} + _, err = randReader.Read(cek[:]) if err != nil { return "", err } - kCipherEncoded, kTagEncoded, kNonceEncoded, err := c.encryptSymKey(kek, sharedSymKey[:]) + kCipherEncoded, kTagEncoded, kNonceEncoded, err := c.encryptCEK(kek, cek[:]) if err != nil { return "", err } @@ -57,13 +59,13 @@ func (c *Crypter) generateSPK(recipientPubKey, senderPubKey *[chacha.KeySize]byt Crv: "X25519", X: base64.RawURLEncoding.EncodeToString(senderPubKey[:]), } - // senderJWKJSON is the payload to be encrypted with sharedSymKey + // senderJWKJSON is the payload to be encrypted with cek senderJWKJSON, err := json.Marshal(senderJWK) if err != nil { return "", err } - return c.encryptSenderJWK(kCipherEncoded, headersEncoded, senderJWKJSON, sharedSymKey[:]) + return c.encryptSenderJWK(kCipherEncoded, headersEncoded, senderJWKJSON, cek[:]) } func (c *Crypter) buildJWKHeaders(epk *[32]byte, kNonceEncoded, kTagEncoded string) (string, error) { @@ -89,7 +91,7 @@ func (c *Crypter) buildJWKHeaders(epk *[32]byte, kNonceEncoded, kTagEncoded stri return base64.RawURLEncoding.EncodeToString(headersJSON), nil } -func (c *Crypter) encryptSenderJWK(encKey, headers string, senderJWKJSON, sharedSymKey []byte) (string, error) { +func (c *Crypter) encryptSenderJWK(encKey, headers string, senderJWKJSON, cek []byte) (string, error) { // create a new nonce nonce := make([]byte, c.nonceSize) _, err := randReader.Read(nonce) @@ -97,15 +99,15 @@ func (c *Crypter) encryptSenderJWK(encKey, headers string, senderJWKJSON, shared return "", err } - // create a cipher for the given nonceSize and generated sharedSymKey above - crypter, err := createCipher(c.nonceSize, sharedSymKey) + // create a cipher for the given nonceSize and cek + cipher, err := createCipher(c.nonceSize, cek) if err != nil { return "", err } // encrypt the sender's encoded JWK using generated nonce and JWK encoded headers as AAD // the output is a []byte containing the cipherText + tag - symOutput := crypter.Seal(nil, nonce, senderJWKJSON, []byte(headers)) + symOutput := cipher.Seal(nil, nonce, senderJWKJSON, []byte(headers)) tagEncoded := extractTag(symOutput) cipherJWKEncoded := extractCipherText(symOutput) diff --git a/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk_test.go b/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk_test.go index 71eef36367..e8951d00bc 100644 --- a/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk_test.go +++ b/pkg/didcomm/crypto/jwe/authcrypt/encrypt_jwk_test.go @@ -13,17 +13,22 @@ import ( "github.com/stretchr/testify/require" chacha "golang.org/x/crypto/chacha20poly1305" + + mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" ) func TestNilEncryptSenderJwk(t *testing.T) { - crypter, err := New(XC20P) + mockWalletProvider, err := mockwallet.NewMockProvider() + require.NoError(t, err) + + crypter, err := New(mockWalletProvider, XC20P) require.NoError(t, err) spk, err := crypter.generateSPK(nil, nil) require.Error(t, err) require.Empty(t, spk) - s, l, m, err := crypter.encryptSymKey(nil, nil) + s, l, m, err := crypter.encryptCEK(nil, nil) require.Error(t, err) require.Empty(t, s) require.Empty(t, l) @@ -48,6 +53,20 @@ func TestNilEncryptSenderJwk(t *testing.T) { spk, err = crypter.generateSPK(someKey, someKey) require.Error(t, err) require.Empty(t, spk) + + r, err := crypter.encodeRecipient(someKey, someKey, someKey) + require.Error(t, err) + require.Empty(t, r) + + s, l, m, err = crypter.encryptCEK(someKey[:], someKey[:]) + require.Error(t, err) + require.Empty(t, s) + require.Empty(t, l) + require.Empty(t, m) + + pld, err := crypter.Encrypt([]byte(""), someKey[:], [][]byte{someKey[:]}) + require.Error(t, err) + require.Empty(t, pld) } // Reset random reader to original value diff --git a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go index 029b048669..eee13324b2 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt.go @@ -13,11 +13,11 @@ import ( "fmt" "io" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/agl/ed25519/extra25519" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/nacl/box" + + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) type privateEd25519 [ed25519.PrivateKeySize]byte @@ -85,8 +85,8 @@ type recipientHeader struct { IV string `json:"iv,omitempty"` } -func keyToEdKey(keyPair crypto.KeyPair) (*keyPairEd25519, error) { - if !crypto.IsKeyPairValid(keyPair) || +func keyToEdKey(keyPair wallet.KeyPair) (*keyPairEd25519, error) { + if !wallet.IsKeyPairValid(keyPair) || len(keyPair.Priv) != ed25519.PrivateKeySize || len(keyPair.Pub) != ed25519.PublicKeySize { return nil, fmt.Errorf( diff --git a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go index bafe4e8571..de82be6f43 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/authcrypt_test.go @@ -15,12 +15,12 @@ import ( insecurerand "math/rand" "testing" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/btcsuite/btcutil/base58" "github.com/stretchr/testify/require" "golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/sign" + + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // failReader wraps a Reader, used for testing different failure checks for encryption tests. @@ -78,7 +78,7 @@ func TestEncrypt(t *testing.T) { "Bxp2KpXeh6RgXXRVGRQUskT9qT35aSSz1JvdbMUcB2Yc", "2QqgiHtrUtDPpfoZG2C3Qi8a1MbLQuTZaaScu5LzQbUCkw5YnXngKLMJ8VuPgoN3Piqt1PBUACVd6uQRmtayZp2x") - senderKey := crypto.KeyPair{ + senderKey := wallet.KeyPair{ Priv: []byte{1, 2, 3, 4}, Pub: []byte{1, 2, 3, 4}, } @@ -376,7 +376,7 @@ func decryptComponentFailureTest( t *testing.T, protectedHeader, msg string, - recKey *crypto.KeyPair, + recKey *wallet.KeyPair, errString string) { fullMessage := `{"protected": "` + base64.URLEncoding.EncodeToString([]byte(protectedHeader)) + "\", " + msg recCrypter := New() @@ -539,7 +539,7 @@ func TestDecryptComponents(t *testing.T) { decryptComponentFailureTest(t, prot, `"iv": "oDZpVO648Po3UcoW", "ciphertext": "pLrFQ6dND0aB4saHjSklcNTDAvpFPmIvebCis7S6UupzhhPOHwhp6o97_EphsWbwqqHl0HTiT7W9kUqrvd8jcWgx5EATtkx5o3PSyHfsfm9jl0tmKsqu6VG0RML_OokZiFv76ZUZuGMrHKxkCHGytILhlpSwajg=", "tag": "6GigdWnW59aC9Y8jhy76rA=="}`, // nolint: lll - &crypto.KeyPair{}, + &wallet.KeyPair{}, "failed to decrypt, recipient keyPair not supported, it must have a 64 byte private key and 32 byte public key") }) } @@ -806,8 +806,8 @@ func randEdKeyPair(randReader io.Reader) (*keyPairEd25519, error) { return &keyPair, nil } -func randKeyPair(randReader io.Reader) (*crypto.KeyPair, error) { - keyPair := crypto.KeyPair{} +func randKeyPair(randReader io.Reader) (*wallet.KeyPair, error) { + keyPair := wallet.KeyPair{} pk, sk, err := sign.GenerateKey(randReader) if err != nil { return nil, err @@ -826,8 +826,8 @@ func randCurveKeyPair(randReader io.Reader) (*keyPairCurve25519, error) { return &keyPair, nil } -func getB58Key(pub, priv string) *crypto.KeyPair { - key := crypto.KeyPair{ +func getB58Key(pub, priv string) *wallet.KeyPair { + key := wallet.KeyPair{ Priv: base58.Decode(priv), Pub: base58.Decode(pub), } diff --git a/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go index 047481a691..d8a041f4c0 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/decrypt.go @@ -14,16 +14,16 @@ import ( "errors" "fmt" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/btcsuite/btcutil/base58" chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" + + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // Decrypt will decode the envelope using the legacy format // Using (X)Chacha20 encryption algorithm and Poly1035 authenticator -func (c *Crypter) Decrypt(envelope []byte, recipient crypto.KeyPair) ([]byte, error) { +func (c *Crypter) Decrypt(envelope []byte, recipient wallet.KeyPair) ([]byte, error) { edRecipient, err := keyToEdKey(recipient) if err != nil { return nil, fmt.Errorf("failed to decrypt, recipient %s", err.Error()) diff --git a/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go b/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go index 532f72978f..693f32edce 100644 --- a/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go +++ b/pkg/didcomm/crypto/legacy/authcrypt/encrypt.go @@ -13,17 +13,17 @@ import ( "errors" "fmt" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/btcsuite/btcutil/base58" chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" "golang.org/x/crypto/poly1305" + + "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // Encrypt will encode the payload argument // Using the protocol defined by Aries RFC 0019 -func (c *Crypter) Encrypt(payload []byte, sender crypto.KeyPair, recipientPubKeys [][]byte) ([]byte, error) { +func (c *Crypter) Encrypt(payload []byte, sender wallet.KeyPair, recipientPubKeys [][]byte) ([]byte, error) { var err error if len(recipientPubKeys) == 0 { diff --git a/pkg/didcomm/dispatcher/api.go b/pkg/didcomm/dispatcher/api.go index 6e641cf64f..4587b4563f 100644 --- a/pkg/didcomm/dispatcher/api.go +++ b/pkg/didcomm/dispatcher/api.go @@ -8,8 +8,8 @@ package dispatcher import ( "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" - "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // Service protocol service @@ -26,7 +26,7 @@ type Outbound interface { // Provider interface for outbound ctx type Provider interface { - PackWallet() wallet.Pack + Packager() envelope.Packager OutboundTransports() []transport.OutboundTransport } diff --git a/pkg/didcomm/dispatcher/outbound.go b/pkg/didcomm/dispatcher/outbound.go index dc4a86b85f..99b46fe315 100644 --- a/pkg/didcomm/dispatcher/outbound.go +++ b/pkg/didcomm/dispatcher/outbound.go @@ -11,19 +11,19 @@ import ( "fmt" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" - "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // OutboundDispatcher dispatch msgs to destination type OutboundDispatcher struct { outboundTransports []transport.OutboundTransport - wallet wallet.Pack + packager envelope.Packager } // NewOutbound return new dispatcher outbound instance func NewOutbound(prov Provider) *OutboundDispatcher { - return &OutboundDispatcher{outboundTransports: prov.OutboundTransports(), wallet: prov.PackWallet()} + return &OutboundDispatcher{outboundTransports: prov.OutboundTransports(), packager: prov.Packager()} } // Send msg @@ -36,8 +36,8 @@ func (o *OutboundDispatcher) Send(msg interface{}, senderVerKey string, des *ser if err != nil { return fmt.Errorf("failed marshal to bytes: %w", err) } - packedMsg, err := o.wallet.PackMessage( - &wallet.Envelope{Message: bytes, FromVerKey: senderVerKey, ToVerKeys: des.RecipientKeys}) + packedMsg, err := o.packager.PackMessage( + &envelope.Envelope{Message: bytes, FromVerKey: senderVerKey, ToVerKeys: des.RecipientKeys}) if err != nil { return fmt.Errorf("failed to pack msg: %w", err) } diff --git a/pkg/didcomm/dispatcher/outbound_test.go b/pkg/didcomm/dispatcher/outbound_test.go index 63ac9151bd..2b5dc40eba 100644 --- a/pkg/didcomm/dispatcher/outbound_test.go +++ b/pkg/didcomm/dispatcher/outbound_test.go @@ -13,21 +13,21 @@ import ( "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" mockdidcomm "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm" - mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" - "github.com/hyperledger/aries-framework-go/pkg/wallet" + mockpackager "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/envelope" ) func TestOutboundDispatcher_Send(t *testing.T) { t.Run("test success", func(t *testing.T) { - o := NewOutbound(&provider{walletValue: &mockwallet.CloseableWallet{}, + o := NewOutbound(&provider{packagerValue: &mockpackager.BasePackager{}, outboundTransportsValue: []transport.OutboundTransport{&mockdidcomm.MockOutboundTransport{AcceptValue: true}}}) require.NoError(t, o.Send("data", "", &service.Destination{ServiceEndpoint: "url"})) }) t.Run("test no outbound transport found", func(t *testing.T) { - o := NewOutbound(&provider{walletValue: &mockwallet.CloseableWallet{}, + o := NewOutbound(&provider{packagerValue: &mockpackager.BasePackager{}, outboundTransportsValue: []transport.OutboundTransport{&mockdidcomm.MockOutboundTransport{AcceptValue: false}}}) err := o.Send("data", "", &service.Destination{ServiceEndpoint: "url"}) require.Error(t, err) @@ -35,7 +35,7 @@ func TestOutboundDispatcher_Send(t *testing.T) { }) t.Run("test pack msg failure", func(t *testing.T) { - o := NewOutbound(&provider{walletValue: &mockwallet.CloseableWallet{PackErr: fmt.Errorf("pack error")}, + o := NewOutbound(&provider{packagerValue: &mockpackager.BasePackager{PackErr: fmt.Errorf("pack error")}, outboundTransportsValue: []transport.OutboundTransport{&mockdidcomm.MockOutboundTransport{AcceptValue: true}}}) err := o.Send("data", "", &service.Destination{ServiceEndpoint: "url"}) require.Error(t, err) @@ -43,7 +43,7 @@ func TestOutboundDispatcher_Send(t *testing.T) { }) t.Run("test outbound send failure", func(t *testing.T) { - o := NewOutbound(&provider{walletValue: &mockwallet.CloseableWallet{}, + o := NewOutbound(&provider{packagerValue: &mockpackager.BasePackager{}, outboundTransportsValue: []transport.OutboundTransport{ &mockdidcomm.MockOutboundTransport{AcceptValue: true, SendErr: fmt.Errorf("send error")}}}) err := o.Send("data", "", &service.Destination{ServiceEndpoint: "url"}) @@ -53,12 +53,12 @@ func TestOutboundDispatcher_Send(t *testing.T) { } type provider struct { - walletValue wallet.Pack + packagerValue envelope.Packager outboundTransportsValue []transport.OutboundTransport } -func (p *provider) PackWallet() wallet.Pack { - return p.walletValue +func (p *provider) Packager() envelope.Packager { + return p.packagerValue } func (p *provider) OutboundTransports() []transport.OutboundTransport { diff --git a/pkg/didcomm/envelope/api.go b/pkg/didcomm/envelope/api.go new file mode 100644 index 0000000000..62003c78b3 --- /dev/null +++ b/pkg/didcomm/envelope/api.go @@ -0,0 +1,51 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package envelope + +// Package envelope manages the handling of DIDComm raw messages in JWE compliant envelopes. +// The aim of this package is build these envelopes and parse them. They are mainly used as +// wire-level wrappers of 'payloads' used in DID Exchange flows. + +// PackagerCreator method to create new outbound dispatcher service +type PackagerCreator func(prov Provider) (Packager, error) + +// Packager provide methods to pack and unpack msg +type Packager interface { + // PackMessage Pack a message for one or more recipients. + // + // Args: + // + // envelope: The message to pack + // + // Returns: + // + // []byte: The packed message + // + // error: error + PackMessage(envelope *Envelope) ([]byte, error) + + // UnpackMessage Unpack a message. + // + // Args: + // + // encMessage: The encrypted message + // + // Returns: + // + // envelope: unpack message + // + // error: error + UnpackMessage(encMessage []byte) (*Envelope, error) +} + +// Envelope contain msg, FromVerKey and ToVerKeys +type Envelope struct { + Message []byte + FromVerKey string + // TODO add key type - issue #272 + ToVerKeys []string +} diff --git a/pkg/didcomm/envelope/package_test.go b/pkg/didcomm/envelope/package_test.go new file mode 100644 index 0000000000..3c3e085568 --- /dev/null +++ b/pkg/didcomm/envelope/package_test.go @@ -0,0 +1,188 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package envelope + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/btcsuite/btcutil/base58" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/nacl/box" + + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto/jwe/authcrypt" + "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm" + mprovider "github.com/hyperledger/aries-framework-go/pkg/internal/mock/provider" + mockstorage "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" + "github.com/hyperledger/aries-framework-go/pkg/storage" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +func TestBaseWalletInPackager_UnpackMessage(t *testing.T) { + t.Run("test failed to unmarshal encMessage", func(t *testing.T) { + w, err := wallet.New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: make(map[string][]byte), + }})) + require.NoError(t, err) + + mockedProviders := &mprovider.Provider{ + WalletValue: w, + } + crypter, err := authcrypt.New(mockedProviders, authcrypt.XC20P) + require.NoError(t, err) + + mockedProviders.CrypterValue = crypter + packager, err := New(mockedProviders) + require.NoError(t, err) + _, err = packager.UnpackMessage(nil) + require.Error(t, err) + require.EqualError(t, err, "failed from decrypt: failed to decrypt message: unexpected end of JSON input") + }) + + t.Run("test key not found", func(t *testing.T) { + w, err := wallet.New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: make(map[string][]byte), + }})) + require.NoError(t, err) + + mockedProviders := &mprovider.Provider{ + WalletValue: w, + } + crypter, err := authcrypt.New(mockedProviders, authcrypt.XC20P) + require.NoError(t, err) + + // use a real crypter with a mocked wallet to validate pack/unpack + mockedProviders.CrypterValue = crypter + packager, err := New(mockedProviders) + require.NoError(t, err) + + // fromKey is stored in the wallet + base58FromVerKey, err := w.CreateEncryptionKey() + require.NoError(t, err) + + // toKey is not stored in the wallet + pub2, _, err := box.GenerateKey(rand.Reader) + require.NoError(t, err) + + // PackMessage should pass without toPrivKey (only fromKey is required in the wallet) + packMsg, err := packager.PackMessage(&Envelope{Message: []byte("msg1"), + FromVerKey: base58FromVerKey, + ToVerKeys: []string{base58.Encode(pub2[:])}}) + require.NoError(t, err) + + // UnpackMessage requires toPrivKey in the wallet (should fail to unpack/decrypt message) + _, err = packager.UnpackMessage(packMsg) + require.Error(t, err) + require.EqualError(t, err, "failed from decrypt: failed to decrypt message: key not found") + }) + + t.Run("test Pack/Unpack fails", func(t *testing.T) { + w, err := wallet.New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: make(map[string][]byte), + }})) + require.NoError(t, err) + + decryptValue := func(envelope []byte) ([]byte, error) { + return nil, fmt.Errorf("decrypt error") + } + + mockedProviders := &mprovider.Provider{ + WalletValue: w, + } + + // use a mocked crypter with a mocked wallet to validate pack/unpack + e := func(payload []byte, senderPubKey []byte, recipientsKeys [][]byte) (bytes []byte, e error) { + crypter, e := authcrypt.New(mockedProviders, authcrypt.XC20P) + require.NoError(t, e) + return crypter.Encrypt(payload, senderPubKey, recipientsKeys) + } + mockCrypter := &didcomm.MockAuthCrypt{DecryptValue: decryptValue, + EncryptValue: e} + + mockedProviders.CrypterValue = mockCrypter + + packager, err := New(mockedProviders) + require.NoError(t, err) + + base58FromVerKey, err := w.CreateEncryptionKey() + require.NoError(t, err) + + base58ToVerKey, err := w.CreateEncryptionKey() + require.NoError(t, err) + + // try pack with nil envelope - should fail + packMsg, err := packager.PackMessage(nil) + require.EqualError(t, err, "envelope argument is nil") + require.Empty(t, packMsg) + + // now try to pack with non empty envelope - should pass + packMsg, err = packager.PackMessage(&Envelope{Message: []byte("msg1"), + FromVerKey: base58FromVerKey, + ToVerKeys: []string{base58ToVerKey}}) + require.NoError(t, err) + require.NotEmpty(t, packMsg) + + // now try unpack - should fail since we mocked the crypter's Decrypt value to return "decrypt error" + // see 'decryptValue' above + _, err = packager.UnpackMessage(packMsg) + require.Error(t, err) + require.Contains(t, err.Error(), "decrypt error") + }) + + t.Run("test Pack/Unpack success", func(t *testing.T) { + // create a mock wallet with storage as a map + w, err := wallet.New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: map[string][]byte{}}})) + require.NoError(t, err) + mockedProviders := &mprovider.Provider{ + WalletValue: w, + } + // create a real crypter (no mocking here) + crypter, err := authcrypt.New(mockedProviders, authcrypt.XC20P) + require.NoError(t, err) + mockedProviders.CrypterValue = crypter + + // now create a new packager with the above provider context + packager, err := New(mockedProviders) + require.NoError(t, err) + + base58FromVerKey, err := w.CreateEncryptionKey() + require.NoError(t, err) + + base58ToVerKey, err := w.CreateEncryptionKey() + require.NoError(t, err) + + // pack an non empty envelope - should pass + packMsg, err := packager.PackMessage(&Envelope{Message: []byte("msg1"), + FromVerKey: base58FromVerKey, + ToVerKeys: []string{base58ToVerKey}}) + require.NoError(t, err) + + // unpack the packed message above - should pass and match the same payload (msg1) + unpackedMsg, err := packager.UnpackMessage(packMsg) + require.NoError(t, err) + require.Equal(t, unpackedMsg.Message, []byte("msg1")) + }) +} + +func newMockWalletProvider(storagePvdr *mockstorage.MockStoreProvider) *mockProvider { + return &mockProvider{storagePvdr} +} + +// mockProvider mocks provider for wallet +type mockProvider struct { + storage *mockstorage.MockStoreProvider +} + +func (m *mockProvider) StorageProvider() storage.Provider { + return m.storage +} + +func (m *mockProvider) InboundTransportEndpoint() string { + return "sample-endpoint.com" +} diff --git a/pkg/didcomm/envelope/packager.go b/pkg/didcomm/envelope/packager.go new file mode 100644 index 0000000000..df5b27fa17 --- /dev/null +++ b/pkg/didcomm/envelope/packager.go @@ -0,0 +1,70 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package envelope + +import ( + "errors" + "fmt" + + "github.com/btcsuite/btcutil/base58" + + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" +) + +// Provider contains dependencies for the base packager and is typically created by using aries.Context() +type Provider interface { + Crypter() crypto.Crypter +} + +// BasePackager is the basic implementation of Packager +type BasePackager struct { + crypter crypto.Crypter +} + +// New return new instance of wallet implementation +func New(ctx Provider) (*BasePackager, error) { + crypter := ctx.Crypter() + + return &BasePackager{crypter: crypter}, nil +} + +// PackMessage Pack a message for one or more recipients. +func (p *BasePackager) PackMessage(envelope *Envelope) ([]byte, error) { + if envelope == nil { + return nil, errors.New("envelope argument is nil") + } + + var recipients [][]byte + for _, verKey := range envelope.ToVerKeys { + // TODO It is possible to have different key schemes in an interop situation + // there is no guarantee that each recipient is using the same key types + // for now this package uses Curve25519 encryption keys. Other key schemes should have their own + // crypter implementations. + // Change the key types to use Ed25519 signing keys and convert them to encryption keys + // ref: https://github.com/hyperledger/aries-framework-go/issues/454 + // decode base58 ver key + verKeyBytes := base58.Decode(verKey) + // create 32 byte key + recipients = append(recipients, verKeyBytes) + } + // encrypt message + bytes, err := p.crypter.Encrypt(envelope.Message, base58.Decode(envelope.FromVerKey), recipients) + if err != nil { + return nil, fmt.Errorf("failed from encrypt: %w", err) + } + return bytes, nil +} + +// UnpackMessage Unpack a message. +func (p *BasePackager) UnpackMessage(encMessage []byte) (*Envelope, error) { + bytes, err := p.crypter.Decrypt(encMessage) + if err != nil { + return nil, fmt.Errorf("failed from decrypt: %w", err) + } + // TODO extract fromVerKey and toVerKey from crypter.Decrypt() call above and set them here + return &Envelope{Message: bytes}, nil +} diff --git a/pkg/didcomm/transport/http/inbound.go b/pkg/didcomm/transport/http/inbound.go index f34aeacb8d..b312bedcf6 100644 --- a/pkg/didcomm/transport/http/inbound.go +++ b/pkg/didcomm/transport/http/inbound.go @@ -13,9 +13,8 @@ import ( "io/ioutil" "net/http" - "github.com/hyperledger/aries-framework-go/pkg/wallet" - "github.com/hyperledger/aries-framework-go/pkg/common/log" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" ) @@ -24,7 +23,7 @@ var logger = log.New("aries-framework/transport") // provider contains dependencies for the HTTP Handler creation and is typically created by using aries.Context() type provider interface { InboundMessageHandler() transport.InboundMessageHandler - PackWallet() wallet.Pack + Packager() envelope.Packager } // NewInboundHandler will create a new handler to enforce Did-Comm HTTP transport specs @@ -58,7 +57,7 @@ func processPOSTRequest(w http.ResponseWriter, r *http.Request, prov transport.I http.Error(w, "Failed to read payload", http.StatusInternalServerError) return } - unpackMsg, err := prov.PackWallet().UnpackMessage(body) + unpackMsg, err := prov.Packager().UnpackMessage(body) if err != nil { logger.Errorf("failed to unpack msg: %s - returning Code: %d", err, http.StatusInternalServerError) http.Error(w, "failed to unpack msg", http.StatusInternalServerError) diff --git a/pkg/didcomm/transport/http/inbound_test.go b/pkg/didcomm/transport/http/inbound_test.go index 8ff0972ee5..ba486c695e 100644 --- a/pkg/didcomm/transport/http/inbound_test.go +++ b/pkg/didcomm/transport/http/inbound_test.go @@ -20,13 +20,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" - mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" - "github.com/hyperledger/aries-framework-go/pkg/wallet" + mockpackager "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/envelope" ) type mockProvider struct { - packWalletValue wallet.Pack + packagerValue envelope.Packager } func (p *mockProvider) InboundMessageHandler() transport.InboundMessageHandler { @@ -36,8 +36,8 @@ func (p *mockProvider) InboundMessageHandler() transport.InboundMessageHandler { } } -func (p *mockProvider) PackWallet() wallet.Pack { - return p.packWalletValue +func (p *mockProvider) Packager() envelope.Packager { + return p.packagerValue } func TestInboundHandler(t *testing.T) { @@ -45,9 +45,9 @@ func TestInboundHandler(t *testing.T) { inHandler, err := NewInboundHandler(nil) require.Error(t, err) require.Nil(t, inHandler) - mockWallet := &mockwallet.CloseableWallet{UnpackValue: &wallet.Envelope{Message: []byte("data")}} + mockPackager := &mockpackager.BasePackager{UnpackValue: &envelope.Envelope{Message: []byte("data")}} // now create a valid inboundHandler to continue testing.. - inHandler, err = NewInboundHandler(&mockProvider{packWalletValue: mockWallet}) + inHandler, err = NewInboundHandler(&mockProvider{packagerValue: mockPackager}) require.NoError(t, err) require.NotNil(t, inHandler) @@ -112,8 +112,8 @@ func TestInboundHandler(t *testing.T) { require.Equal(t, http.StatusAccepted, resp.StatusCode) // test unpack error - mockWallet.UnpackValue = nil - mockWallet.UnpackErr = fmt.Errorf("unpack error") + mockPackager.UnpackValue = nil + mockPackager.UnpackErr = fmt.Errorf("unpack error") resp, err = client.Post(serverURL+"/", commContentType, bytes.NewBuffer([]byte(data))) require.NoError(t, err) require.NotNil(t, resp) @@ -135,8 +135,8 @@ func TestInboundTransport(t *testing.T) { inbound, err := NewInbound(":26602") require.NoError(t, err) require.NotEmpty(t, inbound) - packWalletValue := &mockwallet.CloseableWallet{UnpackValue: &wallet.Envelope{Message: []byte("data")}} - err = inbound.Start(&mockProvider{packWalletValue: packWalletValue}) + mockPackager := &mockpackager.BasePackager{UnpackValue: &envelope.Envelope{Message: []byte("data")}} + err = inbound.Start(&mockProvider{packagerValue: mockPackager}) require.NoError(t, err) err = inbound.Stop() @@ -165,8 +165,8 @@ func TestInboundTransport(t *testing.T) { require.NotEmpty(t, inbound) // start server - packWalletValue := &mockwallet.CloseableWallet{UnpackValue: &wallet.Envelope{Message: []byte("data")}} - err = inbound.Start(&mockProvider{packWalletValue: packWalletValue}) + mockPackager := &mockpackager.BasePackager{UnpackValue: &envelope.Envelope{Message: []byte("data")}} + err = inbound.Start(&mockProvider{packagerValue: mockPackager}) require.NoError(t, err) require.NoError(t, listenFor("localhost:26604", time.Second)) // invoke a endpoint diff --git a/pkg/didcomm/transport/transport_interface.go b/pkg/didcomm/transport/transport_interface.go index c419c1cc57..78b2ba5f9f 100644 --- a/pkg/didcomm/transport/transport_interface.go +++ b/pkg/didcomm/transport/transport_interface.go @@ -6,7 +6,9 @@ SPDX-License-Identifier: Apache-2.0 package transport -import "github.com/hyperledger/aries-framework-go/pkg/wallet" +import ( + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" +) // OutboundTransport interface definition for transport layer // This is the client side of the agent @@ -25,7 +27,7 @@ type InboundMessageHandler func(message []byte) error // It is typically created by using aries.Context(). type InboundProvider interface { InboundMessageHandler() InboundMessageHandler - PackWallet() wallet.Pack + Packager() envelope.Packager } // InboundTransport interface definition for inbound transport layer diff --git a/pkg/framework/aries/api/protocol.go b/pkg/framework/aries/api/protocol.go index b89af26a4b..c77e4fc2c8 100644 --- a/pkg/framework/aries/api/protocol.go +++ b/pkg/framework/aries/api/protocol.go @@ -9,7 +9,9 @@ package api import ( "errors" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" "github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/storage" "github.com/hyperledger/aries-framework-go/pkg/wallet" ) @@ -23,6 +25,8 @@ type Provider interface { Service(id string) (interface{}, error) StorageProvider() storage.Provider CryptoWallet() wallet.Crypto + Crypter() crypto.Crypter + Packager() envelope.Packager InboundTransportEndpoint() string DIDWallet() wallet.DIDCreator } diff --git a/pkg/framework/aries/api/wallet.go b/pkg/framework/aries/api/wallet.go index 99e55cf22a..5f619a1b61 100644 --- a/pkg/framework/aries/api/wallet.go +++ b/pkg/framework/aries/api/wallet.go @@ -16,7 +16,6 @@ import ( type CloseableWallet interface { io.Closer wallet.Crypto - wallet.Pack wallet.DIDCreator } diff --git a/pkg/framework/aries/default.go b/pkg/framework/aries/default.go index 64ac25f9e2..3c0d689531 100644 --- a/pkg/framework/aries/default.go +++ b/pkg/framework/aries/default.go @@ -10,7 +10,10 @@ import ( "fmt" "github.com/hyperledger/aries-framework-go/pkg/common/did" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto/jwe/authcrypt" "github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/didexchange" didcommtrans "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport/http" @@ -95,13 +98,7 @@ func defFrameworkOpts(frameworkOpts *Aries) error { frameworkOpts.didResolver = resolver } - if frameworkOpts.walletCreator == nil { - frameworkOpts.walletCreator = func(provider api.Provider) (api.CloseableWallet, error) { - return wallet.New(provider) - } - } - - setDefaultOutboundDispatcher(frameworkOpts) + setAdditionalDefaultOpts(frameworkOpts) newExchangeSvc := func(prv api.Provider) (dispatcher.Service, error) { return didexchange.New(did.NewLocalDIDCreator(prv), prv) @@ -111,7 +108,25 @@ func defFrameworkOpts(frameworkOpts *Aries) error { return nil } -func setDefaultOutboundDispatcher(frameworkOpts *Aries) { +func setAdditionalDefaultOpts(frameworkOpts *Aries) { + if frameworkOpts.walletCreator == nil { + frameworkOpts.walletCreator = func(provider api.Provider) (api.CloseableWallet, error) { + return wallet.New(provider) + } + } + + if frameworkOpts.crypterCreator == nil { + frameworkOpts.crypterCreator = func(provider crypto.Provider) (crypto.Crypter, error) { + return authcrypt.New(provider, authcrypt.XC20P) + } + } + + if frameworkOpts.packagerCreator == nil { + frameworkOpts.packagerCreator = func(provider envelope.Provider) (envelope.Packager, error) { + return envelope.New(provider) + } + } + if frameworkOpts.outboundDispatcherCreator == nil { frameworkOpts.outboundDispatcherCreator = func(prv dispatcher.Provider) (dispatcher.Outbound, error) { return dispatcher.NewOutbound(prv), nil diff --git a/pkg/framework/aries/framework.go b/pkg/framework/aries/framework.go index e1a1ec6965..96cde5cf3a 100644 --- a/pkg/framework/aries/framework.go +++ b/pkg/framework/aries/framework.go @@ -9,7 +9,9 @@ package aries import ( "fmt" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" "github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api" @@ -35,6 +37,10 @@ type Aries struct { wallet api.CloseableWallet outboundDispatcherCreator dispatcher.OutboundCreator outboundDispatcher dispatcher.Outbound + packagerCreator envelope.PackagerCreator + packager envelope.Packager + crypterCreator crypto.CrypterCreator + crypter crypto.Crypter } // Option configures the framework. @@ -73,6 +79,12 @@ func New(opts ...Option) (*Aries, error) { return nil, e } + // create create crypter and packager (must be done after Wallet) + err = createCrypterAndPackager(frameworkOpts) + if err != nil { + return nil, err + } + // Create outbound dispatcher err = createOutboundDispatcher(frameworkOpts) if err != nil { @@ -150,6 +162,22 @@ func WithWallet(w api.WalletCreator) Option { } } +// WithCrypter injects a crypter service to the Aries framework +func WithCrypter(c crypto.CrypterCreator) Option { + return func(opts *Aries) error { + opts.crypterCreator = c + return nil + } +} + +// WithPackager injects a WithPackager service to the Aries framework +func WithPackager(p envelope.PackagerCreator) Option { + return func(opts *Aries) error { + opts.packagerCreator = p + return nil + } +} + // DIDResolver returns the framework configured DID Resolver. func (a *Aries) DIDResolver() DIDResolver { return a.didResolver @@ -167,6 +195,8 @@ func (a *Aries) Context() (*context.Provider, error) { // TODO configure inbound external endpoints context.WithWallet(a.wallet), context.WithInboundTransportEndpoint(a.inboundTransport.Endpoint()), context.WithStorageProvider(a.storeProvider), + context.WithCrypter(a.crypter), + context.WithPackager(a.packager), ) } @@ -211,7 +241,9 @@ func createOutboundDispatcher(frameworkOpts *Aries) error { if err != nil { return fmt.Errorf("outbound transport initialization failed: %w", err) } - ctx, err := context.New(context.WithWallet(frameworkOpts.wallet), context.WithOutboundTransport(ot)) + ctx, err := context.New(context.WithWallet(frameworkOpts.wallet), + context.WithOutboundTransport(ot), + context.WithPackager(frameworkOpts.packager)) if err != nil { return fmt.Errorf("context creation failed: %w", err) } @@ -224,6 +256,7 @@ func createOutboundDispatcher(frameworkOpts *Aries) error { func startInboundTransport(frameworkOpts *Aries) error { ctx, err := context.New(context.WithWallet(frameworkOpts.wallet), + context.WithPackager(frameworkOpts.packager), context.WithInboundTransportEndpoint(frameworkOpts.inboundTransport.Endpoint()), context.WithProtocolServices(frameworkOpts.services...)) if err != nil { @@ -238,7 +271,9 @@ func startInboundTransport(frameworkOpts *Aries) error { func loadServices(frameworkOpts *Aries) error { ctx, err := context.New(context.WithOutboundDispatcher(frameworkOpts.outboundDispatcher), - context.WithWallet(frameworkOpts.wallet), context.WithStorageProvider(frameworkOpts.storeProvider)) + context.WithStorageProvider(frameworkOpts.storeProvider), + context.WithWallet(frameworkOpts.wallet), + context.WithPackager(frameworkOpts.packager)) if err != nil { return fmt.Errorf("create context failed: %w", err) } @@ -251,3 +286,26 @@ func loadServices(frameworkOpts *Aries) error { } return nil } + +func createCrypterAndPackager(frameworkOpts *Aries) error { + ctx, err := context.New(context.WithWallet(frameworkOpts.wallet)) + if err != nil { + return fmt.Errorf("create crypter context failed: %w", err) + } + + frameworkOpts.crypter, err = frameworkOpts.crypterCreator(ctx) + if err != nil { + return fmt.Errorf("create crypter failed: %w", err) + } + + ctx, err = context.New(context.WithCrypter(frameworkOpts.crypter)) + if err != nil { + return fmt.Errorf("create packager context failed: %w", err) + } + + frameworkOpts.packager, err = frameworkOpts.packagerCreator(ctx) + if err != nil { + return fmt.Errorf("create packager failed: %w", err) + } + return nil +} diff --git a/pkg/framework/aries/framework_test.go b/pkg/framework/aries/framework_test.go index 505d462211..4cfcd4d03f 100644 --- a/pkg/framework/aries/framework_test.go +++ b/pkg/framework/aries/framework_test.go @@ -19,7 +19,9 @@ import ( "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" "github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/didexchange" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" "github.com/hyperledger/aries-framework-go/pkg/didmethod/peer" @@ -28,6 +30,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/framework/didresolver" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm" mockdispatcher "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/dispatcher" + mockenvelope "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/protocol" mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" "github.com/hyperledger/aries-framework-go/pkg/storage/leveldb" @@ -82,10 +85,17 @@ func TestFramework(t *testing.T) { }() serverURL := fmt.Sprintf("http://localhost:%d", port) - aries, err := New(WithInboundTransport( - &mockInboundTransport{}), WithWallet(func(ctx api.Provider) (api.CloseableWallet, error) { - return &mockwallet.CloseableWallet{SignMessageValue: []byte("mockValue")}, nil - })) + aries, err := New( + WithInboundTransport(&mockInboundTransport{}), + WithWallet(func(ctx api.Provider) (api.CloseableWallet, error) { + return &mockwallet.CloseableWallet{SignMessageValue: []byte("mockValue")}, nil + }), + WithCrypter(func(ctx crypto.Provider) (crypto.Crypter, error) { + return &didcomm.MockAuthCrypt{EncryptValue: nil}, nil + }), + WithPackager(func(ctx envelope.Provider) (envelope.Packager, error) { + return &mockenvelope.BasePackager{PackValue: []byte("mockPackValue")}, nil + })) require.NoError(t, err) // context diff --git a/pkg/framework/context/context.go b/pkg/framework/context/context.go index b022519195..37bdb3e614 100644 --- a/pkg/framework/context/context.go +++ b/pkg/framework/context/context.go @@ -11,7 +11,9 @@ import ( "fmt" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" "github.com/hyperledger/aries-framework-go/pkg/didcomm/dispatcher" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/didcomm/transport" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api" "github.com/hyperledger/aries-framework-go/pkg/storage" @@ -24,6 +26,8 @@ type Provider struct { services []dispatcher.Service storeProvider storage.Provider wallet wallet.Wallet + packager envelope.Packager + crypter crypto.Crypter inboundTransportEndpoint string outboundTransport transport.OutboundTransport } @@ -66,9 +70,14 @@ func (p *Provider) CryptoWallet() wallet.Crypto { return p.wallet } -// PackWallet returns the pack wallet service -func (p *Provider) PackWallet() wallet.Pack { - return p.wallet +// Packager returns the packager service +func (p *Provider) Packager() envelope.Packager { + return p.packager +} + +// Crypter returns the crypter service to be used by the packager +func (p *Provider) Crypter() crypto.Crypter { + return p.crypter } // DIDWallet returns the pack wallet service @@ -158,3 +167,19 @@ func WithStorageProvider(s storage.Provider) ProviderOption { return nil } } + +// WithPackager injects a packager into the context +func WithPackager(p envelope.Packager) ProviderOption { + return func(opts *Provider) error { + opts.packager = p + return nil + } +} + +// WithCrypter injects a crypter into the context +func WithCrypter(p crypto.Crypter) ProviderOption { + return func(opts *Provider) error { + opts.crypter = p + return nil + } +} diff --git a/pkg/framework/context/context_test.go b/pkg/framework/context/context_test.go index 355d5ffc2b..b3628c2dc0 100644 --- a/pkg/framework/context/context_test.go +++ b/pkg/framework/context/context_test.go @@ -15,13 +15,14 @@ import ( "github.com/stretchr/testify/require" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/doc/did" mockdidcomm "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm" mockdispatcher "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/dispatcher" + mockenvelope "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm/protocol" "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" mockwallet "github.com/hyperledger/aries-framework-go/pkg/internal/mock/wallet" - "github.com/hyperledger/aries-framework-go/pkg/wallet" ) func TestNewProvider(t *testing.T) { @@ -127,24 +128,29 @@ func TestNewProvider(t *testing.T) { require.Contains(t, err.Error(), "error handling the message") }) - t.Run("test new with wallet service", func(t *testing.T) { - prov, err := New(WithWallet(&mockwallet.CloseableWallet{ - SignMessageValue: []byte("mockValue"), PackValue: []byte("data")})) + t.Run("test new with wallet and packager service", func(t *testing.T) { + prov, err := New( + WithWallet(&mockwallet.CloseableWallet{SignMessageValue: []byte("mockValue")}), + WithPackager(&mockenvelope.BasePackager{PackValue: []byte("data")}), + ) require.NoError(t, err) v, err := prov.CryptoWallet().SignMessage(nil, "") require.NoError(t, err) require.Equal(t, []byte("mockValue"), v) - v, err = prov.PackWallet().PackMessage(&wallet.Envelope{}) + v, err = prov.packager.PackMessage(&envelope.Envelope{}) require.NoError(t, err) require.Equal(t, []byte("data"), v) }) - t.Run("test new with did wallet service", func(t *testing.T) { - prov, err := New(WithWallet(&mockwallet.CloseableWallet{SignMessageValue: []byte("mockValue"), - PackValue: []byte("data"), - MockDID: &did.Doc{ - Context: []string{"https://w3id.org/did/v1"}, - ID: "did:example:123456789abcdefghi#inbox"}}), + t.Run("test new with did wallet packager service", func(t *testing.T) { + prov, err := New( + WithWallet( + &mockwallet.CloseableWallet{SignMessageValue: []byte("mockValue"), + MockDID: &did.Doc{ + Context: []string{"https://w3id.org/did/v1"}, + ID: "did:example:123456789abcdefghi#inbox"}}, + ), + WithPackager(&mockenvelope.BasePackager{PackValue: []byte("data")}), ) require.NoError(t, err) v, err := prov.CryptoWallet().SignMessage(nil, "") diff --git a/pkg/internal/mock/didcomm/envelope/mock_packager.go b/pkg/internal/mock/didcomm/envelope/mock_packager.go new file mode 100644 index 0000000000..d0b6bf252e --- /dev/null +++ b/pkg/internal/mock/didcomm/envelope/mock_packager.go @@ -0,0 +1,27 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package envelope + +import baseenv "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" + +// BasePackager represents a mocked Packager +type BasePackager struct { + PackValue []byte + PackErr error + UnpackValue *baseenv.Envelope + UnpackErr error +} + +// PackMessage Pack a message for one or more recipients. +func (m *BasePackager) PackMessage(envelope *baseenv.Envelope) ([]byte, error) { + return m.PackValue, m.PackErr +} + +// UnpackMessage Unpack a message. +func (m *BasePackager) UnpackMessage(encMessage []byte) (*baseenv.Envelope, error) { + return m.UnpackValue, m.UnpackErr +} diff --git a/pkg/internal/mock/didcomm/mock_authcrypt.go b/pkg/internal/mock/didcomm/mock_authcrypt.go index 0122bcc685..fc4ca2eae8 100644 --- a/pkg/internal/mock/didcomm/mock_authcrypt.go +++ b/pkg/internal/mock/didcomm/mock_authcrypt.go @@ -6,23 +6,19 @@ SPDX-License-Identifier: Apache-2.0 package didcomm -import ( - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" -) - // MockAuthCrypt mock auth crypt type MockAuthCrypt struct { - EncryptValue func(payload []byte, sender crypto.KeyPair, recipients [][]byte) ([]byte, error) - DecryptValue func(envelope []byte, recipientKeyPair crypto.KeyPair) ([]byte, error) + EncryptValue func(payload, senderPubKey []byte, recipients [][]byte) ([]byte, error) + DecryptValue func(envelope []byte) ([]byte, error) } // Encrypt mock encrypt -func (m *MockAuthCrypt) Encrypt(payload []byte, sender crypto.KeyPair, +func (m *MockAuthCrypt) Encrypt(payload, senderPubKey []byte, recipients [][]byte) ([]byte, error) { - return m.EncryptValue(payload, sender, recipients) + return m.EncryptValue(payload, senderPubKey, recipients) } // Decrypt mock decrypt -func (m *MockAuthCrypt) Decrypt(envelope []byte, recipientKeyPair crypto.KeyPair) ([]byte, error) { - return m.DecryptValue(envelope, recipientKeyPair) +func (m *MockAuthCrypt) Decrypt(envelope []byte) ([]byte, error) { + return m.DecryptValue(envelope) } diff --git a/pkg/internal/mock/provider/mock_provider.go b/pkg/internal/mock/provider/mock_provider.go index b962d73d1f..1f414463c1 100644 --- a/pkg/internal/mock/provider/mock_provider.go +++ b/pkg/internal/mock/provider/mock_provider.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package provider import ( + "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" "github.com/hyperledger/aries-framework-go/pkg/storage" "github.com/hyperledger/aries-framework-go/pkg/wallet" ) @@ -18,6 +19,7 @@ type Provider struct { WalletValue wallet.Crypto InboundEndpointValue string StorageProviderValue storage.Provider + CrypterValue crypto.Crypter } // Service return service @@ -39,3 +41,8 @@ func (p *Provider) InboundTransportEndpoint() string { func (p *Provider) StorageProvider() storage.Provider { return p.StorageProviderValue } + +// Crypter returns the crypter service +func (p *Provider) Crypter() crypto.Crypter { + return p.CrypterValue +} diff --git a/pkg/internal/mock/wallet/mock_wallet.go b/pkg/internal/mock/wallet/mock_wallet.go index b4dec72f32..bc9459a1f8 100644 --- a/pkg/internal/mock/wallet/mock_wallet.go +++ b/pkg/internal/mock/wallet/mock_wallet.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package wallet import ( + "github.com/hyperledger/aries-framework-go/pkg/didcomm/envelope" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/wallet" ) @@ -21,7 +22,7 @@ type CloseableWallet struct { SignMessageErr error PackValue []byte PackErr error - UnpackValue *wallet.Envelope + UnpackValue *envelope.Envelope UnpackErr error MockDID *did.Doc } @@ -46,19 +47,16 @@ func (m *CloseableWallet) SignMessage(message []byte, fromVerKey string) ([]byte return m.SignMessageValue, m.SignMessageErr } -// DecryptMessage decrypt message -func (m *CloseableWallet) DecryptMessage(encMessage []byte, toVerKey string) ([]byte, string, error) { - return nil, "", nil +// DeriveKEK derives a key encryption key from two keys +// mocked to return empty derived KEK +func (m *CloseableWallet) DeriveKEK(alg, apu, fromKey, toPubKey []byte) ([]byte, error) { // nolint:lll + return []byte(""), nil } -// PackMessage Pack a message for one or more recipients. -func (m *CloseableWallet) PackMessage(envelope *wallet.Envelope) ([]byte, error) { - return m.PackValue, m.PackErr -} - -// UnpackMessage Unpack a message. -func (m *CloseableWallet) UnpackMessage(encMessage []byte) (*wallet.Envelope, error) { - return m.UnpackValue, m.UnpackErr +// FindVerKey returns the index of candidateKeys that has the first match in the wallet +// mocked to return not found key +func (m *CloseableWallet) FindVerKey(candidateKeys []string) (int, error) { + return -1, wallet.ErrKeyNotFound } // CreateDID returns new DID Document diff --git a/pkg/internal/mock/wallet/mock_wallet_provider.go b/pkg/internal/mock/wallet/mock_wallet_provider.go new file mode 100644 index 0000000000..d0cd008751 --- /dev/null +++ b/pkg/internal/mock/wallet/mock_wallet_provider.go @@ -0,0 +1,59 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package wallet + +import ( + "encoding/json" + + "github.com/btcsuite/btcutil/base58" + + mockprovider "github.com/hyperledger/aries-framework-go/pkg/internal/mock/provider" + mockstorage "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" + "github.com/hyperledger/aries-framework-go/pkg/storage" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +// NewMockProvider will create a new mock Wallet Provider that builds a wallet with the keypairs list kp +func NewMockProvider(kp ...wallet.KeyPair) (*mockprovider.Provider, error) { + store := make(map[string][]byte) + for _, k := range kp { + marshalledKP, err := json.Marshal(k) + if err != nil { + return nil, err + } + store[base58.Encode(k.Pub)] = marshalledKP + } + + mProvider := &mockProvider{&mockstorage.MockStoreProvider{ + Store: &mockstorage.MockStore{ + Store: store, + }}} + w, err := wallet.New(mProvider) + if err != nil { + return nil, err + } + + mockWalletProvider := &mockprovider.Provider{ + WalletValue: w, + } + return mockWalletProvider, nil +} + +// mockProvider mocks provider for wallet +type mockProvider struct { + storage *mockstorage.MockStoreProvider +} + +// StorageProvider() returns the mock storage provider of this mock wallet provider +func (m *mockProvider) StorageProvider() storage.Provider { + return m.storage +} + +// InboundTransportEndpoint returns a mock inbound endpoint +func (m *mockProvider) InboundTransportEndpoint() string { + return "sample-endpoint.com" +} diff --git a/pkg/wallet/api.go b/pkg/wallet/api.go index 573c57160d..40b732a592 100644 --- a/pkg/wallet/api.go +++ b/pkg/wallet/api.go @@ -15,7 +15,6 @@ import ( // Wallet interface type Wallet interface { Crypto - Pack DIDCreator } @@ -55,49 +54,24 @@ type Crypto interface { // error: error SignMessage(message []byte, fromVerKey string) ([]byte, error) - // DecryptMessage decrypt message + // DeriveKEK will derive an ephemeral symmetric key (kek) using a private from key fetched from + // from the wallet corresponding to fromPubKey and derived with toPubKey. // - // Args: - // - // encMessage: The encrypted message content - // - // toVerKey:The verification key of the recipient. - // - // []byte: Decrypted message content - // - // string: The sender verification key - // - // error: error - DecryptMessage(encMessage []byte, toVerKey string) ([]byte, string, error) -} - -// Pack provide methods to pack and unpack msg -type Pack interface { - // PackMessage Pack a message for one or more recipients. - // - // Args: - // - // envelope: The message to pack - // - // Returns: - // - // []byte: The packed message + // This function assumes both fromPubKey and toPubKey to be on curve25519. // - // error: error - PackMessage(envelope *Envelope) ([]byte, error) + // returns: + // kek []byte the key encryption key used to decrypt a cek (a shared key) + // error in case of errors + DeriveKEK(alg, apu, fromPuKey, toPubKey []byte) ([]byte, error) - // UnpackMessage Unpack a message. + // FindVerKey will search the wallet to find stored keys that match any of candidateKeys and + // return the index of the first match + // returns: + // int index of candidateKeys that matches the first key found in the wallet + // error in case of errors (including ErrKeyNotFound) // - // Args: - // - // encMessage: The encrypted message - // - // Returns: - // - // envelope: unpack message - // - // error: error - UnpackMessage(encMessage []byte) (*Envelope, error) + // in case of error, the index will be -1 + FindVerKey(candidateKeys []string) (int, error) } // DIDCreator provide method to create DID document @@ -118,14 +92,6 @@ type DIDCreator interface { CreateDID(method string, opts ...DocOpts) (*did.Doc, error) } -// Envelope contain msg,FromVerKey and ToVerKeys -type Envelope struct { - Message []byte - FromVerKey string - // TODO add key type - issue #272 - ToVerKeys []string -} - // createDIDOpts holds the options for creating DID type createDIDOpts struct { serviceType string @@ -143,3 +109,6 @@ func WithServiceType(serviceType string) DocOpts { // ErrKeyNotFound is returned when key not found var ErrKeyNotFound = errors.New("key not found") + +// ErrInvalidKey is used when a key is invalid +var ErrInvalidKey = errors.New("invalid key") diff --git a/pkg/wallet/utils.go b/pkg/wallet/utils.go new file mode 100644 index 0000000000..36279424b0 --- /dev/null +++ b/pkg/wallet/utils.go @@ -0,0 +1,110 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package wallet + +import ( + "crypto" + "encoding/binary" + "errors" + + josecipher "github.com/square/go-jose/v3/cipher" + chacha "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +// errEmptyRecipients is used when recipients list is empty +var errEmptyRecipients = errors.New("empty recipients") + +// errInvalidKeypair is used when a keypair is invalid +var errInvalidKeypair = errors.New("invalid keypair") + +// VerifyKeys is a utility function that verifies if sender key pair and recipients keys are valid (not empty) +func VerifyKeys(sender KeyPair, recipients [][]byte) error { + if len(recipients) == 0 { + return errEmptyRecipients + } + + if !IsKeyPairValid(sender) { + return errInvalidKeypair + } + + if !IsChachaKeyValid(sender.Priv) || !IsChachaKeyValid(sender.Pub) { + return ErrInvalidKey + } + return nil +} + +// IsChachaKeyValid will return true if key size is the same as chacha20poly1305.keySize +// false otherwise +func IsChachaKeyValid(key []byte) bool { + return len(key) == chacha.KeySize +} + +// KeyPair represents a private/public key pair +type KeyPair struct { + // Priv is a private key + Priv []byte + // Pub is a public key + Pub []byte +} + +// IsKeyPairValid is a utility function that validates a KeyPair +func IsKeyPairValid(kp KeyPair) bool { + if kp.Priv == nil || kp.Pub == nil { + return false + } + + return true +} + +// Derive25519KEK is a utility function that will derive an ephemeral symmetric key (kek) using fromPrivKey and toPubKey +func Derive25519KEK(alg, apu []byte, fromPrivKey, toPubKey *[chacha.KeySize]byte) ([]byte, error) { // nolint:lll + if fromPrivKey == nil || toPubKey == nil { + return nil, ErrInvalidKey + } + + // generating Z is inspired by sodium_crypto_scalarmult() + // https://github.com/gamringer/php-authcrypt/blob/master/src/Crypt.php#L80 + + // with z being a basePoint of a curve25519 + z := new([chacha.KeySize]byte) + // do ScalarMult of the sender's private key with the recipient key to get a derived Z point + // ( equivalent to derive an EC key ) + curve25519.ScalarMult(z, fromPrivKey, toPubKey) + + // inspired by: github.com/square/go-jose/v3@v3.0.0-20190722231519-723929d55157/cipher/ecdh_es.go + // -> DeriveECDHES() call + // suppPubInfo is the encoded length of the recipient shared key output size in bits + supPubInfo := make([]byte, 4) + // since we're using chacha20poly1305 keys, keySize is known + binary.BigEndian.PutUint32(supPubInfo, uint32(chacha.KeySize)*8) + + // as per https://tools.ietf.org/html/rfc7518#section-4.6.2 + // concatKDF requires info data to be length prefixed with BigEndian 32 bits type + // length prefix alg + algInfo := lengthPrefix(alg) + + // length prefix apu + apuInfo := lengthPrefix(apu) + + // length prefix apv (empty) + apvInfo := lengthPrefix(nil) + + // get a Concat KDF stream for z, encryption algorithm, api, supPubInfo and empty supPrivInfo using sha256 + reader := josecipher.NewConcatKDF(crypto.SHA256, z[:], algInfo, apuInfo, apvInfo, supPubInfo, []byte{}) + + // kek is the recipient specific encryption key used to encrypt the sharedSymKey + kek := make([]byte, chacha.KeySize) + + // Read on the KDF will never fail + _, err := reader.Read(kek) + if err != nil { + return nil, err + } + + return kek, nil +} diff --git a/pkg/wallet/utils_test.go b/pkg/wallet/utils_test.go new file mode 100644 index 0000000000..66f487af49 --- /dev/null +++ b/pkg/wallet/utils_test.go @@ -0,0 +1,60 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package wallet + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + chacha "golang.org/x/crypto/chacha20poly1305" +) + +func TestIsKeyPairValid(t *testing.T) { + require.False(t, IsKeyPairValid(KeyPair{})) + pubKey := []byte("testpublickey") + privKey := []byte("testprivatekey") + validChachaKey, err := base64.RawURLEncoding.DecodeString("c8CSJr_27PN9xWCpzXNmepRndD6neQcnO9DS0YWjhNs") + require.NoError(t, err) + + require.False(t, IsKeyPairValid(KeyPair{Priv: privKey, Pub: nil})) + require.False(t, IsKeyPairValid(KeyPair{Priv: nil, Pub: pubKey})) + require.True(t, IsKeyPairValid(KeyPair{Priv: privKey, Pub: pubKey})) + + require.EqualError(t, + VerifyKeys( + KeyPair{Priv: privKey, Pub: pubKey}, + [][]byte{[]byte("abc"), []byte("def")}), + ErrInvalidKey.Error()) + require.EqualError(t, + VerifyKeys( + KeyPair{Priv: privKey, Pub: pubKey}, + [][]byte{}), + errEmptyRecipients.Error()) + require.EqualError(t, VerifyKeys(KeyPair{}, [][]byte{[]byte("abc"), []byte("def")}), errInvalidKeypair.Error()) + require.NoError(t, VerifyKeys(KeyPair{Priv: validChachaKey, Pub: validChachaKey}, [][]byte{validChachaKey})) +} + +func TestDeriveKEK_Util(t *testing.T) { + kek, err := Derive25519KEK(nil, nil, nil, nil) + require.EqualError(t, err, ErrInvalidKey.Error()) + require.Empty(t, kek) + validChachaKey, err := base64.RawURLEncoding.DecodeString("c8CSJr_27PN9xWCpzXNmepRndD6neQcnO9DS0YWjhNs") + require.NoError(t, err) + chachaKey := new([chacha.KeySize]byte) + copy(chachaKey[:], validChachaKey) + kek, err = Derive25519KEK(nil, nil, chachaKey, nil) + require.EqualError(t, err, ErrInvalidKey.Error()) + require.Empty(t, kek) + validChachaKey2, err := base64.RawURLEncoding.DecodeString("AAjrHjiFLw6kf6CZ5zqH1ooG3y2aQhuqxmUvqJnIvDI") + require.NoError(t, err) + chachaKey2 := new([chacha.KeySize]byte) + copy(chachaKey2[:], validChachaKey2) + kek, err = Derive25519KEK(nil, nil, chachaKey, chachaKey2) + require.NoError(t, err) + require.NotEmpty(t, kek) +} diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go index 4ceea2a494..d85820f4c9 100644 --- a/pkg/wallet/wallet.go +++ b/pkg/wallet/wallet.go @@ -9,16 +9,16 @@ package wallet import ( "crypto/ed25519" "crypto/rand" + "encoding/binary" "encoding/json" "errors" "fmt" "time" "github.com/btcsuite/btcutil/base58" + chacha "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/nacl/box" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto/jwe/authcrypt" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/ed25519signature2018" "github.com/hyperledger/aries-framework-go/pkg/storage" @@ -40,23 +40,17 @@ type provider interface { // BaseWallet wallet implementation type BaseWallet struct { store storage.Store - crypter crypto.Crypter inboundTransportEndpoint string } // New return new instance of wallet implementation func New(ctx provider) (*BaseWallet, error) { - crypter, err := authcrypt.New(authcrypt.XC20P) - if err != nil { - return nil, fmt.Errorf("new authcrypt failed: %w", err) - } - store, err := ctx.StorageProvider().OpenStore(storageName) if err != nil { return nil, fmt.Errorf("failed to OpenStore for '%s', cause: %w", storageName, err) } - return &BaseWallet{store: store, crypter: crypter, inboundTransportEndpoint: ctx.InboundTransportEndpoint()}, nil + return &BaseWallet{store: store, inboundTransportEndpoint: ctx.InboundTransportEndpoint()}, nil } // CreateEncryptionKey create a new public/private encryption keypair. @@ -67,7 +61,7 @@ func (w *BaseWallet) CreateEncryptionKey() (string, error) { } base58Pub := base58.Encode(pub[:]) // TODO - need to encrypt the priv before putting them in the store. - if err := w.persistKey(base58Pub, &crypto.KeyPair{Pub: pub[:], Priv: priv[:]}); err != nil { + if err := w.persistKey(base58Pub, &KeyPair{Pub: pub[:], Priv: priv[:]}); err != nil { return "", err } return base58Pub, nil @@ -81,7 +75,7 @@ func (w *BaseWallet) CreateSigningKey() (string, error) { } base58Pub := base58.Encode(pub[:]) // TODO - need to encrypt the priv before putting them in the store. - if err := w.persistKey(base58Pub, &crypto.KeyPair{Pub: pub[:], Priv: priv[:]}); err != nil { + if err := w.persistKey(base58Pub, &KeyPair{Pub: pub[:], Priv: priv[:]}); err != nil { return "", err } return base58Pub, nil @@ -96,66 +90,6 @@ func (w *BaseWallet) SignMessage(message []byte, fromVerKey string) ([]byte, err return ed25519signature2018.New().Sign(keyPair.Priv, message) } -// DecryptMessage decrypt message -func (w *BaseWallet) DecryptMessage(encMessage []byte, toVerKey string) ([]byte, string, error) { - return nil, "", fmt.Errorf("not implemented") -} - -// PackMessage Pack a message for one or more recipients. -func (w *BaseWallet) PackMessage(envelope *Envelope) ([]byte, error) { - if envelope == nil { - return nil, errors.New("envelope argument is nil") - } - // get keypair from db - senderKeyPair, err := w.getKey(envelope.FromVerKey) - if err != nil { - return nil, fmt.Errorf("failed from getKey: %w", err) - } - - var recipients [][]byte - for _, verKey := range envelope.ToVerKeys { - // TODO It is possible to have different key schemes in an interop situation - // there is no guarantee that each recipient is using the same key types - // decode base58 ver key - verKeyBytes := base58.Decode(verKey) - // create 32 byte key - recipients = append(recipients, verKeyBytes) - } - // encrypt message - bytes, err := w.crypter.Encrypt(envelope.Message, *senderKeyPair, recipients) - if err != nil { - return nil, fmt.Errorf("failed from encrypt: %w", err) - } - return bytes, nil -} - -// UnpackMessage Unpack a message. -func (w *BaseWallet) UnpackMessage(encMessage []byte) (*Envelope, error) { - var e authcrypt.Envelope - if err := json.Unmarshal(encMessage, &e); err != nil { - return nil, fmt.Errorf("failed to unmarshal encMessage: %w", err) - } - var keysNotFound []string - for _, v := range e.Recipients { - recipVKeyB58 := v.Header.KID - // get keypair from db - recipientKeyPair, err := w.getKey(recipVKeyB58) - if err != nil { - if errors.Is(err, ErrKeyNotFound) { - keysNotFound = append(keysNotFound, recipVKeyB58) - continue - } - return nil, fmt.Errorf("failed from getKey: %w", err) - } - bytes, err := w.crypter.Decrypt(encMessage, *recipientKeyPair) - if err != nil { - return nil, fmt.Errorf("failed from decrypt: %w", err) - } - return &Envelope{Message: bytes, ToVerKeys: []string{recipVKeyB58}}, nil - } - return nil, fmt.Errorf("no corresponding recipient key found in {%s}", keysNotFound) -} - // Close wallet func (w *BaseWallet) Close() error { return nil @@ -216,7 +150,7 @@ func (w *BaseWallet) CreateDID(method string, opts ...DocOpts) (*did.Doc, error) } // persistKey save key in storage -func (w *BaseWallet) persistKey(key string, value *crypto.KeyPair) error { +func (w *BaseWallet) persistKey(key string, value *KeyPair) error { bytes, err := json.Marshal(value) if err != nil { return fmt.Errorf("failed to marshal key: %w", err) @@ -229,7 +163,7 @@ func (w *BaseWallet) persistKey(key string, value *crypto.KeyPair) error { } // getKey get key -func (w *BaseWallet) getKey(verkey string) (*crypto.KeyPair, error) { +func (w *BaseWallet) getKey(verkey string) (*KeyPair, error) { bytes, err := w.store.Get(verkey) if err != nil { if errors.Is(storage.ErrDataNotFound, err) { @@ -237,9 +171,55 @@ func (w *BaseWallet) getKey(verkey string) (*crypto.KeyPair, error) { } return nil, err } - var key crypto.KeyPair + var key KeyPair if err := json.Unmarshal(bytes, &key); err != nil { return nil, fmt.Errorf("failed unmarshal to key struct: %w", err) } return &key, nil } + +// lengthPrefix array with a bigEndian uint32 value of array's length +func lengthPrefix(array []byte) []byte { + arrInfo := make([]byte, 4+len(array)) + binary.BigEndian.PutUint32(arrInfo, uint32(len(array))) + copy(arrInfo[4:], array) + return arrInfo +} + +// DeriveKEK will derive an ephemeral symmetric key (kek) using a private key fetched from +// the wallet corresponding to fromPubKey and derived with toPubKey +// This implementation is for curve 25519 only +func (w *BaseWallet) DeriveKEK(alg, apu, fromPubKey, toPubKey []byte) ([]byte, error) { // nolint:lll + if fromPubKey == nil || toPubKey == nil { + return nil, ErrInvalidKey + } + fromPrivKey := new([chacha.KeySize]byte) + copy(fromPrivKey[:], fromPubKey) + + // get keypair from wallet store + walletKeyPair, err := w.getKey(base58.Encode(fromPubKey)) + if err != nil { + return nil, fmt.Errorf("failed from getKey: %w", err) + } + copy(fromPrivKey[:], walletKeyPair.Priv) + + toKey := new([chacha.KeySize]byte) + copy(toKey[:], toPubKey) + return Derive25519KEK(alg, apu, fromPrivKey, toKey) +} + +// FindVerKey selects a signing key which is present in candidateKeys that is present in the wallet +func (w *BaseWallet) FindVerKey(candidateKeys []string) (int, error) { + for i, key := range candidateKeys { + _, err := w.getKey(key) + if err != nil { + if errors.Is(err, ErrKeyNotFound) { + continue + } + return -1, fmt.Errorf("failed from getKey: %w", err) + } + // Currently chooses the first usable key, but could use different logic (eg, priorities) + return i, nil + } + return -1, ErrKeyNotFound +} diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index 3a64192b94..3177bcd2f9 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -16,11 +16,8 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/crypto/nacl/box" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/crypto/jwe/authcrypt" "github.com/hyperledger/aries-framework-go/pkg/doc/did" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/ed25519signature2018" - "github.com/hyperledger/aries-framework-go/pkg/internal/mock/didcomm" mockstorage "github.com/hyperledger/aries-framework-go/pkg/internal/mock/storage" "github.com/hyperledger/aries-framework-go/pkg/storage" ) @@ -76,199 +73,6 @@ func TestBaseWallet_Close(t *testing.T) { }) } -func TestBaseWallet_UnpackMessage(t *testing.T) { - t.Run("test failed from getKey", func(t *testing.T) { - m := make(map[string][]byte) - m["key1"] = nil - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: m, ErrGet: fmt.Errorf("get error"), - }})) - require.NoError(t, err) - - crypter, err := authcrypt.New(authcrypt.XC20P) - require.NoError(t, err) - w.crypter = crypter - - packMsg, err := json.Marshal(authcrypt.Envelope{ - Recipients: []authcrypt.Recipient{{Header: authcrypt.RecipientHeaders{KID: "key1"}}}}) - require.NoError(t, err) - _, err = w.UnpackMessage(packMsg) - require.Error(t, err) - require.Contains(t, err.Error(), "get error") - }) - - t.Run("test failed to unmarshal encMessage", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - _, err = w.UnpackMessage(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to unmarshal encMessage") - }) - - t.Run("test key not found", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - - crypter, err := authcrypt.New(authcrypt.XC20P) - require.NoError(t, err) - w.crypter = crypter - - pub1, priv1, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - base58FromVerKey := base58.Encode(pub1[:]) - require.NoError(t, w.persistKey(base58FromVerKey, &crypto.KeyPair{Pub: pub1[:], - Priv: priv1[:]})) - - pub2, _, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - - packMsg, err := w.PackMessage(&Envelope{Message: []byte("msg1"), - FromVerKey: base58FromVerKey, - ToVerKeys: []string{base58.Encode(pub2[:])}}) - require.NoError(t, err) - - _, err = w.UnpackMessage(packMsg) - require.Error(t, err) - require.Contains(t, err.Error(), "no corresponding recipient key found in") - }) - - t.Run("test decrypt failed", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - - decryptValue := func(envelope []byte, recipientKeyPair crypto.KeyPair) ([]byte, error) { - return nil, fmt.Errorf("decrypt error") - } - e := func(payload []byte, sender crypto.KeyPair, recipients [][]byte) (bytes []byte, e error) { - crypter, e := authcrypt.New(authcrypt.XC20P) - require.NoError(t, e) - return crypter.Encrypt(payload, sender, recipients) - } - mockCrypter := &didcomm.MockAuthCrypt{DecryptValue: decryptValue, - EncryptValue: e} - - w.crypter = mockCrypter - - pub1, priv1, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - base58FromVerKey := base58.Encode(pub1[:]) - require.NoError(t, w.persistKey(base58FromVerKey, &crypto.KeyPair{Pub: pub1[:], - Priv: priv1[:]})) - - pub2, priv2, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - require.NoError(t, w.persistKey(base58.Encode(pub2[:]), &crypto.KeyPair{Pub: pub2[:], - Priv: priv2[:]})) - - packMsg, err := w.PackMessage(&Envelope{Message: []byte("msg1"), - FromVerKey: base58FromVerKey, - ToVerKeys: []string{base58.Encode(pub2[:])}}) - require.NoError(t, err) - - _, err = w.UnpackMessage(packMsg) - require.Error(t, err) - require.Contains(t, err.Error(), "decrypt error") - }) -} - -func TestBaseWallet_PackMessage(t *testing.T) { - t.Run("test success", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - - crypter, err := authcrypt.New(authcrypt.XC20P) - require.NoError(t, err) - w.crypter = crypter - - pub1, priv1, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - base58FromVerKey := base58.Encode(pub1[:]) - require.NoError(t, w.persistKey(base58FromVerKey, &crypto.KeyPair{Pub: pub1[:], - Priv: priv1[:]})) - - pub2, priv2, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - require.NoError(t, w.persistKey(base58.Encode(pub2[:]), &crypto.KeyPair{Pub: pub2[:], - Priv: priv2[:]})) - - packMsg, err := w.PackMessage(&Envelope{Message: []byte("msg1"), - FromVerKey: base58FromVerKey, - ToVerKeys: []string{base58.Encode(pub2[:])}}) - require.NoError(t, err) - - unpackMsg, err := w.UnpackMessage(packMsg) - require.NoError(t, err) - require.Equal(t, []byte("msg1"), unpackMsg.Message) - require.Equal(t, []string{base58.Encode(pub2[:])}, unpackMsg.ToVerKeys) - }) - - t.Run("test envelope is nil", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - _, err = w.PackMessage(nil) - require.Error(t, err) - require.Contains(t, err.Error(), "envelope argument is nil") - }) - - t.Run("test key not found error", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - require.NoError(t, err) - - crypter, err := authcrypt.New(authcrypt.XC20P) - require.NoError(t, err) - w.crypter = crypter - - _, err = w.PackMessage(&Envelope{Message: []byte("msg1"), - FromVerKey: "key1", - ToVerKeys: []string{}}) - require.Error(t, err) - require.Contains(t, err.Error(), "failed from getKey") - }) - - t.Run("test encrypt failed", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ - Store: make(map[string][]byte), - }})) - - encryptValue := func(payload []byte, sender crypto.KeyPair, recipients [][]byte) (bytes []byte, e error) { - return nil, fmt.Errorf("encrypt error") - } - - w.crypter = &didcomm.MockAuthCrypt{EncryptValue: encryptValue} - - require.NoError(t, err) - - pub1, priv1, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - base58FromVerKey := base58.Encode(pub1[:]) - require.NoError(t, w.persistKey(base58FromVerKey, &crypto.KeyPair{Pub: pub1[:], - Priv: priv1[:]})) - - pub2, priv2, err := box.GenerateKey(rand.Reader) - require.NoError(t, err) - require.NoError(t, w.persistKey(base58.Encode(pub2[:]), &crypto.KeyPair{Pub: pub2[:], - Priv: priv2[:]})) - - _, err = w.PackMessage(&Envelope{Message: []byte("msg1"), - FromVerKey: base58FromVerKey, - ToVerKeys: []string{base58.Encode(pub2[:])}}) - require.Error(t, err) - require.Contains(t, err.Error(), "encrypt error") - }) -} - func TestBaseWallet_SignMessage(t *testing.T) { t.Run("test key not found", func(t *testing.T) { w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{ @@ -302,15 +106,6 @@ func TestBaseWallet_SignMessage(t *testing.T) { }) } -func TestBaseWallet_DecryptMessage(t *testing.T) { - t.Run("test error not implemented", func(t *testing.T) { - w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{})) - require.NoError(t, err) - _, _, err = w.DecryptMessage(nil, "") - require.Error(t, err) - }) -} - func TestBaseWallet_NewDID(t *testing.T) { const method = "example" @@ -382,6 +177,130 @@ func TestBaseWallet_NewDID(t *testing.T) { }) } +func TestBaseWallet_DeriveKEK(t *testing.T) { + pk32, sk32, err := box.GenerateKey(rand.Reader) + require.NoError(t, err) + kp := KeyPair{Pub: pk32[:], Priv: sk32[:]} + kpm, err := json.Marshal(kp) + require.NoError(t, err) + + pk32a, _, err := box.GenerateKey(rand.Reader) + w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: map[string][]byte{ + base58.Encode(pk32[:]): kpm, + }, + }})) + + t.Run("test success", func(t *testing.T) { + // test DeriveKEK from wallet where fromKey is a public key (private fromKey will be fetched from the wallet) + require.NoError(t, err) + kek, e := w.DeriveKEK(nil, nil, pk32[:], pk32a[:]) + require.NoError(t, e) + require.NotEmpty(t, kek) + + // test Derive25519KEK from the util function where fromKey is a private key + kek, e = Derive25519KEK(nil, nil, sk32, pk32a) + require.NoError(t, e) + require.NotEmpty(t, kek) + }) + + t.Run("test failure fromKey empty and toKey not empty", func(t *testing.T) { + // test DeriveKEK from wallet where fromKey is a public key (private fromKey will be fetched from the wallet) + kek, e := w.DeriveKEK(nil, nil, nil, pk32a[:]) + require.EqualError(t, e, ErrInvalidKey.Error()) + require.Empty(t, kek) + + // test Derive25519KEK from the util function where fromKey is a private key + kek, e = Derive25519KEK(nil, nil, nil, pk32a) + require.EqualError(t, e, ErrInvalidKey.Error()) + require.Empty(t, kek) + }) + + t.Run("test failure fromKey not empty and toKey empty", func(t *testing.T) { + // test DeriveKEK from wallet where fromKey is a public key (private fromKey will be fetched from the wallet) + kek, e := w.DeriveKEK(nil, nil, pk32[:], nil) + require.EqualError(t, e, ErrInvalidKey.Error()) + require.Empty(t, kek) + + // test Derive25519KEK from the util function where fromKey is a private key + kek, e = Derive25519KEK(nil, nil, sk32, nil) + require.EqualError(t, e, ErrInvalidKey.Error()) + require.Empty(t, kek) + }) + + t.Run("test failure fromPubKey not found in wallet", func(t *testing.T) { + // test DeriveKEK from wallet where fromKey is a public key (private fromKey will be fetched from the wallet) + kek, e := w.DeriveKEK(nil, nil, pk32a[:], pk32[:]) + require.EqualError(t, e, "failed from getKey: "+ErrKeyNotFound.Error()) + require.Empty(t, kek) + }) +} + +func TestBaseWallet_FindVerKey(t *testing.T) { + pk1, sk1, err := box.GenerateKey(rand.Reader) + require.NoError(t, err) + kp := KeyPair{Pub: pk1[:], Priv: sk1[:]} + kpm1, err := json.Marshal(kp) + require.NoError(t, err) + + pk2, sk2, err := box.GenerateKey(rand.Reader) + require.NoError(t, err) + kp = KeyPair{Pub: pk2[:], Priv: sk2[:]} + kpm2, err := json.Marshal(kp) + require.NoError(t, err) + + pk3, sk3, err := box.GenerateKey(rand.Reader) + require.NoError(t, err) + kp = KeyPair{Pub: pk3[:], Priv: sk3[:]} + kpm3, err := json.Marshal(kp) + require.NoError(t, err) + + w, err := New(newMockWalletProvider(&mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{ + Store: map[string][]byte{ + base58.Encode(pk1[:]): kpm1, + base58.Encode(pk2[:]): kpm2, + base58.Encode(pk3[:]): kpm3, + }, + }})) + require.NoError(t, err) + + t.Run("test success", func(t *testing.T) { + candidateKeys := []string{ + "somekey1", + "somekey2", + base58.Encode(pk1[:]), + } + i, e := w.FindVerKey(candidateKeys) + require.NoError(t, e) + require.Equal(t, 2, i) + candidateKeys = []string{ + "somekey1", + base58.Encode(pk1[:]), + "somekey2", + } + i, e = w.FindVerKey(candidateKeys) + require.NoError(t, e) + require.Equal(t, 1, i) + candidateKeys = []string{ + base58.Encode(pk1[:]), + "somekey1", + "somekey2", + } + i, e = w.FindVerKey(candidateKeys) + require.NoError(t, e) + require.Equal(t, 0, i) + candidateKeys = []string{ + "somekey1", + base58.Encode(pk2[:]), + "somekey2", + base58.Encode(pk1[:]), + } + i, e = w.FindVerKey(candidateKeys) + require.NoError(t, e) + require.Equal(t, 1, i) + }) +} + func newMockWalletProvider(storagePvdr *mockstorage.MockStoreProvider) *mockProvider { return &mockProvider{storagePvdr} }