From 2df5b2e871784fb0a38b53a57b7e865822484cd1 Mon Sep 17 00:00:00 2001 From: Baha Shaaban Date: Fri, 8 Jan 2021 17:28:55 -0500 Subject: [PATCH] feat: add XChacha key to ECDH AEAD Tink key This is a first change to add XChacha20Poly1305 keys to ECDH Tink keys to support XChacha AEAD encryption and key wrapping. It includes adding new Tink key managers in the Tink keys package to support keys for: 1. NIST P curves KW - AES/XChacha20Poly1305 content encryption 2. X25519 KW - AES/XChacha20Poly1305 content encryption This change also includes new key templates to support the creation of these ECDH keys. Future changes will include supporting Key exports of these ECDH keys, adding new KMS key types, add XChacha Key wrapping and finally testing the new key with the JWE packers. part of #1637, #1806, #1684, #815 Signed-off-by: Baha Shaaban --- pkg/crypto/tinkcrypto/key_wrapper_test.go | 2 +- .../primitive/composite/ecdh/ecdh.go | 34 +- .../ecdh/ecdh_aes_aead_public_key_manager.go | 99 ------ .../composite/ecdh/ecdh_factory_test.go | 106 +++++- .../composite/ecdh/ecdh_key_template.go | 144 ++++++-- .../composite/ecdh/ecdh_key_template_test.go | 47 ++- ...dh_nistpkw_aesaead_private_key_manager.go} | 88 ++--- ...stpkw_aesaead_private_key_manager_test.go} | 79 +++-- ...ecdh_nistpkw_aesaead_public_key_manager.go | 100 ++++++ ...istpkw_aesaead_public_key_manager_test.go} | 30 +- ...nistpkw_xchachaaead_private_key_manager.go | 170 ++++++++++ ...kw_xchachaaead_private_key_manager_test.go | 271 +++++++++++++++ ..._nistpkw_xchachaaead_public_key_manager.go | 100 ++++++ ...pkw_xchachaaead_public_key_manager_test.go | 165 +++++++++ .../ecdh/ecdh_x25519_private_key_manager.go | 9 - ...dh_x25519kw_aesaead_private_key_manager.go | 180 ++++++++++ ...5519kw_aesaead_private_key_manager_test.go | 315 ++++++++++++++++++ ...cdh_x25519kw_aesaead_public_key_manager.go | 99 ++++++ ...25519kw_aesaead_public_key_manager_test.go | 185 ++++++++++ ...25519kw_xchachaaead_private_key_manager.go | 211 ++++++++++++ ...kw_xchachaaead_private_key_manager_test.go | 248 ++++++++++++++ ...x25519kw_xchachaaead_public_key_manager.go | 99 ++++++ ...9kw_xchachaaead_public_key_manager_test.go | 158 +++++++++ .../composite/keyio/composite_key_export.go | 19 +- .../keyio/composite_key_export_import_test.go | 2 +- .../register_ecdh_aead_enc_helper.go | 50 ++- pkg/crypto/tinkcrypto/unwrap_support.go | 2 +- pkg/kms/localkms/pubkey_writer.go | 2 +- proto/tink/ecdh_aead.proto | 15 +- 29 files changed, 2768 insertions(+), 261 deletions(-) delete mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager.go rename pkg/crypto/tinkcrypto/primitive/composite/ecdh/{ecdh_aes_aead_private_key_manager.go => ecdh_nistpkw_aesaead_private_key_manager.go} (52%) rename pkg/crypto/tinkcrypto/primitive/composite/ecdh/{ecdh_aes_aead_private_key_manager_test.go => ecdh_nistpkw_aesaead_private_key_manager_test.go} (74%) create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager.go rename pkg/crypto/tinkcrypto/primitive/composite/ecdh/{ecdh_aes_aead_public_key_manager_test.go => ecdh_nistpkw_aesaead_public_key_manager_test.go} (82%) create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager_test.go delete mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519_private_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager_test.go diff --git a/pkg/crypto/tinkcrypto/key_wrapper_test.go b/pkg/crypto/tinkcrypto/key_wrapper_test.go index a7283840bf..38a5e744f6 100644 --- a/pkg/crypto/tinkcrypto/key_wrapper_test.go +++ b/pkg/crypto/tinkcrypto/key_wrapper_test.go @@ -184,7 +184,7 @@ func Test_ksToPrivateECDSAKey_Failure(t *testing.T) { _, err = ksToPrivateECDSAKey(recipientKeyPub) require.EqualError(t, err, "ksToPrivateECDSAKey: failed to extract sender key: extractPrivKey: "+ "can't extract unsupported private key 'type.hyperledger.org/hyperledger.aries.crypto.tink"+ - ".EcdhAesAeadPublicKey'") + ".EcdhNistPKwAesAeadPublicKey'") } func Test_ksToPublicECDSAKey_Failure(t *testing.T) { diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh.go index 0273d1ba5c..b77043278c 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh.go @@ -103,12 +103,42 @@ import ( // nolint: gochecknoinits func init() { // TODO - avoid the tink registry singleton. - err := registry.RegisterKeyManager(newECDHPrivateKeyManager()) + err := registry.RegisterKeyManager(newECDHNISTPAESPrivateKeyManager()) if err != nil { panic(fmt.Sprintf("ecdh.init() failed: %v", err)) } - err = registry.RegisterKeyManager(newECDHPublicKeyManager()) + err = registry.RegisterKeyManager(newECDHNISTPAESPublicKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHX25519XChachaPublicKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHX25519XChachaPrivateKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHNISTPXChachaPrivateKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHNISTPXChachaPublicKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHX25519AESPublicKeyManager()) + if err != nil { + panic(fmt.Sprintf("ecdh.init() failed: %v", err)) + } + + err = registry.RegisterKeyManager(newECDHX25519AESPrivateKeyManager()) if err != nil { panic(fmt.Sprintf("ecdh.init() failed: %v", err)) } diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager.go deleted file mode 100644 index 439d4c0548..0000000000 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package ecdh - -import ( - "crypto/elliptic" - "errors" - "fmt" - - "github.com/golang/protobuf/proto" - "github.com/google/tink/go/core/registry" - "github.com/google/tink/go/keyset" - tinkpb "github.com/google/tink/go/proto/tink_go_proto" - - "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" - "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" - ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" -) - -const ( - ecdhAESPublicKeyVersion = 0 - ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAesAeadPublicKey" -) - -// common errors. -var errInvalidECDHAESPublicKey = errors.New("ecdh_aes_public_key_manager: invalid key") - -// ecdhPublicKeyManager is an implementation of KeyManager interface. -// It generates new ECDHPublicKey (AES) keys and produces new instances of ECDHAEADCompositeEncrypt subtle. -type ecdhPublicKeyManager struct{} - -// Assert that ecdhPublicKeyManager implements the KeyManager interface. -var _ registry.KeyManager = (*ecdhPublicKeyManager)(nil) - -// newECDHPublicKeyManager creates a new ecdhPublicKeyManager. -func newECDHPublicKeyManager() *ecdhPublicKeyManager { - return new(ecdhPublicKeyManager) -} - -// Primitive creates an ECDHESPublicKey subtle for the given serialized ECDHESPublicKey proto. -func (km *ecdhPublicKeyManager) Primitive(serializedKey []byte) (interface{}, error) { - if len(serializedKey) == 0 { - return nil, errInvalidECDHAESPublicKey - } - - ecdhPubKey := new(ecdhpb.EcdhAeadPublicKey) - - err := proto.Unmarshal(serializedKey, ecdhPubKey) - if err != nil { - return nil, errInvalidECDHAESPublicKey - } - - _, err = km.validateKey(ecdhPubKey) - if err != nil { - return nil, errInvalidECDHAESPublicKey - } - - rEnc, err := composite.NewRegisterCompositeAEADEncHelper(ecdhPubKey.Params.EncParams.AeadEnc) - if err != nil { - return nil, fmt.Errorf("ecdh_aes_public_key_manager: NewRegisterCompositeAEADEncHelper "+ - "failed: %w", err) - } - - return subtle.NewECDHAEADCompositeEncrypt(rEnc, ecdhPubKey.Params.EncParams.CEK), nil -} - -// DoesSupport indicates if this key manager supports the given key type. -func (km *ecdhPublicKeyManager) DoesSupport(typeURL string) bool { - return typeURL == ecdhAESPublicKeyTypeURL -} - -// TypeURL returns the key type of keys managed by this key manager. -func (km *ecdhPublicKeyManager) TypeURL() string { - return ecdhAESPublicKeyTypeURL -} - -// NewKey is not implemented for public key manager. -func (km *ecdhPublicKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { - return nil, errors.New("ecdh_aes_public_key_manager: NewKey not implemented") -} - -// NewKeyData is not implemented for public key manager. -func (km *ecdhPublicKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { - return nil, errors.New("ecdh_aes_public_key_manager: NewKeyData not implemented") -} - -// validateKey validates the given ECDHESPublicKey. -func (km *ecdhPublicKeyManager) validateKey(key *ecdhpb.EcdhAeadPublicKey) (elliptic.Curve, error) { - err := keyset.ValidateKeyVersion(key.Version, ecdhAESPublicKeyVersion) - if err != nil { - return nil, fmt.Errorf("ecdh_aes_publie_key_manager: invalid key: %w", err) - } - - return validateKeyFormat(key.Params) -} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_factory_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_factory_test.go index 416e82828a..a2069d6652 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_factory_test.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_factory_test.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package ecdh import ( + "crypto/ed25519" + "crypto/rand" "encoding/base64" "encoding/json" "fmt" @@ -27,6 +29,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" ) func TestECDHESFactory(t *testing.T) { @@ -35,40 +38,65 @@ func TestECDHESFactory(t *testing.T) { rawPtFmt := commonpb.EcPointFormat_COMPRESSED primaryEncT := aead.AES128GCMKeyTemplate() rawEncT := aead.AES256GCMKeyTemplate() + kt := ecdhpb.KeyType_EC cek := random.GetRandomBytes(32) - primaryPrivProto := generateECDHAEADPrivateKey(t, c, primaryPtFmt, primaryEncT, cek) + primaryPrivProto := generateECDHAEADPrivateKey(t, c, primaryPtFmt, kt, primaryEncT, cek) sPrimaryPriv, err := proto.Marshal(primaryPrivProto) require.NoError(t, err) primaryPrivKey := testutil.NewKey( - testutil.NewKeyData(ecdhAESPrivateKeyTypeURL, sPrimaryPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), + testutil.NewKeyData(ecdhNISTPAESPrivateKeyTypeURL, sPrimaryPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), tinkpb.KeyStatusType_ENABLED, 8, tinkpb.OutputPrefixType_RAW) - rawPrivProto := generateECDHAEADPrivateKey(t, c, rawPtFmt, rawEncT, cek) + rawPrivProto := generateECDHAEADPrivateKey(t, c, rawPtFmt, kt, rawEncT, cek) sRawPriv, err := proto.Marshal(rawPrivProto) require.NoError(t, err) rawPrivKey := testutil.NewKey( - testutil.NewKeyData(ecdhAESPrivateKeyTypeURL, sRawPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), + testutil.NewKeyData(ecdhNISTPAESPrivateKeyTypeURL, sRawPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), tinkpb.KeyStatusType_ENABLED, 11, tinkpb.OutputPrefixType_RAW) - privKeys := []*tinkpb.Keyset_Key{primaryPrivKey, rawPrivKey} + x25519XChachaPrivProto := generateECDHAEADPrivateKey(t, commonpb.EllipticCurveType_CURVE25519, primaryPtFmt, + ecdhpb.KeyType_OKP, aead.XChaCha20Poly1305KeyTemplate(), cek) + + sX25519XChachaPriv, err := proto.Marshal(x25519XChachaPrivProto) + require.NoError(t, err) + + x25519XChachaPrivKey := testutil.NewKey( + testutil.NewKeyData(ecdhX25519XChachaPrivateKeyTypeURL, sX25519XChachaPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), + tinkpb.KeyStatusType_ENABLED, 15, tinkpb.OutputPrefixType_RAW) + + privKeys := []*tinkpb.Keyset_Key{primaryPrivKey, rawPrivKey, x25519XChachaPrivKey} privKeyset := testutil.NewKeyset(privKeys[0].KeyId, privKeys) khPriv, err := testkeyset.NewHandle(privKeyset) require.NoError(t, err) + xPrivKeys := []*tinkpb.Keyset_Key{x25519XChachaPrivKey, rawPrivKey, primaryPrivKey} + xPrivKeyset := testutil.NewKeyset(xPrivKeys[0].KeyId, xPrivKeys) + xKHPriv, err := testkeyset.NewHandle(xPrivKeyset) + require.NoError(t, err) + khPub, err := khPriv.Public() require.NoError(t, err) + xKHPub, err := xKHPriv.Public() + require.NoError(t, err) + e, err := NewECDHEncrypt(khPub) require.NoError(t, err) d, err := NewECDHDecrypt(khPriv) require.NoError(t, err) + xe, err := NewECDHEncrypt(xKHPub) + require.NoError(t, err) + + xd, err := NewECDHDecrypt(xKHPriv) + require.NoError(t, err) + for i := 0; i < 1000; i++ { pt := random.GetRandomBytes(20) aadRndNb := random.GetRandomBytes(10) @@ -88,6 +116,9 @@ func TestECDHESFactory(t *testing.T) { ct, err := e.Encrypt(pt, aad) require.NoError(t, err) + xct, err := xe.Encrypt(pt, aad) + require.NoError(t, err) + // encrypt for single recipient will generate new AAD for recipient, extract it from ct encData := &composite.EncryptedData{} err = json.Unmarshal(ct, encData) @@ -96,12 +127,16 @@ func TestECDHESFactory(t *testing.T) { gotpt, err := d.Decrypt(ct, aad) require.NoError(t, err) + xgotpt, err := xd.Decrypt(xct, aad) + require.NoError(t, err) + require.EqualValues(t, pt, gotpt) + require.EqualValues(t, pt, xgotpt) } } // ecdhAEADPublicKey returns a EcdhAeadPublicKey with specified parameters. -func ecdhAEADPublicKey(t *testing.T, c commonpb.EllipticCurveType, ptfmt commonpb.EcPointFormat, +func ecdhAEADPublicKey(t *testing.T, c commonpb.EllipticCurveType, ptfmt commonpb.EcPointFormat, kt ecdhpb.KeyType, encT *tinkpb.KeyTemplate, x, y, cek []byte) *ecdhpb.EcdhAeadPublicKey { t.Helper() @@ -110,6 +145,7 @@ func ecdhAEADPublicKey(t *testing.T, c commonpb.EllipticCurveType, ptfmt commonp Params: &ecdhpb.EcdhAeadParams{ KwParams: &ecdhpb.EcdhKwParams{ CurveType: c, + KeyType: kt, }, EncParams: &ecdhpb.EcdhAeadEncParams{ AeadEnc: encT, @@ -122,8 +158,8 @@ func ecdhAEADPublicKey(t *testing.T, c commonpb.EllipticCurveType, ptfmt commonp } } -// eciesAEADESPrivateKey returns a EciesAeadHkdfPrivateKey with specified parameters. -func eciesAEADESPrivateKey(t *testing.T, p *ecdhpb.EcdhAeadPublicKey, d []byte) *ecdhpb.EcdhAeadPrivateKey { +// ecdhesAEADPrivateKey returns a EcdhAeadPrivateKey with specified parameters. +func ecdhesAEADPrivateKey(t *testing.T, p *ecdhpb.EcdhAeadPublicKey, d []byte) *ecdhpb.EcdhAeadPrivateKey { t.Helper() return &ecdhpb.EcdhAeadPrivateKey{ @@ -135,18 +171,57 @@ func eciesAEADESPrivateKey(t *testing.T, p *ecdhpb.EcdhAeadPublicKey, d []byte) // generateECDHAEADPrivateKey generates a new EC key pair and returns the private key proto. func generateECDHAEADPrivateKey(t *testing.T, c commonpb.EllipticCurveType, ptfmt commonpb.EcPointFormat, - encT *tinkpb.KeyTemplate, cek []byte) *ecdhpb.EcdhAeadPrivateKey { + kt ecdhpb.KeyType, encT *tinkpb.KeyTemplate, cek []byte) *ecdhpb.EcdhAeadPrivateKey { t.Helper() + if ecdhpb.KeyType_OKP.String() == kt.String() { + return buildXChachaKey(t, ptfmt, encT, c, cek) + } + curve, err := hybrid.GetCurve(c.String()) require.NoError(t, err) pvt, err := hybrid.GenerateECDHKeyPair(curve) require.NoError(t, err) - pubKey := ecdhAEADPublicKey(t, c, ptfmt, encT, pvt.PublicKey.Point.X.Bytes(), pvt.PublicKey.Point.Y.Bytes(), cek) + pubKey := ecdhAEADPublicKey(t, c, ptfmt, kt, encT, pvt.PublicKey.Point.X.Bytes(), pvt.PublicKey.Point.Y.Bytes(), + cek) + + return ecdhesAEADPrivateKey(t, pubKey, pvt.D.Bytes()) +} + +func buildXChachaKey(t *testing.T, ptfmt commonpb.EcPointFormat, encT *tinkpb.KeyTemplate, c commonpb.EllipticCurveType, + cek []byte) *ecdhpb.EcdhAeadPrivateKey { + pub, pvt, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + require.NoError(t, err) + + x25519Pvt, err := cryptoutil.SecretEd25519toCurve25519(pvt) + require.NoError(t, err) + + params := &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + KeyType: ecdhpb.KeyType_OKP, + CurveType: c, + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: encT, + CEK: cek, + }, + EcPointFormat: ptfmt, + } - return eciesAEADESPrivateKey(t, pubKey, pvt.D.Bytes()) + return &ecdhpb.EcdhAeadPrivateKey{ + Version: 0, + KeyValue: x25519Pvt, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: 0, + Params: params, + X: x25519Pub, + }, + } } func TestECDHESFactoryWithBadKeysetType(t *testing.T) { @@ -155,24 +230,25 @@ func TestECDHESFactoryWithBadKeysetType(t *testing.T) { rawPtFmt := commonpb.EcPointFormat_COMPRESSED primaryEncT := aead.AES128GCMKeyTemplate() rawEncT := aead.AES256GCMKeyTemplate() + kt := ecdhpb.KeyType_EC cek := random.GetRandomBytes(32) - primaryPrivProto := generateECDHAEADPrivateKey(t, c, primaryPtFmt, primaryEncT, cek) + primaryPrivProto := generateECDHAEADPrivateKey(t, c, primaryPtFmt, kt, primaryEncT, cek) sPrimaryPriv, err := proto.Marshal(primaryPrivProto) require.NoError(t, err) primaryPrivKey := testutil.NewKey( - testutil.NewKeyData(ecdhAESPrivateKeyTypeURL, sPrimaryPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), + testutil.NewKeyData(ecdhNISTPAESPrivateKeyTypeURL, sPrimaryPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), tinkpb.KeyStatusType_ENABLED, 8, tinkpb.OutputPrefixType_RAW) - rawPrivProto := generateECDHAEADPrivateKey(t, c, rawPtFmt, rawEncT, cek) + rawPrivProto := generateECDHAEADPrivateKey(t, c, rawPtFmt, kt, rawEncT, cek) sRawPriv, err := proto.Marshal(rawPrivProto) require.NoError(t, err) rawPrivKey := testutil.NewKey( - testutil.NewKeyData(ecdhAESPrivateKeyTypeURL, sRawPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), + testutil.NewKeyData(ecdhNISTPAESPrivateKeyTypeURL, sRawPriv, tinkpb.KeyData_ASYMMETRIC_PRIVATE), tinkpb.KeyStatusType_ENABLED, 11, tinkpb.OutputPrefixType_RAW) badPrivKeyProto, err := testutil.GenerateECIESAEADHKDFPrivateKey(c, commonpb.HashType_SHA256, primaryPtFmt, diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template.go index 1eb6af08b4..12b16ecedc 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template.go @@ -15,59 +15,137 @@ import ( ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" ) -// ECDH256KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM encryption. CEK -// wrapping is done outside of this Tink key (in the tinkcrypto service). It -// is used to represent a key to execute the CompositeDecrypt primitive with the following parameters: +// ECDH256KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent a key +// to execute the CompositeDecrypt primitive with the following parameters: // - Content Encryption: AES256-GCM // Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The -// recipient key represented in this key template uses NIST curve P-256. +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-256. func ECDH256KWAES256GCMKeyTemplate() *tinkpb.KeyTemplate { - return createKeyTemplate(commonpb.EllipticCurveType_NIST_P256, nil) + return createKeyTemplate(true, true, commonpb.EllipticCurveType_NIST_P256, nil) } -// ECDH384KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM encryption. CEK -// wrapping is done outside of this Tink key (in the tinkcrypto service). It -// is used to represent a key to execute the CompositeDecrypt primitive with the following parameters: +// ECDH384KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent a key +// to execute the CompositeDecrypt primitive with the following parameters: // - Content Encryption: AES256-GCM // Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The -// recipient key represented in this key template uses NIST curve P-384. +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-384 func ECDH384KWAES256GCMKeyTemplate() *tinkpb.KeyTemplate { - return createKeyTemplate(commonpb.EllipticCurveType_NIST_P384, nil) + return createKeyTemplate(true, true, commonpb.EllipticCurveType_NIST_P384, nil) } -// ECDH521KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM encryption. CEK -// wrapping is done outside of this Tink key (in the tinkcrypto service). It -// is used to represent a key to execute the CompositeDecrypt primitive with the following parameters: +// ECDH521KWAES256GCMKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent a key +// to execute the CompositeDecrypt primitive with the following parameters: // - Content Encryption: AES256-GCM // Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The -// recipient key represented in this key template uses NIST curve P-521. +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-521 func ECDH521KWAES256GCMKeyTemplate() *tinkpb.KeyTemplate { - return createKeyTemplate(commonpb.EllipticCurveType_NIST_P521, nil) + return createKeyTemplate(true, true, commonpb.EllipticCurveType_NIST_P521, nil) } -// AES256GCMKeyTemplateWithCEK is similar to ECDHAES256GCMKeyTemplate but adding the cek to execute the -// CompositeEncrypt primitive for encrypting a message targeted to one ore more recipients. +// ECDH256KWXChachaKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for XChacha20Poly1305 content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent +// a key to execute the CompositeDecrypt primitive with the following parameters: +// - Content Encryption: XChacha20Poly1305 +// Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-256. +func ECDH256KWXChachaKeyTemplate() *tinkpb.KeyTemplate { + return createKeyTemplate(true, false, commonpb.EllipticCurveType_NIST_P256, nil) +} + +// ECDH384KWXChachaKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for XChacha20Poly1305 content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent +// a key to execute the CompositeDecrypt primitive with the following parameters: +// - Content Encryption: XChacha20Poly1305 +// Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-384 +func ECDH384KWXChachaKeyTemplate() *tinkpb.KeyTemplate { + return createKeyTemplate(true, false, commonpb.EllipticCurveType_NIST_P384, nil) +} + +// ECDH521KWXChachaKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for XChacha20Poly1305 content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent +// a key to execute the CompositeDecrypt primitive with the following parameters: +// - Content Encryption: XChacha20Poly1305 +// Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS. The +// recipient key represented in this key template uses the following key wrapping curve: +// - NIST curve P-521 +func ECDH521KWXChachaKeyTemplate() *tinkpb.KeyTemplate { + return createKeyTemplate(true, false, commonpb.EllipticCurveType_NIST_P521, nil) +} + +// X25519XChachaECDHKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for XChacha20Poly1305 content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent a key +// to execute the CompositeDecrypt primitive with the following parameters: +// - Content Encryption: XChaha20Poly1305 +// Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS.The +// recipient key represented in this key template uses the following key wrapping curve: +// - Curve25519 +func X25519XChachaECDHKeyTemplate() *tinkpb.KeyTemplate { + return createKeyTemplate(false, false, commonpb.EllipticCurveType_CURVE25519, nil) +} + +// X25519AES256GCMECDHKeyTemplate is a KeyTemplate that generates a key that accepts a CEK for AES256-GCM content +// encryption. CEK wrapping is done outside of this Tink key (in the tinkcrypto service). It is used to represent a key +// to execute the CompositeDecrypt primitive with the following parameters: +// - Content Encryption: AES256-GCM +// Keys from this template represent a valid recipient public/private key pairs and can be stored in the KMS.The +// recipient key represented in this key template uses the following key wrapping curve: +// - Curve25519 +func X25519AES256GCMECDHKeyTemplate() *tinkpb.KeyTemplate { + return createKeyTemplate(false, true, commonpb.EllipticCurveType_CURVE25519, nil) +} + +// AES256GCMKeyTemplateWithCEK is similar to ECDH256KWAES256GCMKeyTemplate but adding the cek to execute the +// CompositeEncrypt primitive for encrypting a message targeted to one ore more recipients. KW is not executed by this +// template, so it is ignored and set to NIST P Curved key by default. // Keys from this template offer valid CompositeEncrypt primitive execution only and should not be stored in the KMS. -// The key created from this template has no recipient key info linked to it. It is use exclusively used for primitive +// The key created from this template has no recipient key info linked to it. It is exclusively used for primitive // execution. func AES256GCMKeyTemplateWithCEK(cek []byte) *tinkpb.KeyTemplate { // the curve passed in the template below is ignored when executing the primitive, it's hardcoded to pass key // key format validation only. - return createKeyTemplate(0, cek) + return createKeyTemplate(true, true, 0, cek) } -// TODO add chacha key templates as well https://github.com/hyperledger/aries-framework-go/issues/1637 +// XChachaKeyTemplateWithCEK is similar to X25519XChachaECDHKeyTemplate but adding the cek to execute the +// CompositeEncrypt primitive for encrypting a message targeted to one ore more recipients. +// Keys from this template offer valid CompositeEncrypt primitive execution only and should not be stored in the KMS. +// The key created from this template has no recipient key info linked to it. It is exclusively used for primitive +// execution. +func XChachaKeyTemplateWithCEK(cek []byte) *tinkpb.KeyTemplate { + return createKeyTemplate(false, false, 0, cek) +} + +// createKeyTemplate creates a new ECDH-AEAD key template with the set cek for primitive execution. Boolean flags used: +// - nistpKW flag to state if kw is either NIST P curves (true) or Curve25519 (false) +// - aesEnc flag to state if content encryption is either AES256-GCM (true) or XChacha20Poly1305 (false) +func createKeyTemplate(nistpKW, aesEnc bool, c commonpb.EllipticCurveType, cek []byte) *tinkpb.KeyTemplate { + var encTemplate *tinkpb.KeyTemplate + + typeURL, keyType := getTypeParams(nistpKW, aesEnc) + + if aesEnc { + encTemplate = aead.AES256GCMKeyTemplate() + } else { + encTemplate = aead.XChaCha20Poly1305KeyTemplate() + } -// createKeyTemplate creates a new ECDH-AEAD key template with the set cek for primitive execution. -func createKeyTemplate(c commonpb.EllipticCurveType, cek []byte) *tinkpb.KeyTemplate { format := &ecdhpb.EcdhAeadKeyFormat{ Params: &ecdhpb.EcdhAeadParams{ KwParams: &ecdhpb.EcdhKwParams{ CurveType: c, - KeyType: ecdhpb.KeyType_EC, + KeyType: keyType, }, EncParams: &ecdhpb.EcdhAeadEncParams{ - AeadEnc: aead.AES256GCMKeyTemplate(), + AeadEnc: encTemplate, CEK: cek, }, EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, @@ -80,8 +158,24 @@ func createKeyTemplate(c commonpb.EllipticCurveType, cek []byte) *tinkpb.KeyTemp } return &tinkpb.KeyTemplate{ - TypeUrl: ecdhAESPrivateKeyTypeURL, + TypeUrl: typeURL, Value: serializedFormat, OutputPrefixType: tinkpb.OutputPrefixType_RAW, } } + +func getTypeParams(nispKW, aesEnc bool) (string, ecdhpb.KeyType) { + if nispKW { + if aesEnc { + return ecdhNISTPAESPrivateKeyTypeURL, ecdhpb.KeyType_EC + } + + return ecdhNISTPXChachaPrivateKeyTypeURL, ecdhpb.KeyType_EC + } + + if aesEnc { + return ecdhX25519AESPrivateKeyTypeURL, ecdhpb.KeyType_OKP + } + + return ecdhX25519XChachaPrivateKeyTypeURL, ecdhpb.KeyType_OKP +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template_test.go index 0782007124..6b6a95fcbd 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template_test.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_key_template_test.go @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 package ecdh import ( + "strings" "testing" "github.com/google/tink/go/keyset" @@ -17,24 +18,40 @@ import ( func TestECDHESKeyTemplateSuccess(t *testing.T) { flagTests := []struct { - tcName string - curveType string - tmplFunc func() *tinkpb.KeyTemplate + tcName string + tmplFunc func() *tinkpb.KeyTemplate }{ { - tcName: "create ECDH AES-GCM 256 key templates test", - curveType: "P-256", - tmplFunc: ECDH256KWAES256GCMKeyTemplate, + tcName: "create ECDH NIST P-256 KW with AES256-GCM key templates test", + tmplFunc: ECDH256KWAES256GCMKeyTemplate, }, { - tcName: "create ECDH AES-GCM 384 key templates test", - curveType: "P-384", - tmplFunc: ECDH384KWAES256GCMKeyTemplate, + tcName: "create ECDH NIST P-384 KW AES256-GCM key templates test", + tmplFunc: ECDH384KWAES256GCMKeyTemplate, }, { - tcName: "create ECDH AES-GCM 521 key templates test", - curveType: "P-521", - tmplFunc: ECDH521KWAES256GCMKeyTemplate, + tcName: "create ECDH NIST P-521 KW AES256-GCM key templates test", + tmplFunc: ECDH521KWAES256GCMKeyTemplate, + }, + { + tcName: "creat ECDH X25519 KW with XChacha20Poly1305 key templates test", + tmplFunc: X25519XChachaECDHKeyTemplate, + }, + { + tcName: "create ECDH NIST P-256 KW with XChacha20Poly1305 key templates test", + tmplFunc: ECDH256KWXChachaKeyTemplate, + }, + { + tcName: "create ECDH NIST P-384 KW XChacha20Poly1305 key templates test", + tmplFunc: ECDH384KWXChachaKeyTemplate, + }, + { + tcName: "create ECDH NIST P-521 KW key XChacha20Poly1305 templates test", + tmplFunc: ECDH521KWXChachaKeyTemplate, + }, + { + tcName: "creat ECDH X25519 KW with AES256-GCM key templates test", + tmplFunc: X25519AES256GCMECDHKeyTemplate, }, } @@ -63,7 +80,11 @@ func TestECDHESKeyTemplateSuccess(t *testing.T) { require.Empty(t, ct) // now try to create a new KH for primitive execution and try to encrypt - kt = AES256GCMKeyTemplateWithCEK(cek) + if strings.Contains(tc.tcName, "XChacha") { + kt = XChachaKeyTemplateWithCEK(cek) + } else { + kt = AES256GCMKeyTemplateWithCEK(cek) + } kh, err = keyset.NewHandle(kt) require.NoError(t, err) diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager.go similarity index 52% rename from pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager.go rename to pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager.go index f7f984765c..f9f5e4595e 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager.go @@ -23,49 +23,50 @@ import ( ) const ( - ecdhAESPrivateKeyVersion = 0 - ecdhAESPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAesAeadPrivateKey" + ecdhNISTPAESPrivateKeyVersion = 0 + ecdhNISTPAESPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPrivateKey" ) // common errors. var ( - errInvalidECDHAESPrivateKey = errors.New("ecdh_aes_private_key_manager: invalid key") - errInvalidECDHAESPrivateKeyFormat = errors.New("ecdh_aes_private_key_manager: invalid key format") + errInvalidECDHNISTPAESPrivateKey = errors.New("ecdh_nistpkw_aesaead_private_key_manager: invalid key") + errInvalidECDHNISTPAESPrivateKeyFormat = errors.New("ecdh_nistpkw_aesaead_private_key_manager: invalid key format") // nolint:lll ) -// ecdhAESPrivateKeyManager is an implementation of PrivateKeyManager interface. -// It generates new ECDHPrivateKey (AES) keys and produces new instances of ECDHAEADCompositeDecrypt subtle. -type ecdhAESPrivateKeyManager struct{} +// ecdhNISTPAESPrivateKeyManager is an implementation of PrivateKeyManager interface for NIST P curved key wrapping and +// AES-GCM content encryption. +// It generates new ECDHPrivateKey (NIST P KW) keys and produces new instances of ECDHAEADCompositeDecrypt subtle. +type ecdhNISTPAESPrivateKeyManager struct{} -// Assert that ecdhAESPrivateKeyManager implements the PrivateKeyManager interface. -var _ registry.PrivateKeyManager = (*ecdhAESPrivateKeyManager)(nil) +// Assert that ecdhNISTPAESPrivateKeyManager implements the PrivateKeyManager interface. +var _ registry.PrivateKeyManager = (*ecdhNISTPAESPrivateKeyManager)(nil) -// newECDHPrivateKeyManager creates a new ecdhAESPrivateKeyManager. -func newECDHPrivateKeyManager() *ecdhAESPrivateKeyManager { - return new(ecdhAESPrivateKeyManager) +// newECDHNISTPAESPrivateKeyManager creates a new ecdhNISTPAESPrivateKeyManager. +func newECDHNISTPAESPrivateKeyManager() *ecdhNISTPAESPrivateKeyManager { + return new(ecdhNISTPAESPrivateKeyManager) } // Primitive creates an ECDHESPrivateKey subtle for the given serialized ECDHESPrivateKey proto. -func (km *ecdhAESPrivateKeyManager) Primitive(serializedKey []byte) (interface{}, error) { +func (km *ecdhNISTPAESPrivateKeyManager) Primitive(serializedKey []byte) (interface{}, error) { if len(serializedKey) == 0 { - return nil, errInvalidECDHAESPrivateKey + return nil, errInvalidECDHNISTPAESPrivateKey } key := new(ecdhpb.EcdhAeadPrivateKey) err := proto.Unmarshal(serializedKey, key) if err != nil { - return nil, errInvalidECDHAESPrivateKey + return nil, errInvalidECDHNISTPAESPrivateKey } _, err = km.validateKey(key) if err != nil { - return nil, errInvalidECDHAESPrivateKey + return nil, errInvalidECDHNISTPAESPrivateKey } rEnc, err := composite.NewRegisterCompositeAEADEncHelper(key.PublicKey.Params.EncParams.AeadEnc) if err != nil { - return nil, fmt.Errorf("ecdh_aes_private_key_manager: NewRegisterCompositeAEADEncHelper "+ + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: NewRegisterCompositeAEADEncHelper "+ "failed: %w", err) } @@ -73,33 +74,33 @@ func (km *ecdhAESPrivateKeyManager) Primitive(serializedKey []byte) (interface{} } // NewKey creates a new key according to the specification of ECDHESPrivateKey format. -func (km *ecdhAESPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { +func (km *ecdhNISTPAESPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { if len(serializedKeyFormat) == 0 { - return nil, errInvalidECDHAESPrivateKeyFormat + return nil, errInvalidECDHNISTPAESPrivateKeyFormat } keyFormat := new(ecdhpb.EcdhAeadKeyFormat) err := proto.Unmarshal(serializedKeyFormat, keyFormat) if err != nil { - return nil, errInvalidECDHAESPrivateKeyFormat + return nil, errInvalidECDHNISTPAESPrivateKeyFormat } curve, err := validateKeyFormat(keyFormat.Params) if err != nil { - return nil, errInvalidECDHAESPrivateKeyFormat + return nil, errInvalidECDHNISTPAESPrivateKeyFormat } pvt, err := hybrid.GenerateECDHKeyPair(curve) if err != nil { - return nil, fmt.Errorf("ecdh_aes_private_key_manager: GenerateECDHKeyPair failed: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: GenerateECDHKeyPair failed: %w", err) } return &ecdhpb.EcdhAeadPrivateKey{ - Version: ecdhAESPrivateKeyVersion, + Version: ecdhNISTPAESPrivateKeyVersion, KeyValue: pvt.D.Bytes(), PublicKey: &ecdhpb.EcdhAeadPublicKey{ - Version: ecdhAESPrivateKeyVersion, + Version: ecdhNISTPAESPrivateKeyVersion, Params: keyFormat.Params, X: pvt.PublicKey.Point.X.Bytes(), Y: pvt.PublicKey.Point.Y.Bytes(), @@ -109,7 +110,7 @@ func (km *ecdhAESPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Me // NewKeyData creates a new KeyData according to the specification of ECDHESPrivateKey Format. // It should be used solely by the key management API. -func (km *ecdhAESPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { +func (km *ecdhNISTPAESPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { key, err := km.NewKey(serializedKeyFormat) if err != nil { return nil, err @@ -117,52 +118,52 @@ func (km *ecdhAESPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tin serializedKey, err := proto.Marshal(key) if err != nil { - return nil, fmt.Errorf("ecdhes_aes_private_key_manager: Proto.Marshal failed: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: Proto.Marshal failed: %w", err) } return &tinkpb.KeyData{ - TypeUrl: ecdhAESPrivateKeyTypeURL, + TypeUrl: ecdhNISTPAESPrivateKeyTypeURL, Value: serializedKey, KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, }, nil } // PublicKeyData returns the enclosed public key data of serializedPrivKey. -func (km *ecdhAESPrivateKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { +func (km *ecdhNISTPAESPrivateKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { privKey := new(ecdhpb.EcdhAeadPrivateKey) err := proto.Unmarshal(serializedPrivKey, privKey) if err != nil { - return nil, errInvalidECDHAESPrivateKey + return nil, errInvalidECDHNISTPAESPrivateKey } serializedPubKey, err := proto.Marshal(privKey.PublicKey) if err != nil { - return nil, errInvalidECDHAESPrivateKey + return nil, errInvalidECDHNISTPAESPrivateKey } return &tinkpb.KeyData{ - TypeUrl: ecdhAESPublicKeyTypeURL, + TypeUrl: ecdhNISTPAESPublicKeyTypeURL, Value: serializedPubKey, KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC, }, nil } // DoesSupport indicates if this key manager supports the given key type. -func (km *ecdhAESPrivateKeyManager) DoesSupport(typeURL string) bool { - return typeURL == ecdhAESPrivateKeyTypeURL +func (km *ecdhNISTPAESPrivateKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhNISTPAESPrivateKeyTypeURL } // TypeURL returns the key type of keys managed by this key manager. -func (km *ecdhAESPrivateKeyManager) TypeURL() string { - return ecdhAESPrivateKeyTypeURL +func (km *ecdhNISTPAESPrivateKeyManager) TypeURL() string { + return ecdhNISTPAESPrivateKeyTypeURL } // validateKey validates the given ECDHPrivateKey and returns the KW curve. -func (km *ecdhAESPrivateKeyManager) validateKey(key *ecdhpb.EcdhAeadPrivateKey) (elliptic.Curve, error) { - err := keyset.ValidateKeyVersion(key.Version, ecdhAESPrivateKeyVersion) +func (km *ecdhNISTPAESPrivateKeyManager) validateKey(key *ecdhpb.EcdhAeadPrivateKey) (elliptic.Curve, error) { + err := keyset.ValidateKeyVersion(key.Version, ecdhNISTPAESPrivateKeyVersion) if err != nil { - return nil, fmt.Errorf("ecdhes_aes_private_key_manager: invalid key: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: invalid key: %w", err) } return validateKeyFormat(key.PublicKey.Params) @@ -180,7 +181,7 @@ func validateKeyFormat(params *ecdhpb.EcdhAeadParams) (elliptic.Curve, error) { if params.EncParams.CEK == nil { c, err = hybrid.GetCurve(params.KwParams.CurveType.String()) if err != nil { - return nil, fmt.Errorf("ecdhes_aes_private_key_manager: invalid key: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: invalid key: %w", err) } } else { c = elliptic.P384() @@ -188,12 +189,17 @@ func validateKeyFormat(params *ecdhpb.EcdhAeadParams) (elliptic.Curve, error) { km, err := registry.GetKeyManager(params.EncParams.AeadEnc.TypeUrl) if err != nil { - return nil, fmt.Errorf("ecdhes_aes_private_key_manager: GetKeyManager error: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: GetKeyManager error: %w", err) } _, err = km.NewKeyData(params.EncParams.AeadEnc.Value) if err != nil { - return nil, fmt.Errorf("ecdhes_aes_private_key_manager: NewKeyData error: %w", err) + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: NewKeyData error: %w", err) + } + + if params.KwParams.KeyType.String() != ecdhpb.KeyType_EC.String() { + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_private_key_manager: invalid key type %v", + params.KwParams.KeyType) } return c, nil diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager_test.go similarity index 74% rename from pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager_test.go rename to pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager_test.go index e927d4782b..ddf6740b60 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_private_key_manager_test.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_private_key_manager_test.go @@ -25,20 +25,20 @@ import ( ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" ) -func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { - km := newECDHPrivateKeyManager() +func TestECDHNISTPAESPrivateKeyManager_Primitive(t *testing.T) { + km := newECDHNISTPAESPrivateKeyManager() t.Run("Test private key manager Primitive() with empty serialized key", func(t *testing.T) { p, err := km.Primitive([]byte("")) - require.EqualError(t, err, errInvalidECDHAESPrivateKey.Error(), - "ECDHESPrivate primitive from empty serialized key must fail") + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKey.Error(), + "ecdhNISTPAESPrivateKeyManager primitive from empty serialized key must fail") require.Empty(t, p) }) t.Run("Test private key manager Primitive() with bad serialize key", func(t *testing.T) { p, err := km.Primitive([]byte("bad.data")) - require.EqualError(t, err, errInvalidECDHAESPrivateKey.Error(), - "ECDHESPrivate primitive from bad serialized key must fail") + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKey.Error(), + "ecdhNISTPAESPrivateKeyManager primitive from bad serialized key must fail") require.Empty(t, p) }) @@ -59,6 +59,7 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName string version uint32 curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType ecPtFmt commonpb.EcPointFormat encTmp *tinkpb.KeyTemplate }{ @@ -66,6 +67,7 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName: "private key manager Primitive() using key with bad version", version: 9999, curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: aead.AES128GCMKeyTemplate(), }, @@ -73,6 +75,15 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName: "private key manager Primitive() using key with bad curve", version: 0, curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.AES128GCMKeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: aead.AES128GCMKeyTemplate(), }, @@ -80,6 +91,7 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName: "success private key manager Primitive()", version: 0, curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_UNCOMPRESSED, encTmp: aead.AES128GCMKeyTemplate(), }, @@ -87,6 +99,7 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName: "private key manager Primitive() using key with bad key template URL", version: 0, curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: &tinkpb.KeyTemplate{ TypeUrl: "bad.type/url/value", @@ -98,6 +111,7 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { tcName: "private key manager Primitive() using key with bad dem key size", version: 0, curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: &tinkpb.KeyTemplate{ TypeUrl: composite.AESGCMTypeURL, @@ -130,13 +144,14 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { c = tt.curveType } - pubKeyProto := &ecdhpb.EcdhAeadPrivateKey{ + privKeyProto := &ecdhpb.EcdhAeadPrivateKey{ Version: v, PublicKey: &ecdhpb.EcdhAeadPublicKey{ Version: v, // if v > 0 to force an error when calling km.Primitive() Params: &ecdhpb.EcdhAeadParams{ KwParams: &ecdhpb.EcdhKwParams{ - CurveType: c, // unknown curve type to force an error when calling km.Primitive() + CurveType: c, // unknown curve type to force an error when calling km.Primitive() + KeyType: tt.keyType, // invalid key type to force error when calling km.Primitive() }, EncParams: &ecdhpb.EcdhAeadEncParams{ AeadEnc: encT, // invalid data enc key template to get an error when calling km.Primitive() @@ -149,13 +164,13 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { KeyValue: d, } - sPubKey, err := proto.Marshal(pubKeyProto) + sPrivKey, err := proto.Marshal(privKeyProto) require.NoError(t, err) - p, err := km.Primitive(sPubKey) + p, err := km.Primitive(sPrivKey) if bytes.Equal(tt.encTmp.Value, badSerializedFormat) { - require.EqualError(t, err, errInvalidECDHAESPrivateKey.Error(), - "ECDHESPrivate primitive from serialized key with invalid serialized key") + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKey.Error(), + "ecdhNISTPAESPrivateKeyManager primitive from serialized key with invalid serialized key") require.Empty(t, p) return @@ -173,24 +188,24 @@ func TestECDHESPrivateKeyManager_Primitive(t *testing.T) { } } -func TestEcdhesPrivateKeyManager_DoesSupport(t *testing.T) { - km := newECDHPrivateKeyManager() +func TestEcdhNISTPAESPrivateKeyManager_DoesSupport(t *testing.T) { + km := newECDHNISTPAESPrivateKeyManager() require.False(t, km.DoesSupport("bad/url")) - require.True(t, km.DoesSupport(ecdhAESPrivateKeyTypeURL)) + require.True(t, km.DoesSupport(ecdhNISTPAESPrivateKeyTypeURL)) } -func TestEcdhesPrivateKeyManager_NewKey(t *testing.T) { - km := newECDHPrivateKeyManager() +func TestEcdhNISTPAESPrivateKeyManager_NewKey(t *testing.T) { + km := newECDHNISTPAESPrivateKeyManager() t.Run("Test private key manager NewKey() with nil key", func(t *testing.T) { k, err := km.NewKey(nil) - require.EqualError(t, err, errInvalidECDHAESPrivateKeyFormat.Error()) + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKeyFormat.Error()) require.Empty(t, k) }) t.Run("Test private key manager NewKey() with bad serialize key", func(t *testing.T) { p, err := km.NewKey([]byte("bad.data")) - require.EqualError(t, err, errInvalidECDHAESPrivateKeyFormat.Error(), + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKeyFormat.Error(), "ECDHESPrivate NewKey() from bad serialized key must fail") require.Empty(t, p) }) @@ -212,24 +227,35 @@ func TestEcdhesPrivateKeyManager_NewKey(t *testing.T) { flagTests := []struct { tcName string curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType ecPtFmt commonpb.EcPointFormat encTmp *tinkpb.KeyTemplate }{ { tcName: "success private key manager NewKey() and NewKeyData()", curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: aead.AES128GCMKeyTemplate(), }, { tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: aead.AES128GCMKeyTemplate(), }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key type", + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.AES256GCMKeyTemplate(), + }, { tcName: "private key manager NewKey() and NewKeyData() using key with bad key template URL", curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: &tinkpb.KeyTemplate{ TypeUrl: "bad.type/url/value", @@ -240,6 +266,7 @@ func TestEcdhesPrivateKeyManager_NewKey(t *testing.T) { { tcName: "private key manager NewKey() and NewKeyData() using key with bad dem key size", curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, ecPtFmt: commonpb.EcPointFormat_COMPRESSED, encTmp: &tinkpb.KeyTemplate{ TypeUrl: composite.AESGCMTypeURL, @@ -252,19 +279,19 @@ func TestEcdhesPrivateKeyManager_NewKey(t *testing.T) { for _, tc := range flagTests { tt := tc t.Run("Test "+tt.tcName, func(t *testing.T) { - c := tt.curveType encT := tt.encTmp ptFmt := tt.ecPtFmt privKeyProto := &ecdhpb.EcdhAeadKeyFormat{ Params: &ecdhpb.EcdhAeadParams{ KwParams: &ecdhpb.EcdhKwParams{ - CurveType: c, // unknown curve type to force an error when calling km.Primitive() + CurveType: tt.curveType, // unknown curve type to force an error when calling km.NewKey() + KeyType: tt.keyType, // unknown curve type to force an error when calling km.NewKey() }, EncParams: &ecdhpb.EcdhAeadEncParams{ - AeadEnc: encT, // invalid data enc key template to force an error when calling km.Primitive() + AeadEnc: encT, // invalid data enc key template to force an error when calling km.NewKey() }, - EcPointFormat: ptFmt, // unknown EC Point format type to force an error when calling km.Primitive() + EcPointFormat: ptFmt, // unknown EC Point format type to force an error when calling km.NewKey() }, } @@ -295,14 +322,14 @@ func TestEcdhesPrivateKeyManager_NewKey(t *testing.T) { if strings.Contains(tt.tcName, "success") { require.NoError(t, err) require.NotEmpty(t, kd) - require.Equal(t, kd.TypeUrl, ecdhAESPrivateKeyTypeURL) + require.Equal(t, kd.TypeUrl, ecdhNISTPAESPrivateKeyTypeURL) require.Equal(t, kd.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PRIVATE) return } if bytes.Equal(tt.encTmp.Value, badSerializedFormat) { - require.EqualError(t, err, errInvalidECDHAESPrivateKeyFormat.Error(), - "ECDHESPrivate NewKey from serialized key with invalid serialized key") + require.EqualError(t, err, errInvalidECDHNISTPAESPrivateKeyFormat.Error(), + "ecdhNISTPAESPrivateKeyManager NewKey from serialized key with invalid serialized key") require.Empty(t, p) return diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager.go new file mode 100644 index 0000000000..6e664fcd8a --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager.go @@ -0,0 +1,100 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/elliptic" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +const ( + ecdhNISTPAESPublicKeyVersion = 0 + ecdhNISTPAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPublicKey" +) + +// common errors. +var errInvalidECDHNISTPAESPublicKey = errors.New("ecdh_nistpkw_aesaead_public_key_manager: invalid key") + +// ecdhNISTPAESPublicKeyManager is an implementation of KeyManager interface for NIST P curved key wrapping and +// AES-GCM content encryption. +// It generates new ECDHPublicKey (AES) keys and produces new instances of ECDHAEADCompositeEncrypt subtle. +type ecdhNISTPAESPublicKeyManager struct{} + +// Assert that ecdhNISTPAESPublicKeyManager implements the KeyManager interface. +var _ registry.KeyManager = (*ecdhNISTPAESPublicKeyManager)(nil) + +// newECDHNISTPAESPublicKeyManager creates a new ecdhNISTPAESPublicKeyManager. +func newECDHNISTPAESPublicKeyManager() *ecdhNISTPAESPublicKeyManager { + return new(ecdhNISTPAESPublicKeyManager) +} + +// Primitive creates an ECDHESPublicKey subtle for the given serialized ECDHESPublicKey proto. +func (km *ecdhNISTPAESPublicKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHNISTPAESPublicKey + } + + ecdhPubKey := new(ecdhpb.EcdhAeadPublicKey) + + err := proto.Unmarshal(serializedKey, ecdhPubKey) + if err != nil { + return nil, errInvalidECDHNISTPAESPublicKey + } + + _, err = km.validateKey(ecdhPubKey) + if err != nil { + return nil, errInvalidECDHNISTPAESPublicKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(ecdhPubKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_public_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeEncrypt(rEnc, ecdhPubKey.Params.EncParams.CEK), nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhNISTPAESPublicKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhNISTPAESPublicKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhNISTPAESPublicKeyManager) TypeURL() string { + return ecdhNISTPAESPublicKeyTypeURL +} + +// NewKey is not implemented for public key manager. +func (km *ecdhNISTPAESPublicKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + return nil, errors.New("ecdh_nistpkw_aesaead_public_key_manager: NewKey not implemented") +} + +// NewKeyData is not implemented for public key manager. +func (km *ecdhNISTPAESPublicKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + return nil, errors.New("ecdh_nistpkw_aesaead_public_key_manager: NewKeyData not implemented") +} + +// validateKey validates the given EcdhAeadPublicKey. +func (km *ecdhNISTPAESPublicKeyManager) validateKey(key *ecdhpb.EcdhAeadPublicKey) (elliptic.Curve, error) { + err := keyset.ValidateKeyVersion(key.Version, ecdhNISTPAESPublicKeyVersion) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_aesaead_public_key_manager: invalid key: %w", err) + } + + return validateKeyFormat(key.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager_test.go similarity index 82% rename from pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager_test.go rename to pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager_test.go index 7bdc0c50d3..1fc6927ad0 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_aes_aead_public_key_manager_test.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_aesaead_public_key_manager_test.go @@ -24,20 +24,20 @@ import ( ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" ) -func TestECDHPublicKeyManager_Primitive(t *testing.T) { - km := newECDHPublicKeyManager() +func TestECDHNISTPAESPublicKeyManager_Primitive(t *testing.T) { + km := newECDHNISTPAESPublicKeyManager() t.Run("Test public key manager Primitive() with empty serialized key", func(t *testing.T) { p, err := km.Primitive([]byte("")) - require.EqualError(t, err, errInvalidECDHAESPublicKey.Error(), - "ECDHPublic primitive from empty serialized key must fail") + require.EqualError(t, err, errInvalidECDHNISTPAESPublicKey.Error(), + "newECDHNISTPAESPublicKeyManager primitive from empty serialized key must fail") require.Empty(t, p) }) t.Run("Test public key manager Primitive() with bad serialize key", func(t *testing.T) { p, err := km.Primitive([]byte("bad.data")) - require.EqualError(t, err, errInvalidECDHAESPublicKey.Error(), - "ECDHPublic primitive from bad serialized key must fail") + require.EqualError(t, err, errInvalidECDHNISTPAESPublicKey.Error(), + "newECDHNISTPAESPublicKeyManager primitive from bad serialized key must fail") require.Empty(t, p) }) @@ -150,8 +150,8 @@ func TestECDHPublicKeyManager_Primitive(t *testing.T) { p, err := km.Primitive(sPubKey) if strings.Contains(tt.tcName, "with bad content encryption key size") { - require.EqualError(t, err, errInvalidECDHAESPublicKey.Error(), - "ECDHPublic primitive from serialized key with invalid serialized key") + require.EqualError(t, err, errInvalidECDHNISTPAESPublicKey.Error(), + "newECDHNISTPAESPublicKeyManager primitive from serialized key with invalid serialized key") require.Empty(t, p) return @@ -169,24 +169,24 @@ func TestECDHPublicKeyManager_Primitive(t *testing.T) { } } -func TestEcdhPublicKeyManager_DoesSupport(t *testing.T) { - km := newECDHPublicKeyManager() +func TestEcdhNISTPAESPublicKeyManager_DoesSupport(t *testing.T) { + km := newECDHNISTPAESPublicKeyManager() require.False(t, km.DoesSupport("bad/url")) - require.True(t, km.DoesSupport(ecdhAESPublicKeyTypeURL)) + require.True(t, km.DoesSupport(ecdhNISTPAESPublicKeyTypeURL)) } -func TestEcdhPublicKeyManager_NewKeyAndNewKeyData(t *testing.T) { - km := newECDHPublicKeyManager() +func TestEcdhNISTPAESPublicKeyManager_NewKeyAndNewKeyData(t *testing.T) { + km := newECDHNISTPAESPublicKeyManager() t.Run("Test public key manager NewKey()", func(t *testing.T) { k, err := km.NewKey(nil) - require.EqualError(t, err, "ecdh_aes_public_key_manager: NewKey not implemented") + require.EqualError(t, err, "ecdh_nistpkw_aesaead_public_key_manager: NewKey not implemented") require.Empty(t, k) }) t.Run("Test private key manager NewKeyData()", func(t *testing.T) { p, err := km.NewKeyData(nil) - require.EqualError(t, err, "ecdh_aes_public_key_manager: NewKeyData not implemented") + require.EqualError(t, err, "ecdh_nistpkw_aesaead_public_key_manager: NewKeyData not implemented") require.Empty(t, p) }) } diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager.go new file mode 100644 index 0000000000..cbf0327295 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager.go @@ -0,0 +1,170 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/elliptic" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + hybrid "github.com/google/tink/go/hybrid/subtle" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +const ( + ecdhNISTPXChachaPrivateKeyVersion = 0 + ecdhNISTPXChachaPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwXChachaAeadPrivateKey" // nolint:lll +) + +// common errors. +var ( + errInvalidECDHNISTPXChachaPrivateKey = errors.New("ecdh_nistpkw_xchachaaead_private_key_manager: invalid key") // nolint:lll + errInvalidECDHNISTPXChachaPrivateKeyFormat = errors.New("ecdh_nistpkw_xchachaaead_private_key_manager: invalid key format") // nolint:lll +) + +// ecdhNISTPXChachaPrivateKeyManager is an implementation of PrivateKeyManager interface for NIST P curved key wrapping +// and XChacha20Poly1305 content encryption. +// It generates new ECDHPrivateKey (NIST P KW) keys and produces new instances of ECDHAEADCompositeDecrypt subtle. +type ecdhNISTPXChachaPrivateKeyManager struct{} + +// Assert that ecdhNISTPXChachaPrivateKeyManager implements the PrivateKeyManager interface. +var _ registry.PrivateKeyManager = (*ecdhNISTPXChachaPrivateKeyManager)(nil) + +// newECDHNISTPXChachaPrivateKeyManager creates a new ecdhNISTPXChachaPrivateKeyManager. +func newECDHNISTPXChachaPrivateKeyManager() *ecdhNISTPXChachaPrivateKeyManager { + return new(ecdhNISTPXChachaPrivateKeyManager) +} + +// Primitive creates an ECDHESPrivateKey subtle for the given serialized ECDHESPrivateKey proto. +func (km *ecdhNISTPXChachaPrivateKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHNISTPXChachaPrivateKey + } + + key := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedKey, key) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKey + } + + _, err = km.validateKey(key) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(key.PublicKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_private_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeDecrypt(rEnc, key.PublicKey.Params.EncParams.CEK), nil +} + +// NewKey creates a new key according to the specification of ECDHESPrivateKey format. +func (km *ecdhNISTPXChachaPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + if len(serializedKeyFormat) == 0 { + return nil, errInvalidECDHNISTPXChachaPrivateKeyFormat + } + + keyFormat := new(ecdhpb.EcdhAeadKeyFormat) + + err := proto.Unmarshal(serializedKeyFormat, keyFormat) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKeyFormat + } + + curve, err := validateKeyFormat(keyFormat.Params) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKeyFormat + } + + pvt, err := hybrid.GenerateECDHKeyPair(curve) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_private_key_manager: GenerateECDHKeyPair failed: %w", err) + } + + return &ecdhpb.EcdhAeadPrivateKey{ + Version: ecdhNISTPXChachaPrivateKeyVersion, + KeyValue: pvt.D.Bytes(), + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: ecdhNISTPXChachaPrivateKeyVersion, + Params: keyFormat.Params, + X: pvt.PublicKey.Point.X.Bytes(), + Y: pvt.PublicKey.Point.Y.Bytes(), + }, + }, nil +} + +// NewKeyData creates a new KeyData according to the specification of ECDHESPrivateKey Format. +// It should be used solely by the key management API. +func (km *ecdhNISTPXChachaPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + key, err := km.NewKey(serializedKeyFormat) + if err != nil { + return nil, err + } + + serializedKey, err := proto.Marshal(key) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_private_key_manager: Proto.Marshal failed: %w", err) + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhNISTPXChachaPrivateKeyTypeURL, + Value: serializedKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, + }, nil +} + +// PublicKeyData returns the enclosed public key data of serializedPrivKey. +func (km *ecdhNISTPXChachaPrivateKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { + privKey := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedPrivKey, privKey) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKey + } + + serializedPubKey, err := proto.Marshal(privKey.PublicKey) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPrivateKey + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhNISTPXChachaPublicKeyTypeURL, + Value: serializedPubKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC, + }, nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhNISTPXChachaPrivateKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhNISTPXChachaPrivateKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhNISTPXChachaPrivateKeyManager) TypeURL() string { + return ecdhNISTPXChachaPrivateKeyTypeURL +} + +// validateKey validates the given ECDHPrivateKey and returns the KW curve. +func (km *ecdhNISTPXChachaPrivateKeyManager) validateKey(key *ecdhpb.EcdhAeadPrivateKey) (elliptic.Curve, error) { + err := keyset.ValidateKeyVersion(key.Version, ecdhNISTPXChachaPrivateKeyVersion) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_private_key_manager: invalid key: %w", err) + } + + return validateKeyFormat(key.PublicKey.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager_test.go new file mode 100644 index 0000000000..5f2654dc8f --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_private_key_manager_test.go @@ -0,0 +1,271 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/elliptic" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + hybrid "github.com/google/tink/go/hybrid/subtle" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +func TestECDHNISTPXChachaPrivateKeyManager_Primitive(t *testing.T) { + km := newECDHNISTPXChachaPrivateKeyManager() + + t.Run("Test private key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPrivateKey.Error(), + "ecdhNISTPXChachaPrivateKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test private key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPrivateKey.Error(), + "ecdhNISTPXChachaPrivateKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + ecPtFmt commonpb.EcPointFormat + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "private key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success private key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_UNCOMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + c := tt.curveType + encT := tt.encTmp + ptFmt := tt.ecPtFmt + v := tt.version + + // temporarily reset curvType if its unknown type so subtle.GetCurve() below doesn't fail + if tt.curveType.String() == commonpb.EllipticCurveType_UNKNOWN_CURVE.String() { + c = commonpb.EllipticCurveType_NIST_P256 + } + + crv, err := hybrid.GetCurve(c.String()) + require.NoError(t, err) + d, x, y, err := elliptic.GenerateKey(crv, rand.Reader) + require.NoError(t, err) + + // set back curvType if it was unknown to proceed with the test + if tt.curveType.String() == commonpb.EllipticCurveType_UNKNOWN_CURVE.String() { + c = tt.curveType + } + + privKeyProto := &ecdhpb.EcdhAeadPrivateKey{ + Version: v, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: v, // if v > 0 to force an error when calling km.Primitive() + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: c, // unknown curve type to force an error when calling km.Primitive() + KeyType: tt.keyType, // invalid key type to force error when calling km.Primitive() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: encT, // invalid data enc key template to get an error when calling km.Primitive() + }, + EcPointFormat: ptFmt, + }, + X: x.Bytes(), + Y: y.Bytes(), + }, + KeyValue: d, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestEcdhNISTPXChachaPrivateKeyManager_DoesSupport(t *testing.T) { + km := newECDHNISTPXChachaPrivateKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhNISTPXChachaPrivateKeyTypeURL)) +} + +func TestEcdhNISTPXChachaPrivateKeyManager_NewKey(t *testing.T) { + km := newECDHNISTPXChachaPrivateKeyManager() + + t.Run("Test private key manager NewKey() with nil key", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPrivateKeyFormat.Error()) + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKey() with bad serialize key", func(t *testing.T) { + p, err := km.NewKey([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPrivateKeyFormat.Error(), + "ecdhNISTPXChachaPrivateKeyManager NewKey() from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + ecPtFmt commonpb.EcPointFormat + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "success private key manager NewKey() and NewKeyData()", + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key type", + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key template URL", + curveType: commonpb.EllipticCurveType_NIST_P256, + keyType: ecdhpb.KeyType_EC, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + encT := tt.encTmp + ptFmt := tt.ecPtFmt + + privKeyProto := &ecdhpb.EcdhAeadKeyFormat{ + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve type to force an error when calling km.NewKey() + KeyType: tt.keyType, // unknown curve type to force an error when calling km.NewKey() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: encT, // invalid data enc key template to force an error when calling km.NewKey() + }, + EcPointFormat: ptFmt, // unknown EC Point format type to force an error when calling km.NewKey() + }, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.NewKey(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + + sp, e := proto.Marshal(p) + require.NoError(t, e) + require.NotEmpty(t, sp) + + // try PublicKeyData() with bad serialized private key + pubK, e := km.PublicKeyData([]byte("bad serialized private key")) + require.Error(t, e) + require.Empty(t, pubK) + + // try PublicKeyData() with valid serialized private key + pubK, e = km.PublicKeyData(sp) + require.NoError(t, e) + require.NotEmpty(t, pubK) + } + + kd, err := km.NewKeyData(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, kd) + require.Equal(t, kd.TypeUrl, ecdhNISTPXChachaPrivateKeyTypeURL) + require.Equal(t, kd.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PRIVATE) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager.go new file mode 100644 index 0000000000..5eabe0b68e --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager.go @@ -0,0 +1,100 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/elliptic" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +const ( + ecdhNISTPXChachaPublicKeyVersion = 0 + ecdhNISTPXChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwXChachaAeadPublicKey" // nolint:lll +) + +// common errors. +var errInvalidECDHNISTPXChachaPublicKey = errors.New("ecdh_nistpkw_xchachaaead_public_key_manager: invalid key") + +// ecdhNISTPXChachaPublicKeyManager is an implementation of KeyManager interface for NIST P curved key wrapping and +// XChacha20Poly1305 content encryption. +// It generates new ECDHPublicKey (X25519) keys and produces new instances of ECDHAEADCompositeEncrypt subtle. +type ecdhNISTPXChachaPublicKeyManager struct{} + +// Assert that ecdhNISTPXChachaPublicKeyManager implements the KeyManager interface. +var _ registry.KeyManager = (*ecdhNISTPXChachaPublicKeyManager)(nil) + +// newECDHNISTPXChachaPublicKeyManager creates a new ecdhNISTPXChachaPublicKeyManager. +func newECDHNISTPXChachaPublicKeyManager() *ecdhNISTPXChachaPublicKeyManager { + return new(ecdhNISTPXChachaPublicKeyManager) +} + +// Primitive creates an ECDHESPublicKey subtle for the given serialized ECDHESPublicKey proto. +func (km *ecdhNISTPXChachaPublicKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHNISTPXChachaPublicKey + } + + ecdhPubKey := new(ecdhpb.EcdhAeadPublicKey) + + err := proto.Unmarshal(serializedKey, ecdhPubKey) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPublicKey + } + + _, err = km.validateKey(ecdhPubKey) + if err != nil { + return nil, errInvalidECDHNISTPXChachaPublicKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(ecdhPubKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_public_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeEncrypt(rEnc, ecdhPubKey.Params.EncParams.CEK), nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhNISTPXChachaPublicKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhNISTPXChachaPublicKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhNISTPXChachaPublicKeyManager) TypeURL() string { + return ecdhNISTPXChachaPublicKeyTypeURL +} + +// NewKey is not implemented for public key manager. +func (km *ecdhNISTPXChachaPublicKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + return nil, errors.New("ecdh_nistpkw_xchachaaead_public_key_manager: NewKey not implemented") +} + +// NewKeyData is not implemented for public key manager. +func (km *ecdhNISTPXChachaPublicKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + return nil, errors.New("ecdh_nistpkw_xchachaaead_public_key_manager: NewKeyData not implemented") +} + +// validateKey validates the given EcdhAeadPublicKey. +func (km *ecdhNISTPXChachaPublicKeyManager) validateKey(key *ecdhpb.EcdhAeadPublicKey) (elliptic.Curve, error) { + err := keyset.ValidateKeyVersion(key.Version, ecdhNISTPXChachaPublicKeyVersion) + if err != nil { + return nil, fmt.Errorf("ecdh_nistpkw_xchachaaead_public_key_manager: invalid key: %w", err) + } + + return validateKeyFormat(key.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager_test.go new file mode 100644 index 0000000000..9f11c07822 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_nistpkw_xchachaaead_public_key_manager_test.go @@ -0,0 +1,165 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/elliptic" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + hybrid "github.com/google/tink/go/hybrid/subtle" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +func TestECDHNISTPXChachaPublicKeyManager_Primitive(t *testing.T) { + km := newECDHNISTPXChachaPublicKeyManager() + + t.Run("Test public key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPublicKey.Error(), + "ecdhNISTPXChachaPublicKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test public key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHNISTPXChachaPublicKey.Error(), + "ecdhNISTPXChachaPublicKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + ecPtFmt commonpb.EcPointFormat + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "public key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_NIST_P256, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success public key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + ecPtFmt: commonpb.EcPointFormat_UNCOMPRESSED, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_NIST_P256, + ecPtFmt: commonpb.EcPointFormat_COMPRESSED, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + c := tt.curveType + encT := tt.encTmp + ptFmt := tt.ecPtFmt + v := tt.version + + // temporarily reset curvType if its unknown type so subtle.GetCurve() below doesn't fail + if tt.curveType.String() == commonpb.EllipticCurveType_UNKNOWN_CURVE.String() { + c = commonpb.EllipticCurveType_NIST_P256 + } + + crv, err := hybrid.GetCurve(c.String()) + require.NoError(t, err) + _, x, y, err := elliptic.GenerateKey(crv, rand.Reader) + require.NoError(t, err) + + // set back curvType if it was unknown to proceed with the test + if tt.curveType.String() == commonpb.EllipticCurveType_UNKNOWN_CURVE.String() { + c = tt.curveType + } + + pubKeyProto := &ecdhpb.EcdhAeadPublicKey{ + Version: v, // if v > 0 to force an error when calling km.Primitive() + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: c, // unknown curve type to force an error when calling km.Primitive() + KeyType: ecdhpb.KeyType_EC, + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: encT, // invalid data enc key template to force an error when calling km.Primitive() + }, + EcPointFormat: ptFmt, // unknown EC Point format type to force an error when calling km.Primitive() + }, + X: x.Bytes(), + Y: y.Bytes(), + } + + sPubKey, err := proto.Marshal(pubKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPubKey) + if strings.Contains(tt.tcName, "with bad content encryption key size") { + require.EqualError(t, err, errInvalidECDHNISTPXChachaPublicKey.Error(), + "ecdhNISTPXChachaPublicKeyManager primitive from serialized key with invalid serialized key") + require.Empty(t, p) + + return + } + + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestEcdhNISTPXChachaPublicKeyManager_DoesSupport(t *testing.T) { + km := newECDHNISTPXChachaPublicKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhNISTPXChachaPublicKeyTypeURL)) +} + +func TestEcdhNISTPXChachaPublicKeyManager_NewKeyAndNewKeyData(t *testing.T) { + km := newECDHNISTPXChachaPublicKeyManager() + + t.Run("Test public key manager NewKey()", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, "ecdh_nistpkw_xchachaaead_public_key_manager: NewKey not implemented") + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKeyData()", func(t *testing.T) { + p, err := km.NewKeyData(nil) + require.EqualError(t, err, "ecdh_nistpkw_xchachaaead_public_key_manager: NewKeyData not implemented") + require.Empty(t, p) + }) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519_private_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519_private_key_manager.go deleted file mode 100644 index beb29793d7..0000000000 --- a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519_private_key_manager.go +++ /dev/null @@ -1,9 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package ecdh - -// TODO implement diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager.go new file mode 100644 index 0000000000..484c1b87ef --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager.go @@ -0,0 +1,180 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/ed25519" + "crypto/rand" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +const ( + ecdhX25519AESPrivateKeyVersion = 0 + ecdhX25519AESPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwAesAeadPrivateKey" +) + +// common errors. +var ( + errInvalidECDHX25519AESPrivateKey = errors.New("ecdh_x25519kw_aesaead_private_key_manager: invalid key") + errInvalidECDHX25519AESPrivateKeyFormat = errors.New("ecdh_x25519kw_aesaead_private_key_manager: invalid key format") // nolint:lll +) + +// ecdhX25519AESPrivateKeyManager is an implementation of PrivateKeyManager interface for X25519 key wrapping and +// AES-GCM content encryption. +// It generates new ECDHPrivateKey (X25519 KW) keys and produces new instances of ECDHAEADCompositeDecrypt subtle. +type ecdhX25519AESPrivateKeyManager struct{} + +// Assert that ecdhX25519AESPrivateKeyManager implements the PrivateKeyManager interface. +var _ registry.PrivateKeyManager = (*ecdhX25519AESPrivateKeyManager)(nil) + +// newECDHX25519AESPrivateKeyManager creates a new ecdhX25519AESPrivateKeyManager. +func newECDHX25519AESPrivateKeyManager() *ecdhX25519AESPrivateKeyManager { + return new(ecdhX25519AESPrivateKeyManager) +} + +// Primitive creates an ECDHESPrivateKey subtle for the given serialized ECDHESPrivateKey proto. +func (km *ecdhX25519AESPrivateKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHX25519AESPrivateKey + } + + key := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedKey, key) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKey + } + + err = km.validateKey(key) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(key.PublicKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeDecrypt(rEnc, key.PublicKey.Params.EncParams.CEK), nil +} + +// NewKey creates a new key according to the specification of ECDHESPrivateKey format. +func (km *ecdhX25519AESPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + if len(serializedKeyFormat) == 0 { + return nil, errInvalidECDHX25519AESPrivateKeyFormat + } + + keyFormat := new(ecdhpb.EcdhAeadKeyFormat) + + err := proto.Unmarshal(serializedKeyFormat, keyFormat) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKeyFormat + } + + err = validateKeyXChachaFormat(keyFormat.Params) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKeyFormat + } + + pub, pvt, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: GenerateECDHKeyPair failed: %w", err) + } + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: Convert to X25519 pub key failed: %w", err) + } + + x25519Pvt, err := cryptoutil.SecretEd25519toCurve25519(pvt) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: Convert to X25519 priv key failed: %w", err) + } + + return &ecdhpb.EcdhAeadPrivateKey{ + Version: ecdhX25519AESPrivateKeyVersion, + KeyValue: x25519Pvt, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: ecdhX25519AESPrivateKeyVersion, + Params: keyFormat.Params, + X: x25519Pub, + }, + }, nil +} + +// NewKeyData creates a new KeyData according to the specification of ECDHESPrivateKey Format. +// It should be used solely by the key management API. +func (km *ecdhX25519AESPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + key, err := km.NewKey(serializedKeyFormat) + if err != nil { + return nil, err + } + + serializedKey, err := proto.Marshal(key) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: Proto.Marshal failed: %w", err) + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhX25519AESPrivateKeyTypeURL, + Value: serializedKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, + }, nil +} + +// PublicKeyData returns the enclosed public key data of serializedPrivKey. +func (km *ecdhX25519AESPrivateKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { + privKey := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedPrivKey, privKey) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKey + } + + serializedPubKey, err := proto.Marshal(privKey.PublicKey) + if err != nil { + return nil, errInvalidECDHX25519AESPrivateKey + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhX25519AESPublicKeyTypeURL, + Value: serializedPubKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC, + }, nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhX25519AESPrivateKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhX25519AESPrivateKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhX25519AESPrivateKeyManager) TypeURL() string { + return ecdhX25519AESPrivateKeyTypeURL +} + +// validateKey validates the given ECDHPrivateKey and returns the KW curve. +func (km *ecdhX25519AESPrivateKeyManager) validateKey(key *ecdhpb.EcdhAeadPrivateKey) error { + err := keyset.ValidateKeyVersion(key.Version, ecdhX25519AESPrivateKeyVersion) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_aesaead_private_key_manager: invalid key: %w", err) + } + + return validateKeyXChachaFormat(key.PublicKey.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager_test.go new file mode 100644 index 0000000000..bddbf7644d --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_private_key_manager_test.go @@ -0,0 +1,315 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + gcmpb "github.com/google/tink/go/proto/aes_gcm_go_proto" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +func TestECDHX25519AESPrivateKeyManager_Primitive(t *testing.T) { + km := newECDHX25519AESPrivateKeyManager() + + t.Run("Test private key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKey.Error(), + "ecdhX25519AESPrivateKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test private key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKey.Error(), + "ecdhX25519AESPrivateKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + format := &gcmpb.AesGcmKeyFormat{ + KeySize: 32, + } + serializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + format = &gcmpb.AesGcmKeyFormat{ + KeySize: 99, // bad AES128GCM size + } + + badSerializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "private key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.AES256GCMKeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success private key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.AES256GCMKeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + Value: serializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + { + tcName: "private key manager Primitive() using key with bad dem key size", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: composite.AESGCMTypeURL, + Value: badSerializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + pub, pvt, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + require.NoError(t, err) + + x25519Pvt, err := cryptoutil.SecretEd25519toCurve25519(pvt) + require.NoError(t, err) + + params := &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve to force an error when calling km.NewKey() + KeyType: tt.keyType, // invalid key type to force error when calling km.Primitive() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, + CEK: []byte{}, + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + } + + privKeyProto := &ecdhpb.EcdhAeadPrivateKey{ + Version: tt.version, + KeyValue: x25519Pvt, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: ecdhX25519AESPrivateKeyVersion, + Params: params, + X: x25519Pub, + }, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPrivKey) + if bytes.Equal(tt.encTmp.Value, badSerializedFormat) { + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKey.Error(), + "ecdhX25519AESPrivateKeyManager primitive from serialized key with invalid serialized key") + require.Empty(t, p) + + return + } + + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestECDHX25519AESPrivateKeyManager_DoesSupport(t *testing.T) { + km := newECDHX25519AESPrivateKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhX25519AESPrivateKeyTypeURL)) +} + +func TestECDHX25519AESPrivateKeyManager_NewKey(t *testing.T) { + km := newECDHX25519AESPrivateKeyManager() + + t.Run("Test private key manager NewKey() with nil key", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKeyFormat.Error()) + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKey() with bad serialize key", func(t *testing.T) { + p, err := km.NewKey([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKeyFormat.Error(), + "ecdhX25519AESPrivateKeyManager NewKey() from bad serialized key must fail") + require.Empty(t, p) + }) + + format := &gcmpb.AesGcmKeyFormat{ + KeySize: 32, + } + + serializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + format = &gcmpb.AesGcmKeyFormat{ + KeySize: 99, // bad AES128GCM size + } + + badSerializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + flagTests := []struct { + tcName string + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "success private key manager NewKey() and NewKeyData()", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.AES256GCMKeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key type", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + encTmp: aead.AES256GCMKeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key template URL", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + Value: serializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad dem key size", + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: composite.AESGCMTypeURL, + Value: badSerializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + privKeyProto := &ecdhpb.EcdhAeadKeyFormat{ + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve to force an error when calling km.NewKey() + KeyType: tt.keyType, // unknown curve type to force an error when calling km.NewKey() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, // invalid data enc key template to force an error when calling km.NewKey() + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + }, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.NewKey(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + + sp, e := proto.Marshal(p) + require.NoError(t, e) + require.NotEmpty(t, sp) + + // try PublicKeyData() with bad serialized private key + pubK, e := km.PublicKeyData([]byte("bad serialized private key")) + require.Error(t, e) + require.Empty(t, pubK) + + // try PublicKeyData() with valid serialized private key + pubK, e = km.PublicKeyData(sp) + require.NoError(t, e) + require.NotEmpty(t, pubK) + } + + kd, err := km.NewKeyData(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, kd) + require.Equal(t, kd.TypeUrl, ecdhX25519AESPrivateKeyTypeURL) + require.Equal(t, kd.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PRIVATE) + return + } + + if bytes.Equal(tt.encTmp.Value, badSerializedFormat) { + require.EqualError(t, err, errInvalidECDHX25519AESPrivateKeyFormat.Error(), + "ecdhX25519AESPrivateKeyManager NewKey from serialized key with invalid serialized key") + require.Empty(t, p) + + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager.go new file mode 100644 index 0000000000..7dfb3110e1 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager.go @@ -0,0 +1,99 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +const ( + ecdhX25519AESPublicKeyVersion = 0 + ecdhX25519AESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwAesAeadPublicKey" +) + +// common errors. +var errInvalidECDHX25519AESPublicKey = errors.New("ecdh_x25519kw_aesaead_public_key_manager: invalid key") + +// ecdhX25519AESPublicKeyManager is an implementation of KeyManager interface for X25519 key wrapping and +// AES20Poly1305 content encryption. +// It generates new ECDHPublicKey (X25519) keys and produces new instances of ECDHAEADCompositeEncrypt subtle. +type ecdhX25519AESPublicKeyManager struct{} + +// Assert that ecdhX25519AESPublicKeyManager implements the KeyManager interface. +var _ registry.KeyManager = (*ecdhX25519AESPublicKeyManager)(nil) + +// newECDHX25519AESPublicKeyManager creates a new ecdhX25519AESPublicKeyManager. +func newECDHX25519AESPublicKeyManager() *ecdhX25519AESPublicKeyManager { + return new(ecdhX25519AESPublicKeyManager) +} + +// Primitive creates an ECDHESAESPublicKey subtle for the given serialized ECDHESAESPublicKey proto. +func (km *ecdhX25519AESPublicKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHX25519AESPublicKey + } + + ecdhPubKey := new(ecdhpb.EcdhAeadPublicKey) + + err := proto.Unmarshal(serializedKey, ecdhPubKey) + if err != nil { + return nil, errInvalidECDHX25519AESPublicKey + } + + err = km.validateKey(ecdhPubKey) + if err != nil { + return nil, errInvalidECDHX25519AESPublicKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(ecdhPubKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_aesaead_public_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeEncrypt(rEnc, ecdhPubKey.Params.EncParams.CEK), nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhX25519AESPublicKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhX25519AESPublicKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhX25519AESPublicKeyManager) TypeURL() string { + return ecdhX25519AESPublicKeyTypeURL +} + +// NewKey is not implemented for public key manager. +func (km *ecdhX25519AESPublicKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + return nil, errors.New("ecdh_x25519kw_aesaead_public_key_manager: NewKey not implemented") +} + +// NewKeyData is not implemented for public key manager. +func (km *ecdhX25519AESPublicKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + return nil, errors.New("ecdh_x25519kw_aesaead_public_key_manager: NewKeyData not implemented") +} + +// validateKey validates the given EcdhAeadPublicKey. +func (km *ecdhX25519AESPublicKeyManager) validateKey(key *ecdhpb.EcdhAeadPublicKey) error { + err := keyset.ValidateKeyVersion(key.Version, ecdhX25519AESPublicKeyVersion) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_aesaead_public_key_manager: invalid key: %w", err) + } + + return validateKeyXChachaFormat(key.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager_test.go new file mode 100644 index 0000000000..3be1f0642c --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_aesaead_public_key_manager_test.go @@ -0,0 +1,185 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/ed25519" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + gcmpb "github.com/google/tink/go/proto/aes_gcm_go_proto" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +func TestECDHX25519AESPublicKeyManager_Primitive(t *testing.T) { + km := newECDHX25519AESPublicKeyManager() + + t.Run("Test public key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHX25519AESPublicKey.Error(), + "ecdhX25519AESPublicKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test public key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519AESPublicKey.Error(), + "ecdhX25519AESPublicKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + format := &gcmpb.AesGcmKeyFormat{ + KeySize: 32, + } + + serializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + format = &gcmpb.AesGcmKeyFormat{ + KeySize: 99, // bad AES128GCM size + } + + badSerializedFormat, err := proto.Marshal(format) + require.NoError(t, err) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "public key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_EC, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success public key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + Value: serializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + { + tcName: "public key manager Primitive() using key with bad content encryption key size", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: composite.AESGCMTypeURL, + Value: badSerializedFormat, + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + pub, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + require.NoError(t, err) + + pubKeyProto := &ecdhpb.EcdhAeadPublicKey{ + Version: tt.version, // if version > 0 to force an error when calling km.Primitive() + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve to force an error when calling km.NewKey() + KeyType: tt.keyType, // invalid key type to force error when calling km.Primitive() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, // invalid data enc key template to force error when calling km.Primitive() + CEK: []byte{}, + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + }, + X: x25519Pub, + } + + sPubKey, err := proto.Marshal(pubKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPubKey) + if strings.Contains(tt.tcName, "with bad content encryption key size") { + require.EqualError(t, err, errInvalidECDHX25519AESPublicKey.Error(), + "ecdhX25519AESPublicKeyManager primitive from serialized key with invalid serialized key") + require.Empty(t, p) + + return + } + + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestEcdhX25519AESPublicKeyManager_DoesSupport(t *testing.T) { + km := newECDHX25519AESPublicKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhX25519AESPublicKeyTypeURL)) +} + +func TestEcdhX25519AESPublicKeyManager_NewKeyAndNewKeyData(t *testing.T) { + km := newECDHX25519AESPublicKeyManager() + + t.Run("Test public key manager NewKey()", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, "ecdh_x25519kw_aesaead_public_key_manager: NewKey not implemented") + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKeyData()", func(t *testing.T) { + p, err := km.NewKeyData(nil) + require.EqualError(t, err, "ecdh_x25519kw_aesaead_public_key_manager: NewKeyData not implemented") + require.Empty(t, p) + }) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager.go new file mode 100644 index 0000000000..3c901ec7a2 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager.go @@ -0,0 +1,211 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/ed25519" + "crypto/rand" + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +const ( + ecdhX25519XChachaPrivateKeyVersion = 0 + ecdhX25519XChachaPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwXChachaAeadPrivateKey" // nolint:lll +) + +// common errors. +var ( + errInvalidECDHX25519XChachaPrivateKey = errors.New("ecdh_x25519kw_xchachaaead_private_key_manager: invalid key") // nolint:lll + errInvalidECDHX25519XChachaPrivateKeyFormat = errors.New("ecdh_x25519kw_xchachaaead_private_key_manager: invalid key format") // nolint:lll +) + +// ecdhX25519XChachaPrivateKeyManager is an implementation of PrivateKeyManager interface for X25519 key wrapping and +// XChacha20Poly1305 content encryption. +// It generates new ECDHPrivateKey (X25519 KW) keys and produces new instances of ECDHAEADCompositeDecrypt subtle. +type ecdhX25519XChachaPrivateKeyManager struct{} + +// Assert that ecdhX25519XChachaPrivateKeyManager implements the PrivateKeyManager interface. +var _ registry.PrivateKeyManager = (*ecdhX25519XChachaPrivateKeyManager)(nil) + +// newECDHX25519XChachaPrivateKeyManager creates a new ecdhX25519XChachaPrivateKeyManager. +func newECDHX25519XChachaPrivateKeyManager() *ecdhX25519XChachaPrivateKeyManager { + return new(ecdhX25519XChachaPrivateKeyManager) +} + +// Primitive creates an ECDHESPrivateKey subtle for the given serialized ECDHESPrivateKey proto. +func (km *ecdhX25519XChachaPrivateKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHX25519XChachaPrivateKey + } + + key := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedKey, key) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKey + } + + err = km.validateKey(key) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(key.PublicKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeDecrypt(rEnc, key.PublicKey.Params.EncParams.CEK), nil +} + +// NewKey creates a new key according to the specification of ECDHESPrivateKey format. +func (km *ecdhX25519XChachaPrivateKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + if len(serializedKeyFormat) == 0 { + return nil, errInvalidECDHX25519XChachaPrivateKeyFormat + } + + keyFormat := new(ecdhpb.EcdhAeadKeyFormat) + + err := proto.Unmarshal(serializedKeyFormat, keyFormat) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKeyFormat + } + + err = validateKeyXChachaFormat(keyFormat.Params) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKeyFormat + } + + pub, pvt, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: GenerateECDHKeyPair failed: %w", err) + } + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: Convert to X25519 pub key failed: %w", err) + } + + x25519Pvt, err := cryptoutil.SecretEd25519toCurve25519(pvt) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: Convert to X25519 priv key failed: %w", err) + } + + return &ecdhpb.EcdhAeadPrivateKey{ + Version: ecdhX25519XChachaPrivateKeyVersion, + KeyValue: x25519Pvt, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: ecdhX25519XChachaPrivateKeyVersion, + Params: keyFormat.Params, + X: x25519Pub, + }, + }, nil +} + +// NewKeyData creates a new KeyData according to the specification of ECDHESPrivateKey Format. +// It should be used solely by the key management API. +func (km *ecdhX25519XChachaPrivateKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + key, err := km.NewKey(serializedKeyFormat) + if err != nil { + return nil, err + } + + serializedKey, err := proto.Marshal(key) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: Proto.Marshal failed: %w", err) + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhX25519XChachaPrivateKeyTypeURL, + Value: serializedKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, + }, nil +} + +// PublicKeyData returns the enclosed public key data of serializedPrivKey. +func (km *ecdhX25519XChachaPrivateKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { + privKey := new(ecdhpb.EcdhAeadPrivateKey) + + err := proto.Unmarshal(serializedPrivKey, privKey) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKey + } + + serializedPubKey, err := proto.Marshal(privKey.PublicKey) + if err != nil { + return nil, errInvalidECDHX25519XChachaPrivateKey + } + + return &tinkpb.KeyData{ + TypeUrl: ecdhX25519XChachaPublicKeyTypeURL, + Value: serializedPubKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC, + }, nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhX25519XChachaPrivateKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhX25519XChachaPrivateKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhX25519XChachaPrivateKeyManager) TypeURL() string { + return ecdhX25519XChachaPrivateKeyTypeURL +} + +// validateKey validates the given ECDHPrivateKey and returns the KW curve. +func (km *ecdhX25519XChachaPrivateKeyManager) validateKey(key *ecdhpb.EcdhAeadPrivateKey) error { + err := keyset.ValidateKeyVersion(key.Version, ecdhX25519XChachaPrivateKeyVersion) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: invalid key: %w", err) + } + + return validateKeyXChachaFormat(key.PublicKey.Params) +} + +// validateKeyXChachaFormat validates the given ECDHESKeyFormat and returns the KW Curve. +func validateKeyXChachaFormat(params *ecdhpb.EcdhAeadParams) error { + var err error + + km, err := registry.GetKeyManager(params.EncParams.AeadEnc.TypeUrl) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: GetKeyManager error: %w", err) + } + + _, err = km.NewKeyData(params.EncParams.AeadEnc.Value) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: NewKeyData error: %w", err) + } + + if params.KwParams.KeyType.String() != ecdhpb.KeyType_OKP.String() { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: invalid key type %v", + params.KwParams.KeyType) + } + + // if CEK is not set, this is a KW key for storage, it must have the curve. + // if it is set, then this is a primitive execution key, the curve is not needed since we do content encryption. + if params.EncParams.CEK == nil && + params.KwParams.CurveType.String() != commonpb.EllipticCurveType_CURVE25519.String() { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_private_key_manager: invalid curve %v", + params.KwParams.CurveType) + } + + return nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager_test.go new file mode 100644 index 0000000000..bd379e2ef7 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_private_key_manager_test.go @@ -0,0 +1,248 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/ed25519" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +func TestECDHX25519XChachaPrivateKeyManager_Primitive(t *testing.T) { + km := newECDHX25519XChachaPrivateKeyManager() + + t.Run("Test private key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHX25519XChachaPrivateKey.Error(), + "ecdhX25519XChachaPrivateKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test private key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519XChachaPrivateKey.Error(), + "ecdhX25519XChachaPrivateKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "private key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_UNKNOWN_KEY_TYPE, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success private key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + pub, pvt, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + require.NoError(t, err) + + x25519Pvt, err := cryptoutil.SecretEd25519toCurve25519(pvt) + require.NoError(t, err) + + params := &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + KeyType: tt.keyType, + CurveType: tt.curveType, + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, + CEK: []byte{}, + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + } + + privKeyProto := &ecdhpb.EcdhAeadPrivateKey{ + Version: tt.version, + KeyValue: x25519Pvt, + PublicKey: &ecdhpb.EcdhAeadPublicKey{ + Version: ecdhX25519XChachaPrivateKeyVersion, + Params: params, + X: x25519Pub, + }, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestECDHX25519XChachaPrivateKeyManager_DoesSupport(t *testing.T) { + km := newECDHX25519XChachaPrivateKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhX25519XChachaPrivateKeyTypeURL)) +} + +func TestECDHX25519XChachaPrivateKeyManager_NewKey(t *testing.T) { + km := newECDHX25519XChachaPrivateKeyManager() + + t.Run("Test private key manager NewKey() with nil key", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, errInvalidECDHX25519XChachaPrivateKeyFormat.Error()) + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKey() with bad serialize key", func(t *testing.T) { + p, err := km.NewKey([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519XChachaPrivateKeyFormat.Error(), + "ecdhX25519XChachaPrivateKeyManager NewKey() from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "success private key manager NewKey() and NewKeyData()", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key type", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_EC, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad key template URL", + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + privKeyProto := &ecdhpb.EcdhAeadKeyFormat{ + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve to force an error when calling km.NewKey() + KeyType: tt.keyType, // unknown key type to force an error when calling km.NewKey() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, // invalid data enc key template to force an error when calling km.NewKey() + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + }, + } + + sPrivKey, err := proto.Marshal(privKeyProto) + require.NoError(t, err) + + p, err := km.NewKey(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + + sp, e := proto.Marshal(p) + require.NoError(t, e) + require.NotEmpty(t, sp) + + // try PublicKeyData() with bad serialized private key + pubK, e := km.PublicKeyData([]byte("bad serialized private key")) + require.Error(t, e) + require.Empty(t, pubK) + + // try PublicKeyData() with valid serialized private key + pubK, e = km.PublicKeyData(sp) + require.NoError(t, e) + require.NotEmpty(t, pubK) + } + + kd, err := km.NewKeyData(sPrivKey) + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, kd) + require.Equal(t, kd.TypeUrl, ecdhX25519XChachaPrivateKeyTypeURL) + require.Equal(t, kd.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PRIVATE) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager.go new file mode 100644 index 0000000000..c3ca07966c --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager.go @@ -0,0 +1,99 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "errors" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh/subtle" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" +) + +const ( + ecdhX25519XChachaPublicKeyVersion = 0 + ecdhX25519XChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwXChachaAeadPublicKey" // nolint:lll +) + +// common errors. +var errInvalidECDHX25519XChachaPublicKey = errors.New("ecdh_x25519kw_xchachaaead_public_key_manager: invalid key") + +// ecdhX25519XChachaPublicKeyManager is an implementation of KeyManager interface for X25519 key wrapping and +// XChacha20Poly1305 content encryption. +// It generates new ECDHPublicKey (X25519) keys and produces new instances of ECDHAEADCompositeEncrypt subtle. +type ecdhX25519XChachaPublicKeyManager struct{} + +// Assert that ecdhX25519XChachaPublicKeyManager implements the KeyManager interface. +var _ registry.KeyManager = (*ecdhX25519XChachaPublicKeyManager)(nil) + +// newECDHX25519XChachaPublicKeyManager creates a new ecdhX25519XChachaPublicKeyManager. +func newECDHX25519XChachaPublicKeyManager() *ecdhX25519XChachaPublicKeyManager { + return new(ecdhX25519XChachaPublicKeyManager) +} + +// Primitive creates an ECDHESXChachaPublicKey subtle for the given serialized ECDHESXChachaPublicKey proto. +func (km *ecdhX25519XChachaPublicKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidECDHX25519XChachaPublicKey + } + + ecdhPubKey := new(ecdhpb.EcdhAeadPublicKey) + + err := proto.Unmarshal(serializedKey, ecdhPubKey) + if err != nil { + return nil, errInvalidECDHX25519XChachaPublicKey + } + + err = km.validateKey(ecdhPubKey) + if err != nil { + return nil, errInvalidECDHX25519XChachaPublicKey + } + + rEnc, err := composite.NewRegisterCompositeAEADEncHelper(ecdhPubKey.Params.EncParams.AeadEnc) + if err != nil { + return nil, fmt.Errorf("ecdh_x25519kw_xchachaaead_public_key_manager: NewRegisterCompositeAEADEncHelper "+ + "failed: %w", err) + } + + return subtle.NewECDHAEADCompositeEncrypt(rEnc, ecdhPubKey.Params.EncParams.CEK), nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *ecdhX25519XChachaPublicKeyManager) DoesSupport(typeURL string) bool { + return typeURL == ecdhX25519XChachaPublicKeyTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *ecdhX25519XChachaPublicKeyManager) TypeURL() string { + return ecdhX25519XChachaPublicKeyTypeURL +} + +// NewKey is not implemented for public key manager. +func (km *ecdhX25519XChachaPublicKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + return nil, errors.New("ecdh_x25519kw_xchachaaead_public_key_manager: NewKey not implemented") +} + +// NewKeyData is not implemented for public key manager. +func (km *ecdhX25519XChachaPublicKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + return nil, errors.New("ecdh_x25519kw_xchachaaead_public_key_manager: NewKeyData not implemented") +} + +// validateKey validates the given EcdhAeadPublicKey. +func (km *ecdhX25519XChachaPublicKeyManager) validateKey(key *ecdhpb.EcdhAeadPublicKey) error { + err := keyset.ValidateKeyVersion(key.Version, ecdhX25519XChachaPublicKeyVersion) + if err != nil { + return fmt.Errorf("ecdh_x25519kw_xchachaaead_public_key_manager: invalid key: %w", err) + } + + return validateKeyXChachaFormat(key.Params) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager_test.go new file mode 100644 index 0000000000..a83d467bf1 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/composite/ecdh/ecdh_x25519kw_xchachaaead_public_key_manager_test.go @@ -0,0 +1,158 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ecdh + +import ( + "crypto/ed25519" + "crypto/rand" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/aead" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" +) + +func TestECDHX25519XChachaPublicKeyManager_Primitive(t *testing.T) { + km := newECDHX25519XChachaPublicKeyManager() + + t.Run("Test public key manager Primitive() with empty serialized key", func(t *testing.T) { + p, err := km.Primitive([]byte("")) + require.EqualError(t, err, errInvalidECDHX25519XChachaPublicKey.Error(), + "ecdhX25519XChachaPublicKeyManager primitive from empty serialized key must fail") + require.Empty(t, p) + }) + + t.Run("Test public key manager Primitive() with bad serialize key", func(t *testing.T) { + p, err := km.Primitive([]byte("bad.data")) + require.EqualError(t, err, errInvalidECDHX25519XChachaPublicKey.Error(), + "ecdhX25519XChachaPublicKeyManager primitive from bad serialized key must fail") + require.Empty(t, p) + }) + + flagTests := []struct { + tcName string + version uint32 + curveType commonpb.EllipticCurveType + keyType ecdhpb.KeyType + encTmp *tinkpb.KeyTemplate + }{ + { + tcName: "public key manager Primitive() using key with bad version", + version: 9999, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "private key manager NewKey() and NewKeyData() using key with bad curve", + version: 0, + curveType: commonpb.EllipticCurveType_UNKNOWN_CURVE, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad key type", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_EC, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "success public key manager Primitive()", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: aead.XChaCha20Poly1305KeyTemplate(), + }, + { + tcName: "public key manager Primitive() using key with bad key template URL", + version: 0, + curveType: commonpb.EllipticCurveType_CURVE25519, + keyType: ecdhpb.KeyType_OKP, + encTmp: &tinkpb.KeyTemplate{ + TypeUrl: "bad.type/url/value", + OutputPrefixType: tinkpb.OutputPrefixType_RAW, + }, + }, + } + + for _, tc := range flagTests { + tt := tc + t.Run("Test "+tt.tcName, func(t *testing.T) { + pub, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + x25519Pub, err := cryptoutil.PublicEd25519toCurve25519(pub) + require.NoError(t, err) + + pubKeyProto := &ecdhpb.EcdhAeadPublicKey{ + Version: tt.version, // if version > 0 to force an error when calling km.Primitive() + Params: &ecdhpb.EcdhAeadParams{ + KwParams: &ecdhpb.EcdhKwParams{ + CurveType: tt.curveType, // unknown curve to force an error when calling km.NewKey() + KeyType: tt.keyType, // invalid key type to force error when calling km.Primitive() + }, + EncParams: &ecdhpb.EcdhAeadEncParams{ + AeadEnc: tt.encTmp, // invalid data enc key template to force error when calling km.Primitive() + CEK: []byte{}, + }, + EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED, + }, + X: x25519Pub, + } + + sPubKey, err := proto.Marshal(pubKeyProto) + require.NoError(t, err) + + p, err := km.Primitive(sPubKey) + if strings.Contains(tt.tcName, "with bad content encryption key size") { + require.EqualError(t, err, errInvalidECDHX25519XChachaPublicKey.Error(), + "ecdhX25519XChachaPublicKeyManager primitive from serialized key with invalid serialized key") + require.Empty(t, p) + + return + } + + if strings.Contains(tt.tcName, "success") { + require.NoError(t, err) + require.NotEmpty(t, p) + return + } + + require.Errorf(t, err, tt.tcName) + require.Empty(t, p) + }) + } +} + +func TestEcdhX25519XChachaPublicKeyManager_DoesSupport(t *testing.T) { + km := newECDHX25519XChachaPublicKeyManager() + require.False(t, km.DoesSupport("bad/url")) + require.True(t, km.DoesSupport(ecdhX25519XChachaPublicKeyTypeURL)) +} + +func TestEcdhX25519XChachaPublicKeyManager_NewKeyAndNewKeyData(t *testing.T) { + km := newECDHX25519XChachaPublicKeyManager() + + t.Run("Test public key manager NewKey()", func(t *testing.T) { + k, err := km.NewKey(nil) + require.EqualError(t, err, "ecdh_x25519kw_xchachaaead_public_key_manager: NewKey not implemented") + require.Empty(t, k) + }) + + t.Run("Test private key manager NewKeyData()", func(t *testing.T) { + p, err := km.NewKeyData(nil) + require.EqualError(t, err, "ecdh_x25519kw_xchachaaead_public_key_manager: NewKeyData not implemented") + require.Empty(t, p) + }) +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export.go b/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export.go index 5cf63c3832..56f396f3c7 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export.go @@ -30,12 +30,14 @@ import ( // key (aka PublicKeyToHandle to be used as a valid Tink key) const ( - ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAesAeadPublicKey" + ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPublicKey" + ecdhXChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwAesAeadPublicKey" // nolint:lll ) // PubKeyWriter will write the raw bytes of a Tink KeySet's primary public key. The raw bytes are a marshaled // composite.VerificationMethod type. -// The keyset must have a keyURL value equal to `ecdhAESPublicKeyTypeURL` constant of ecdh package. +// The keyset must have a keyURL value equal to `ecdhAESPublicKeyTypeURL` or `ecdhXChachaPublicKeyTypeURL` constant of +// ecdh package. // Note: This writer should be used only for ECDH public key exports. Other export of public keys should be // called via localkms package. type PubKeyWriter struct { @@ -115,6 +117,11 @@ func protoToCompositeKey(keyData *tinkpb.KeyData) (*cryptoapi.PublicKey, error) if err != nil { return nil, err } + case ecdhXChachaPublicKeyTypeURL: + cKey, err = newECDHXChachaKey(keyData.Value) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("can't export key with keyURL:%s", keyData.TypeUrl) } @@ -123,6 +130,7 @@ func protoToCompositeKey(keyData *tinkpb.KeyData) (*cryptoapi.PublicKey, error) } func buildKey(c compositeKeyGetter) (*cryptoapi.PublicKey, error) { + // TODO build XChacha key differently curveName := c.curveName() keyTypeName := c.keyType() @@ -246,7 +254,7 @@ func PublicKeyToKeysetHandle(pubKey *cryptoapi.PublicKey) (*keyset.Handle, error Params: &ecdhpb.EcdhAeadParams{ KwParams: &ecdhpb.EcdhKwParams{ CurveType: cp, - KeyType: ecdhpb.KeyType_EC, // for now, TODO create getTypeProto(pubKey.Type) function + KeyType: ecdhpb.KeyType_EC, }, EncParams: &ecdhpb.EcdhAeadEncParams{ AeadEnc: aead.AES256GCMKeyTemplate(), @@ -308,3 +316,8 @@ func newKeySet(tURL string, marshalledKey []byte, keyMaterialType tinkpb.KeyData PrimaryKeyId: 1, } } + +func newECDHXChachaKey(mKey []byte) (compositeKeyGetter, error) { + // TODO implement + return nil, nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export_import_test.go b/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export_import_test.go index d54a61ce13..b286de0f59 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export_import_test.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export_import_test.go @@ -30,7 +30,7 @@ func TestPubKeyExport(t *testing.T) { keyTemplate *tinkpb.KeyTemplate }{ { - tcName: "export then read AES256GCM with ECDH P-256 public recipient key", + tcName: "export then read AES256GCM with ECDHES P-256 public recipient key", keyTemplate: ecdh.ECDH256KWAES256GCMKeyTemplate(), }, { diff --git a/pkg/crypto/tinkcrypto/primitive/composite/register_ecdh_aead_enc_helper.go b/pkg/crypto/tinkcrypto/primitive/composite/register_ecdh_aead_enc_helper.go index 0d1b3b6a35..a08c63024b 100644 --- a/pkg/crypto/tinkcrypto/primitive/composite/register_ecdh_aead_enc_helper.go +++ b/pkg/crypto/tinkcrypto/primitive/composite/register_ecdh_aead_enc_helper.go @@ -66,25 +66,71 @@ func NewRegisterCompositeAEADEncHelper(k *tinkpb.KeyTemplate) (*RegisterComposit skf, err = proto.Marshal(gcmKeyFormat) if err != nil { - return nil, fmt.Errorf("compositeAEADEncHelper: failed to serialize key format, error: %w", err) + return nil, fmt.Errorf("compositeAEADEncHelper: failed to serialize gcm key format, error: %w", err) } case ChaCha20Poly1305TypeURL: tagSize = poly1305.TagSize ivSize = chacha20poly1305.NonceSize + + skf, err = buildChachaSKF(k) + if err != nil { + return nil, err + } case XChaCha20Poly1305TypeURL: tagSize = poly1305.TagSize ivSize = chacha20poly1305.NonceSizeX + + skf, err = buildXChachaSKF(k) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("compositeAEADEncHelper: unsupported AEAD content encryption key type: %s", k.TypeUrl) } + return buildRegisterCompositeAEADEncHelper(k, skf, tagSize, ivSize) +} + +func buildChachaSKF(k *tinkpb.KeyTemplate) ([]byte, error) { + chachaKeyFormat := new(chachapb.ChaCha20Poly1305KeyFormat) + + err := proto.Unmarshal(k.Value, chachaKeyFormat) + if err != nil { + return nil, fmt.Errorf("compositeAEADEncHelper: failed to unmarshal chachaKeyFormat: %w", err) + } + + skf, err := proto.Marshal(chachaKeyFormat) + if err != nil { + return nil, fmt.Errorf("compositeAEADEncHelper: failed to serialize chacha key format, error: %w", err) + } + + return skf, nil +} + +func buildXChachaSKF(k *tinkpb.KeyTemplate) ([]byte, error) { + xChachaKeyFormat := new(xchachapb.XChaCha20Poly1305KeyFormat) + + err := proto.Unmarshal(k.Value, xChachaKeyFormat) + if err != nil { + return nil, fmt.Errorf("compositeAEADEncHelper: failed to unmarshal xChachaKeyFormat: %w", err) + } + + skf, err := proto.Marshal(xChachaKeyFormat) + if err != nil { + return nil, fmt.Errorf("compositeAEADEncHelper: failed to serialize xChacha key format, error: %w", err) + } + + return skf, nil +} + +func buildRegisterCompositeAEADEncHelper(k *tinkpb.KeyTemplate, skf []byte, + tagSize, ivSize int) (*RegisterCompositeAEADEncHelper, error) { km, err := registry.GetKeyManager(k.TypeUrl) if err != nil { return nil, fmt.Errorf("compositeAEADEncHelper: failed to fetch KeyManager, error: %w", err) } - // skf is nil for (X)Chahcha20Poly1305 km key, err := km.NewKey(skf) if err != nil { return nil, fmt.Errorf("compositeAEADEncHelper: failed to fetch key, error: %w", err) diff --git a/pkg/crypto/tinkcrypto/unwrap_support.go b/pkg/crypto/tinkcrypto/unwrap_support.go index aa8357d0a9..d6046f86b5 100644 --- a/pkg/crypto/tinkcrypto/unwrap_support.go +++ b/pkg/crypto/tinkcrypto/unwrap_support.go @@ -42,7 +42,7 @@ func extractPrivKey(kh *keyset.Handle) (*hybrid.ECPrivateKey, error) { return nil, errors.New("extractPrivKey: invalid private key") } - ecdhAESPrivateKeyTypeURL := "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAesAeadPrivateKey" + ecdhAESPrivateKeyTypeURL := "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPrivateKey" primaryKey := ks.Key[0] if primaryKey.KeyData.TypeUrl != ecdhAESPrivateKeyTypeURL { diff --git a/pkg/kms/localkms/pubkey_writer.go b/pkg/kms/localkms/pubkey_writer.go index ff8eaa60ef..01349cdde7 100644 --- a/pkg/kms/localkms/pubkey_writer.go +++ b/pkg/kms/localkms/pubkey_writer.go @@ -27,7 +27,7 @@ import ( const ( ecdsaVerifierTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey" ed25519VerifierTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey" - ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAesAeadPublicKey" + ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPublicKey" ) // PubKeyWriter will write the raw bytes of a Tink KeySet's primary public key diff --git a/proto/tink/ecdh_aead.proto b/proto/tink/ecdh_aead.proto index 8cc99f9032..65881bef88 100644 --- a/proto/tink/ecdh_aead.proto +++ b/proto/tink/ecdh_aead.proto @@ -31,19 +31,19 @@ enum KeyType { // Parameters of KW (Key Wrapping) for storage only as KW is done outside the Tink key primitive. message EcdhKwParams { // Required. - EllipticCurveType curve_type = 1; + EllipticCurveType curve_type = 1; // required for NIST P Curved keys, not needed or X25519 keys. // Required. - KeyType key_type = 2; + KeyType key_type = 2; // EC for NIST P Curved keys, OKP for X25519 keys } // Parameters of AEAD Content encryption. message EcdhAeadEncParams { // Required. - KeyTemplate aead_enc = 1; // Contains e.g. AesGcmKeyFormat. + KeyTemplate aead_enc = 1; // Contains e.g. AesGcmKeyFormat or XChaCha20Poly1305KeyFormat. // Required for primitive execution, not storage. - bytes CEK = 2; // Contains cek used for AES GCM encryption + bytes CEK = 2; // Contains cek used for AES GCM/XChacha20Poly1305 encryption. } message EcdhAeadParams { @@ -61,7 +61,7 @@ message EcdhAeadParams { } // EcdhAeadPublicKey represents ECDHEncrypt primitive. -// key_type: type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAeadPublicKey +// key_type: type.hyperledger.org/hyperledger.aries.crypto.tink.Ecdh[...]AeadPublicKey message EcdhAeadPublicKey { // Required. uint32 version = 1; @@ -73,7 +73,8 @@ message EcdhAeadPublicKey { string KID = 3; // Affine coordinates of the public key in bigendian representation. - // The public key is a point (x, y) on the curve defined by params.kw_params.curve. + // The public key is a point (x, y) on the curve defined by params.kw_params.curve for NIST P Curved keys. + // For X25519 keys, x is the raw key value (y is not used). // Required. bytes x = 4; @@ -82,7 +83,7 @@ message EcdhAeadPublicKey { } // EcdhAeadPrivateKey represents ECDHDecrypt primitive. -// key_type: type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhAeadPrivateKey +// key_type: type.hyperledger.org/hyperledger.aries.crypto.tink.Ecdh[...]AeadPrivateKey message EcdhAeadPrivateKey { // Required. uint32 version = 1;