Skip to content

Commit

Permalink
feat: X25519 ECDH export pub key (hyperledger-archives#2448)
Browse files Browse the repository at this point in the history
  • Loading branch information
baha-ai authored and sudeshrshetty committed Oct 14, 2021
1 parent 5222160 commit c816668
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 43 deletions.
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

0 comments on commit c816668

Please sign in to comment.