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