Skip to content

Commit

Permalink
feat: add KMS key type for X25519 key
Browse files Browse the repository at this point in the history
This is the last change about X25519 keys for ECDH KW.
It updates the old ECDH key types into the new type names
and add the X25519 key type as well.

Also part of this change is the removal of remnant code
from legacyKMS which was removed from the framework last
year.

closes hyperledger-archives#2447, hyperledger-archives#1684, hyperledger-archives#815
also part of hyperledger-archives#857
closes hyperledger-archives#475, hyperledger-archives#596

Signed-off-by: Baha Shaaban <[email protected]>
  • Loading branch information
Baha Shaaban committed Jan 25, 2021
1 parent 34e1819 commit dace834
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 244 deletions.
12 changes: 6 additions & 6 deletions pkg/didcomm/packager/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestBaseKMSInPackager_UnpackMessage(t *testing.T) {
require.NoError(t, err)

// fromKey is stored in the KMS
fromKID, fromKey, err := customKMS.CreateAndExportPubKeyBytes(kms.ECDH256KWAES256GCMType)
fromKID, fromKey, err := customKMS.CreateAndExportPubKeyBytes(kms.NISTP256ECDHKWType)
require.NoError(t, err)

// for authcrypt, sender key should be in third party store, must use base58 wrapped store to match kms store.
Expand All @@ -149,7 +149,7 @@ func TestBaseKMSInPackager_UnpackMessage(t *testing.T) {
require.NoError(t, err)

// toVerKey is stored in the KMS as well
toKID, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.ECDH256KWAES256GCM)
toKID, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.NISTP256ECDHKW)
require.NoError(t, err)

// PackMessage should pass with both value from and to keys
Expand Down Expand Up @@ -207,10 +207,10 @@ func TestBaseKMSInPackager_UnpackMessage(t *testing.T) {
require.NoError(t, err)

// use ECDH1PU type as we are using a sender key (ie: packer's FromKey is not empty aka authcrypt)
fromKID, _, err := customKMS.Create(kms.ECDH384KWAES256GCMType)
fromKID, _, err := customKMS.Create(kms.NISTP384ECDHKWType)
require.NoError(t, err)

_, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.ECDH384KWAES256GCMType)
_, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.NISTP384ECDHKWType)
require.NoError(t, err)

// try pack with nil envelope - should fail
Expand Down Expand Up @@ -282,10 +282,10 @@ func TestBaseKMSInPackager_UnpackMessage(t *testing.T) {
packager, err := New(mockedProviders)
require.NoError(t, err)

fromKID, fromKey, err := customKMS.CreateAndExportPubKeyBytes(kms.ECDH256KWAES256GCMType)
fromKID, fromKey, err := customKMS.CreateAndExportPubKeyBytes(kms.NISTP256ECDHKWType)
require.NoError(t, err)

_, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.ECDH256KWAES256GCMType)
_, toKey, err := customKMS.CreateAndExportPubKeyBytes(kms.NISTP256ECDHKWType)
require.NoError(t, err)

// pack an non empty envelope - should pass
Expand Down
10 changes: 5 additions & 5 deletions pkg/didcomm/packer/anoncrypt/pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ func TestAnoncryptPackerSuccess(t *testing.T) {
func TestAnoncryptPackerSuccessWithDifferentCurvesSuccess(t *testing.T) {
k := createKMS(t)
_, recipientsKey1, keyHandles1 := createRecipients(t, k, 1)
_, recipientsKey2, _ := createRecipientsByKeyType(t, k, 1, kms.ECDH384KWAES256GCM)
_, recipientsKey3, _ := createRecipientsByKeyType(t, k, 1, kms.ECDH521KWAES256GCM)
_, recipientsKey2, _ := createRecipientsByKeyType(t, k, 1, kms.NISTP384ECDHKW)
_, recipientsKey3, _ := createRecipientsByKeyType(t, k, 1, kms.NISTP521ECDHKW)

recipientsKeys := make([][]byte, 3)
recipientsKeys[0] = make([]byte, len(recipientsKey1[0]))
Expand Down Expand Up @@ -183,10 +183,10 @@ func TestAnoncryptPackerFail(t *testing.T) {
require.NoError(t, err)

// rotate keys to update keyID and force a failure
_, _, err = k.Rotate(kms.ECDH256KWAES256GCMType, kids[0])
_, _, err = k.Rotate(kms.NISTP256ECDHKWType, kids[0])
require.NoError(t, err)

_, _, err = k.Rotate(kms.ECDH256KWAES256GCMType, kids[1])
_, _, err = k.Rotate(kms.NISTP256ECDHKWType, kids[1])
require.NoError(t, err)

_, err = validAnonPacker.Unpack(ct)
Expand All @@ -196,7 +196,7 @@ func TestAnoncryptPackerFail(t *testing.T) {

// createRecipients and return their public key and keyset.Handle.
func createRecipients(t *testing.T, k *localkms.LocalKMS, recipientsCount int) ([]string, [][]byte, []*keyset.Handle) {
return createRecipientsByKeyType(t, k, recipientsCount, kms.ECDH256KWAES256GCM)
return createRecipientsByKeyType(t, k, recipientsCount, kms.NISTP256ECDHKW)
}

func createRecipientsByKeyType(t *testing.T, k *localkms.LocalKMS, recipientsCount int,
Expand Down
12 changes: 6 additions & 6 deletions pkg/didcomm/packer/authcrypt/pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func TestAuthryptPackerUsingKeysWithDifferentCurvesSuccess(t *testing.T) {
_, recipientsKey1, keyHandles1 := createRecipients(t, k, 1)
// since authcrypt does ECDH kw using the sender key, the recipient keys must be on the same curve as the sender's.
// this is why recipient keys with different curves are not supported for authcrypt.
_, recipientsKey2, _ := createRecipients(t, k, 1) // can't create key with kms.ECDH384KWAES256GCM
_, recipientsKey3, _ := createRecipients(t, k, 1) // can't create key with kms.ECDH521KWAES256GCM
_, recipientsKey2, _ := createRecipients(t, k, 1) // can't create key with kms.NISTP384ECDHKW
_, recipientsKey3, _ := createRecipients(t, k, 1) // can't create key with kms.NISTP521ECDHKW

recipientsKeys := make([][]byte, 3)
recipientsKeys[0] = make([]byte, len(recipientsKey1[0]))
Expand Down Expand Up @@ -251,10 +251,10 @@ func TestAuthcryptPackerFail(t *testing.T) {
require.NoError(t, err)

// rotate keys to update keyID and force a failure
_, _, err = k.Rotate(kms.ECDH256KWAES256GCMType, kids[0])
_, _, err = k.Rotate(kms.NISTP256ECDHKWType, kids[0])
require.NoError(t, err)

_, _, err = k.Rotate(kms.ECDH256KWAES256GCMType, kids[1])
_, _, err = k.Rotate(kms.NISTP256ECDHKWType, kids[1])
require.NoError(t, err)

_, err = validAuthPacker.Unpack(ct)
Expand All @@ -264,7 +264,7 @@ func TestAuthcryptPackerFail(t *testing.T) {

// createRecipients and return their public key and keyset.Handle.
func createRecipients(t *testing.T, k *localkms.LocalKMS, recipientsCount int) ([]string, [][]byte, []*keyset.Handle) {
return createRecipientsByKeyType(t, k, recipientsCount, kms.ECDH256KWAES256GCM)
return createRecipientsByKeyType(t, k, recipientsCount, kms.NISTP256ECDHKW)
}

func createRecipientsByKeyType(t *testing.T, k *localkms.LocalKMS, recipientsCount int,
Expand All @@ -291,7 +291,7 @@ func createRecipientsByKeyType(t *testing.T, k *localkms.LocalKMS, recipientsCou
// createAndMarshalKey creates a new recipient keyset.Handle, extracts public key, marshals it and returns
// both marshalled public key and original recipient keyset.Handle.
func createAndMarshalKey(t *testing.T, k *localkms.LocalKMS) (string, []byte, *keyset.Handle) {
return createAndMarshalKeyByKeyType(t, k, kms.ECDH256KWAES256GCMType)
return createAndMarshalKeyByKeyType(t, k, kms.NISTP256ECDHKWType)
}

func createAndMarshalKeyByKeyType(t *testing.T, k *localkms.LocalKMS, kt kms.KeyType) (string, []byte, *keyset.Handle) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/jose/encrypter_decrypter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ func createAndMarshalEntityKey(t *testing.T) ([]byte, *keyset.Handle, string) {
err = pubKH.WriteWithNoSecrets(pubKeyWriter)
require.NoError(t, err)

kid, err := jwkkid.CreateKID(buf.Bytes(), kms.ECDH256KWAES256GCMType)
kid, err := jwkkid.CreateKID(buf.Bytes(), kms.NISTP256ECDHKWType)
require.NoError(t, err)

return buf.Bytes(), kh, kid
Expand Down
72 changes: 65 additions & 7 deletions pkg/doc/util/jwkkid/kid_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto"
"github.com/hyperledger/aries-framework-go/pkg/doc/jose"
"github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil"
"github.com/hyperledger/aries-framework-go/pkg/kms"
)

Expand All @@ -33,6 +34,16 @@ var errInvalidKeyType = errors.New("key type is not supported")
// - base64 raw (no padding) URL encoded KID
// - error in case of error
func CreateKID(keyBytes []byte, kt kms.KeyType) (string, error) {
// X25519 JWK is not supported by go jose, manually build it and build its resulting KID.
if kt == kms.X25519ECDHKWType {
x25519KID, err := createX25519KID(keyBytes)
if err != nil {
return "", fmt.Errorf("createKID: %w", err)
}

return x25519KID, nil
}

jwk, err := buildJWK(keyBytes, kt)
if err != nil {
return "", fmt.Errorf("createKID: failed to build jwk: %w", err)
Expand Down Expand Up @@ -64,7 +75,7 @@ func buildJWK(keyBytes []byte, kt kms.KeyType) (*jose.JWK, error) {
return nil, fmt.Errorf("buildJWK: failed to build JWK from ed25519 key: %w", err)
}
case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363:
c := getCurve(kt)
c := getCurveByKMSKeyType(kt)
x, y := elliptic.Unmarshal(c, keyBytes)

pubKey := &ecdsa.PublicKey{
Expand All @@ -77,7 +88,7 @@ func buildJWK(keyBytes []byte, kt kms.KeyType) (*jose.JWK, error) {
if err != nil {
return nil, fmt.Errorf("buildJWK: failed to build JWK from ecdsa key in IEEE1363 format: %w", err)
}
case kms.ECDH256KWAES256GCMType, kms.ECDH384KWAES256GCMType, kms.ECDH521KWAES256GCMType:
case kms.NISTP256ECDHKWType, kms.NISTP384ECDHKWType, kms.NISTP521ECDHKWType:
jwk, err = generateJWKFromECDH(keyBytes)
if err != nil {
return nil, fmt.Errorf("buildJWK: failed to build JWK from ecdh key: %w", err)
Expand All @@ -99,11 +110,9 @@ func generateJWKFromDERECDSA(keyBytes []byte) (*jose.JWK, error) {
}

func generateJWKFromECDH(keyBytes []byte) (*jose.JWK, error) {
compositeKey := &cryptoapi.PublicKey{}

err := json.Unmarshal(keyBytes, compositeKey)
compositeKey, err := unmarshalECDHKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("generateJWKFromECDH: failed to unmarshal ECDH key: %w", err)
return nil, fmt.Errorf("generateJWKFromECDH: %w", err)
}

c, err := hybrid.GetCurve(compositeKey.Curve)
Expand All @@ -120,7 +129,7 @@ func generateJWKFromECDH(keyBytes []byte) (*jose.JWK, error) {
return jose.JWKFromPublicKey(pubKey)
}

func getCurve(kt kms.KeyType) elliptic.Curve {
func getCurveByKMSKeyType(kt kms.KeyType) elliptic.Curve {
switch kt {
case kms.ECDSAP256TypeIEEEP1363:
return elliptic.P256()
Expand All @@ -133,3 +142,52 @@ func getCurve(kt kms.KeyType) elliptic.Curve {
// should never be called but added for linting
return elliptic.P256()
}

func unmarshalECDHKey(keyBytes []byte) (*cryptoapi.PublicKey, error) {
compositeKey := &cryptoapi.PublicKey{}

err := json.Unmarshal(keyBytes, compositeKey)
if err != nil {
return nil, fmt.Errorf("unmarshalECDHKey: failed to unmarshal ECDH key: %w", err)
}

return compositeKey, nil
}

func createX25519KID(marshalledKey []byte) (string, error) {
compositeKey, err := unmarshalECDHKey(marshalledKey)
if err != nil {
return "", fmt.Errorf("createX25519KID: %w", err)
}

jwk, err := buildX25519JWK(compositeKey.X)
if err != nil {
return "", fmt.Errorf("createX25519KID: %w", err)
}

thumbprint := thumbprintX25519(jwk)

return base64.RawURLEncoding.EncodeToString(thumbprint), nil
}

func buildX25519JWK(keyBytes []byte) (string, error) {
const x25519ThumbprintTemplate = `{"crv":"X25519","kty":"OKP",x":"%s"}`

if len(keyBytes) > cryptoutil.Curve25519KeySize {
return "", errors.New("buildX25519JWK: invalid ECDH X25519 key")
}

pad := make([]byte, cryptoutil.Curve25519KeySize-len(keyBytes))
x25519RawKey := append(pad, keyBytes...)

jwk := fmt.Sprintf(x25519ThumbprintTemplate, base64.RawURLEncoding.EncodeToString(x25519RawKey))

return jwk, nil
}

func thumbprintX25519(jwk string) []byte {
h := crypto.SHA256.New()
_, _ = h.Write([]byte(jwk)) // nolint: errcheck // SHA256 digest returns empty error on Write()

return h.Sum(nil)
}
63 changes: 59 additions & 4 deletions pkg/doc/util/jwkkid/kid_creator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import (
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"encoding/json"
"strings"
"testing"

commonpb "github.com/google/tink/go/proto/common_go_proto"
"github.com/stretchr/testify/require"

cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto"
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"
"github.com/hyperledger/aries-framework-go/pkg/kms"
)

Expand All @@ -30,19 +36,39 @@ func TestCreateKID(t *testing.T) {
require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: failed to build JWK from ed25519 "+
"key: create JWK: unable to read jose JWK, square/go-jose: unknown curve Ed25519'")

_, x, y, err := elliptic.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)

ecdhKey := &cryptoapi.PublicKey{
Curve: elliptic.P256().Params().Name,
X: x.Bytes(),
Y: y.Bytes(),
}

ecdhKeyMarshalled, err := json.Marshal(ecdhKey)
require.NoError(t, err)

kid, err = CreateKID(ecdhKeyMarshalled, kms.NISTP256ECDHKWType)
require.NoError(t, err)
require.NotEmpty(t, kid)

_, err = CreateKID(pubKey, "badType")
require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: key type is not supported: 'badType'")

badPubKey := ed25519.PublicKey{}
_, err = CreateKID(badPubKey, kms.ECDH256KWAES256GCMType)
_, err = CreateKID(badPubKey, kms.NISTP256ECDHKWType)
require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: failed to build JWK from ecdh "+
"key: generateJWKFromECDH: failed to unmarshal ECDH key: unexpected end of JSON input")
"key: generateJWKFromECDH: unmarshalECDHKey: failed to unmarshal ECDH key: unexpected end of JSON input")

_, err = CreateKID(badPubKey, kms.ECDSAP256TypeDER)
require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: failed to build JWK from ecdsa "+
"DER key: generateJWKFromDERECDSA: failed to parse ecdsa key in DER format: asn1: syntax error: sequence "+
"truncated")

_, err = CreateKID(badPubKey, kms.X25519ECDHKWType)
require.EqualError(t, err, "createKID: createX25519KID: unmarshalECDHKey: failed to unmarshal ECDH key: "+
"unexpected end of JSON input")

ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)

Expand All @@ -52,9 +78,38 @@ func TestCreateKID(t *testing.T) {
}

func TestGetCurve(t *testing.T) {
c := getCurve(kms.ECDSAP384TypeIEEEP1363)
c := getCurveByKMSKeyType(kms.ECDSAP384TypeIEEEP1363)
require.Equal(t, c, elliptic.P384())

c = getCurve(kms.AES128GCMType) // default P-256 if curve not found
c = getCurveByKMSKeyType(kms.AES128GCMType) // default P-256 if curve not found
require.Equal(t, c, elliptic.P256())
}

func TestGenerateJWKFromECDH(t *testing.T) {
keyBytesWithBadCurve := &cryptoapi.PublicKey{
Curve: commonpb.EllipticCurveType_UNKNOWN_CURVE.String(),
X: []byte{},
Y: []byte{},
}

badKeyMarshalled, err := json.Marshal(keyBytesWithBadCurve)
require.NoError(t, err)

_, err = generateJWKFromECDH(badKeyMarshalled)
require.EqualError(t, err, "generateJWKFromECDH: failed to get Curve for ECDH key: unsupported curve")
}

func TestCreateX25519KID_Failure(t *testing.T) {
key := &cryptoapi.PublicKey{
Curve: "X25519",
X: []byte(strings.Repeat("a", cryptoutil.Curve25519KeySize+1)), // public key > X25519 key size
Y: []byte{},
Type: ecdhpb.KeyType_OKP.String(),
}

mKey, err := json.Marshal(key)
require.NoError(t, err)

_, err = createX25519KID(mKey)
require.EqualError(t, err, "createX25519KID: buildX25519JWK: invalid ECDH X25519 key")
}
Loading

0 comments on commit dace834

Please sign in to comment.