Skip to content

Commit

Permalink
feat: X25519 ECDH export pub key
Browse files Browse the repository at this point in the history
This change includes eporting the public key from ECDH Tink key templates
based on the key type and curve

closes hyperledger-archives#1806
closes hyperledger-archives#1637

Signed-off-by: Baha Shaaban <[email protected]>
  • Loading branch information
Baha Shaaban committed Jan 13, 2021
1 parent 7872928 commit 7614bca
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 41 deletions.
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
// function serves 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 ECDHES NIST P-384 public recipient key",
keyTemplate: ecdh.ECDH384KWXChachaKeyTemplate(),
},
{
tcName: "export then read XChacha20Poly1305 with ECDHES 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

0 comments on commit 7614bca

Please sign in to comment.