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

feat: X25519 ECDH export pub key #2448

Merged
merged 1 commit into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/crypto/tinkcrypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func (t *Crypto) UnwrapKey(recWK *cryptoapi.RecipientWrappedKey, kh interface{},
return nil, fmt.Errorf("unwrapKey: %w", err)
}

// TODO: add support for 25519 key wrapping https://github.com/hyperledger/aries-framework-go/issues/1637
// TODO: add support for 25519 key wrapping https://github.com/hyperledger/aries-framework-go/issues/2445
recPrivKey := hybridECPrivToECDSAKey(recipientPrivateKey)

epkCurve, err := t.kw.getCurve(recWK.EPK.Curve)
Expand Down
2 changes: 1 addition & 1 deletion pkg/crypto/tinkcrypto/key_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (t *Crypto) deriveKEKAndWrap(cek, apu, apv []byte, senderKH interface{}, ep
recPubKey *ecdsa.PublicKey, recKID string) (*cryptoapi.RecipientWrappedKey, error) {
var kek []byte

// TODO: add support for 25519 key wrapping https://github.com/hyperledger/aries-framework-go/issues/1637
// TODO: add support for 25519 key wrapping https://github.com/hyperledger/aries-framework-go/issues/2445
keyType := ecdhpb.KeyType_EC.String()
wrappingAlg := ECDHESA256KWAlg

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ func TestEncryptDecryptNegativeTCs(t *testing.T) {

cEnc := NewECDHAEADCompositeEncrypt(mEncHelper, cek)

// Encrypt should fail with large AEAD key size value
// Encrypt should fail with AEAD error value
_, err := cEnc.Encrypt(pt, aad)
require.EqualError(t, err, "error from GetAEAD")

mEncHelper.AEADErrValue = nil

// Encrypt should fail with nil cek
cEncNilCEK := NewECDHAEADCompositeEncrypt(mEncHelper, nil)
_, err = cEncNilCEK.Encrypt(pt, aad)
require.EqualError(t, err, "ecdhAEADCompositeEncrypt: missing cek")

// create a valid ciphertext to test Decrypt for all recipients
cEnc = NewECDHAEADCompositeEncrypt(mEncHelper, cek)

Expand Down Expand Up @@ -102,6 +107,11 @@ func TestEncryptDecryptNegativeTCs(t *testing.T) {

mEncHelper.AEADErrValue = nil

// Decrypt should fail with nil cek
dEncNilCEK := NewECDHAEADCompositeDecrypt(mEncHelper, nil)
_, err = dEncNilCEK.Decrypt(ct, aad)
require.EqualError(t, err, "ecdh decrypt: missing cek")

// create a valid Decrypt message and test against ct
dEnc = NewECDHAEADCompositeDecrypt(mEncHelper, cek)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ import (
// key (aka PublicKeyToHandle to be used as a valid Tink key)

const (
ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPublicKey"
ecdhXChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwAesAeadPublicKey" // nolint:lll
ecdhNISTPAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwAesAeadPublicKey"
ecdhNISTPXChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhNistPKwXChachaAeadPublicKey" // nolint:lll
ecdhX25519AESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwAesAeadPublicKey"
ecdhX25519XChachaPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.EcdhX25519KwXChachaAeadPublicKey" // 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` or `ecdhXChachaPublicKeyTypeURL` constant of
// ecdh package.
// The keyset must have a keyURL value equal to either one of the public key URLs:
// - `ecdhNISTPAESPublicKeyTypeURL`
// - `ecdhX25519AESPublicKeyTypeURL`
// - `ecdhNISTPXChachaPublicKeyTypeURL`
// - `ecdhX25519XChachaPublicKeyTypeURL`
// constants 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 {
Expand Down Expand Up @@ -112,16 +118,12 @@ func protoToCompositeKey(keyData *tinkpb.KeyData) (*cryptoapi.PublicKey, error)
)

switch keyData.TypeUrl {
case ecdhAESPublicKeyTypeURL:
case ecdhNISTPAESPublicKeyTypeURL, ecdhNISTPXChachaPublicKeyTypeURL, ecdhX25519AESPublicKeyTypeURL,
ecdhX25519XChachaPublicKeyTypeURL:
cKey, err = newECDHKey(keyData.Value)
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)
}
Expand All @@ -130,18 +132,27 @@ 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()

return buildCompositeKey(c.kid(), keyTypeName, curveName, c.x(), c.y())
}

func buildCompositeKey(kid, keyType, curve string, x, y []byte) (*cryptoapi.PublicKey, error) {
// validate curve
_, err := hybrid.GetCurve(curve)
if err != nil {
return nil, fmt.Errorf("undefined curve: %w", err)
// validate keyType and curve
switch keyType {
case ecdhpb.KeyType_EC.String():
// validate NIST P curves
_, err := hybrid.GetCurve(curve)
if err != nil {
return nil, fmt.Errorf("undefined EC curve: %w", err)
}
case ecdhpb.KeyType_OKP.String():
if curve != commonpb.EllipticCurveType_CURVE25519.String() {
return nil, fmt.Errorf("invalid OKP curve: %s", curve)
}
default:
return nil, fmt.Errorf("invalid keyType: %s", keyType)
}

return &cryptoapi.PublicKey{
Expand Down Expand Up @@ -173,11 +184,6 @@ func newECDHKey(mKey []byte) (compositeKeyGetter, error) {
return nil, err
}

// validate key type
if pubKeyProto.Params.KwParams.KeyType != ecdhpb.KeyType_EC {
return nil, fmt.Errorf("undefined key type: '%s'", pubKeyProto.Params.KwParams.KeyType)
}

return &ecdhKey{protoKey: pubKeyProto}, nil
}

Expand Down Expand Up @@ -241,23 +247,49 @@ func writePubKeyFromKeyHandle(handle *keyset.Handle) ([]byte, error) {

// PublicKeyToKeysetHandle converts pubKey into a *keyset.Handle where pubKey could be either a sender or a recipient
// key. The resulting handle cannot be directly used for primitive execution as the cek is not set. This function serves
// as a helper to get a senderKH to be used as an option for ECDH execution (for ECDH-1PU/authcrypt).
// as a helper to get a senderKH to be used as an option for ECDH execution (for ECDH-1PU/authcrypt). The keyset handle
// will be set with AES256-GCM AEAD key template for content encryption.
func PublicKeyToKeysetHandle(pubKey *cryptoapi.PublicKey) (*keyset.Handle, error) {
return publicKeyWithEncTemplateToKeysetHandle(pubKey, true)
}

// PublicKeyToKeysetHandleXChacha converts pubKey into a *keyset.Handle where pubKey could be either a sender or a
// recipient key. The resulting handle cannot be directly used for primitive execution as the cek is not set. This
// as a helper to get a senderKH to be used as an option for ECDH execution (for ECDH-1PU/authcrypt). The keyset handle
// will be set with XChacha20Poly1305 AEAD key template for content encryption.
func PublicKeyToKeysetHandleXChacha(pubKey *cryptoapi.PublicKey) (*keyset.Handle, error) {
return publicKeyWithEncTemplateToKeysetHandle(pubKey, false)
}

func publicKeyWithEncTemplateToKeysetHandle(pubKey *cryptoapi.PublicKey, isAES bool) (*keyset.Handle, error) {
// validate curve
cp, err := getCurveProto(pubKey.Curve)
if err != nil {
return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to convert curve string to proto: %w", err)
}

kt, err := getKeyType(pubKey.Type)
if err != nil {
return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to convert key type to proto: %w", err)
}

encT := aead.AES256GCMKeyTemplate()

if !isAES {
encT = aead.XChaCha20Poly1305KeyTemplate()
}

keyURL := getKeyURL(isAES, kt)

protoKey := &ecdhpb.EcdhAeadPublicKey{
Version: 0,
Params: &ecdhpb.EcdhAeadParams{
KwParams: &ecdhpb.EcdhKwParams{
CurveType: cp,
KeyType: ecdhpb.KeyType_EC,
KeyType: kt,
},
EncParams: &ecdhpb.EcdhAeadEncParams{
AeadEnc: aead.AES256GCMKeyTemplate(),
AeadEnc: encT,
},
EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED,
},
Expand All @@ -271,7 +303,7 @@ func PublicKeyToKeysetHandle(pubKey *cryptoapi.PublicKey) (*keyset.Handle, error
return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to marshal proto: %w", err)
}

ks := newKeySet(ecdhAESPublicKeyTypeURL, marshalledKey, tinkpb.KeyData_ASYMMETRIC_PUBLIC)
ks := newKeySet(keyURL, marshalledKey, tinkpb.KeyData_ASYMMETRIC_PUBLIC)

memReader := &keyset.MemReaderWriter{Keyset: ks}

Expand All @@ -283,6 +315,22 @@ func PublicKeyToKeysetHandle(pubKey *cryptoapi.PublicKey) (*keyset.Handle, error
return parsedHandle, nil
}

func getKeyURL(aes bool, kt ecdhpb.KeyType) string {
if aes {
if kt == ecdhpb.KeyType_EC {
return ecdhNISTPAESPublicKeyTypeURL
}

return ecdhX25519AESPublicKeyTypeURL
}

if kt == ecdhpb.KeyType_EC {
return ecdhNISTPXChachaPublicKeyTypeURL
}

return ecdhX25519XChachaPublicKeyTypeURL
}

func getCurveProto(c string) (commonpb.EllipticCurveType, error) {
switch c {
case "secp256r1", "NIST_P256", "P-256", "EllipticCurveType_NIST_P256":
Expand All @@ -291,8 +339,21 @@ func getCurveProto(c string) (commonpb.EllipticCurveType, error) {
return commonpb.EllipticCurveType_NIST_P384, nil
case "secp521r1", "NIST_P521", "P-521", "EllipticCurveType_NIST_P521":
return commonpb.EllipticCurveType_NIST_P521, nil
case commonpb.EllipticCurveType_CURVE25519.String():
return commonpb.EllipticCurveType_CURVE25519, nil
default:
return 0, errors.New("unsupported curve")
return commonpb.EllipticCurveType_UNKNOWN_CURVE, errors.New("unsupported curve")
}
}

func getKeyType(k string) (ecdhpb.KeyType, error) {
switch k {
case ecdhpb.KeyType_EC.String():
return ecdhpb.KeyType_EC, nil
case ecdhpb.KeyType_OKP.String():
return ecdhpb.KeyType_OKP, nil
default:
return ecdhpb.KeyType_UNKNOWN_KEY_TYPE, errors.New("unsupported key type")
}
}

Expand All @@ -316,8 +377,3 @@ func newKeySet(tURL string, marshalledKey []byte, keyMaterialType tinkpb.KeyData
PrimaryKeyId: 1,
}
}

func newECDHXChachaKey(mKey []byte) (compositeKeyGetter, error) {
// TODO implement
return nil, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"bytes"
"encoding/json"
"errors"
"strings"
"testing"

"github.com/golang/protobuf/proto"
Expand All @@ -30,17 +31,37 @@ func TestPubKeyExport(t *testing.T) {
keyTemplate *tinkpb.KeyTemplate
}{
{
tcName: "export then read AES256GCM with ECDHES P-256 public recipient key",
tcName: "export then read AES256GCM with ECDH NIST P-256 public recipient key",
keyTemplate: ecdh.ECDH256KWAES256GCMKeyTemplate(),
},
{
tcName: "export then read AES256GCM with ECDHES P-384 public recipient key",
tcName: "export then read AES256GCM with ECDH NIST P-384 public recipient key",
keyTemplate: ecdh.ECDH384KWAES256GCMKeyTemplate(),
},
{
tcName: "export then read AES256GCM with ECDHES P-521 public recipient key",
tcName: "export then read AES256GCM with ECDH NIST P-521 public recipient key",
keyTemplate: ecdh.ECDH521KWAES256GCMKeyTemplate(),
},
{
tcName: "export then read XChacha20Poly1305 with ECDH NIST P-256 public recipient key",
keyTemplate: ecdh.ECDH256KWXChachaKeyTemplate(),
},
{
tcName: "export then read XChacha20Poly1305 with ECDH NIST P-384 public recipient key",
keyTemplate: ecdh.ECDH384KWXChachaKeyTemplate(),
},
{
tcName: "export then read XChacha20Poly1305 with ECDH NIST P-521 public recipient key",
keyTemplate: ecdh.ECDH521KWXChachaKeyTemplate(),
},
{
tcName: "export then read AES256GCM with X25519 public recipient key",
keyTemplate: ecdh.X25519AES256GCMECDHKeyTemplate(),
},
{
tcName: "export then read XChacha20Poly1305 with X25519 public recipient key",
keyTemplate: ecdh.X25519XChachaECDHKeyTemplate(),
},
}

for _, tc := range flagTests {
Expand All @@ -62,13 +83,25 @@ func TestPubKeyExport(t *testing.T) {

require.EqualValues(t, ecPubKey, extractedPubKey)

// now convert back ecPubKey to *keyset.Handle
// now convert back ecPubKey to *keyset.Handle using default AES content encryption template
xPubKH, err := PublicKeyToKeysetHandle(ecPubKey)
require.NoError(t, err)

// now convert back ecPubKey to *keyset.Handle using XChacha content encryption template
x2PubKH, err := PublicKeyToKeysetHandleXChacha(ecPubKey)
require.NoError(t, err)

xk, err := ExtractPrimaryPublicKey(xPubKH)
require.NoError(t, err)
require.EqualValues(t, ecPubKey, xk)

x2k, err := ExtractPrimaryPublicKey(x2PubKH)
require.NoError(t, err)
require.EqualValues(t, ecPubKey, x2k)
if strings.Contains(tt.keyTemplate.TypeUrl, "X25519Kw") {
require.EqualValues(t, x2k.Curve, commonpb.EllipticCurveType_CURVE25519.String())
require.EqualValues(t, xk.Curve, commonpb.EllipticCurveType_CURVE25519.String())
}
})
}
}
Expand Down Expand Up @@ -106,9 +139,14 @@ func TestNegativeCases(t *testing.T) {
require.Empty(t, exportedKeyBytes)
})

t.Run("test buildCompositeKey() with bad curve", func(t *testing.T) {
_, err := buildCompositeKey("", "", "BAD", nil, nil)
require.EqualError(t, err, "undefined curve: unsupported curve")
t.Run("test buildCompositeKey() with bad EC curve", func(t *testing.T) {
_, err := buildCompositeKey("", ecdhpb.KeyType_EC.String(), "BAD", nil, nil)
require.EqualError(t, err, "undefined EC curve: unsupported curve")
})

t.Run("test buildCompositeKey() with bad OKP curve", func(t *testing.T) {
_, err := buildCompositeKey("", ecdhpb.KeyType_OKP.String(), "BAD", nil, nil)
require.EqualError(t, err, "invalid OKP curve: BAD")
})

t.Run("test protoToCompositeKey() with bad key type", func(t *testing.T) {
Expand All @@ -129,11 +167,11 @@ func TestNegativeCases(t *testing.T) {
require.NoError(t, err)

_, err = protoToCompositeKey(&tinkpb.KeyData{
TypeUrl: ecdhAESPublicKeyTypeURL,
TypeUrl: ecdhNISTPAESPublicKeyTypeURL,
Value: mKey,
KeyMaterialType: 0,
})
require.EqualError(t, err, "undefined key type: 'UNKNOWN_KEY_TYPE'")
require.EqualError(t, err, "invalid keyType: UNKNOWN_KEY_TYPE")
})

t.Run("test WriteEncrypted() should fail since it's not supported by Writer", func(t *testing.T) {
Expand Down Expand Up @@ -182,7 +220,7 @@ func TestNegativeCases(t *testing.T) {
Key: []*tinkpb.Keyset_Key{
{
KeyData: &tinkpb.KeyData{
TypeUrl: ecdhAESPublicKeyTypeURL,
TypeUrl: ecdhNISTPAESPublicKeyTypeURL,
Value: mKey,
KeyMaterialType: 0,
},
Expand Down