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.

It includes JWE encryption/decryption updates to support
XC20P content encryption along with recipients kw using both
NIST P curved keys and X25519 keys.

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
closes hyperledger-archives#1684
closes hyperledger-archives#815
closes hyperledger-archives#475
closes hyperledger-archives#596
also part of hyperledger-archives#857

Signed-off-by: Baha Shaaban <[email protected]>
  • Loading branch information
Baha Shaaban committed Jan 27, 2021
1 parent 34e1819 commit c5c0f56
Show file tree
Hide file tree
Showing 20 changed files with 877 additions and 497 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
85 changes: 60 additions & 25 deletions pkg/didcomm/packer/anoncrypt/pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,78 @@ import (

func TestAnoncryptPackerSuccess(t *testing.T) {
k := createKMS(t)
_, recipientsKeys, keyHandles := createRecipients(t, k, 10)

cryptoSvc, err := tinkcrypto.New()
require.NoError(t, err)
tests := []struct {
name string
keyType kms.KeyType
encAlg jose.EncAlg
}{
{
"anoncrypt using NISTP256ECDHKW and AES256-GCM",
kms.NISTP256ECDHKWType,
jose.A256GCM,
},
{
"anoncrypt using X25519ECDHKW and XChacha20Poly1305",
kms.X25519ECDHKWType,
jose.XC20P,
},
{
"anoncrypt using NISTP256ECDHKW and XChacha20Poly1305",
kms.NISTP256ECDHKWType,
jose.XC20P,
},
{
"anoncrypt using X25519ECDHKW and AES256-GCM",
kms.X25519ECDHKWType,
jose.A256GCM,
},
}

anonPacker, err := New(newMockProvider(k, cryptoSvc), jose.A256GCM)
require.NoError(t, err)
t.Parallel()

origMsg := []byte("secret message")
ct, err := anonPacker.Pack(origMsg, nil, recipientsKeys)
require.NoError(t, err)
for _, tt := range tests {
tc := tt
t.Run(fmt.Sprintf("running %s", tc.name), func(t *testing.T) {
_, recipientsKeys, keyHandles := createRecipientsByKeyType(t, k, 3, tc.keyType)

msg, err := anonPacker.Unpack(ct)
require.NoError(t, err)
cryptoSvc, err := tinkcrypto.New()
require.NoError(t, err)

recKey, err := exportPubKeyBytes(keyHandles[0])
require.NoError(t, err)
anonPacker, err := New(newMockProvider(k, cryptoSvc), jose.A256GCM)
require.NoError(t, err)

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)
origMsg := []byte("secret message")
ct, err := anonPacker.Pack(origMsg, nil, recipientsKeys)
require.NoError(t, err)

// try with only 1 recipient
ct, err = anonPacker.Pack(origMsg, nil, [][]byte{recipientsKeys[0]})
require.NoError(t, err)
msg, err := anonPacker.Unpack(ct)
require.NoError(t, err)

msg, err = anonPacker.Unpack(ct)
require.NoError(t, err)
recKey, err := exportPubKeyBytes(keyHandles[0])
require.NoError(t, err)

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)
require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)

require.Equal(t, encodingType, anonPacker.EncodingType())
// try with only 1 recipient
ct, err = anonPacker.Pack(origMsg, nil, [][]byte{recipientsKeys[0]})
require.NoError(t, err)

msg, err = anonPacker.Unpack(ct)
require.NoError(t, err)

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)

require.Equal(t, encodingType, anonPacker.EncodingType())
})
}
}

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 +218,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 +231,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
108 changes: 72 additions & 36 deletions pkg/didcomm/packer/authcrypt/pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,56 +31,92 @@ import (

func TestAuthryptPackerSuccess(t *testing.T) {
k := createKMS(t)
_, recipientsKeys, keyHandles := createRecipients(t, k, 10)

skid, senderKey, _ := createAndMarshalKey(t, k)
tests := []struct {
name string
keyType kms.KeyType
encAlg jose.EncAlg
}{
{
"authpack using NISTP256ECDHKW and AES256-GCM",
kms.NISTP256ECDHKWType,
jose.A256GCM,
},
{
"authpack using X25519ECDHKW and XChacha20Poly1305",
kms.X25519ECDHKWType,
jose.XC20P,
},
{
"authpack using NISTP256ECDHKW and XChacha20Poly1305",
kms.NISTP256ECDHKWType,
jose.XC20P,
},
{
"authpack using X25519ECDHKW and AES256-GCM",
kms.X25519ECDHKWType,
jose.A256GCM,
},
}

thirdPartyKeyStore := make(map[string][]byte)
mockStoreProvider := &mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{
Store: thirdPartyKeyStore,
}}
t.Parallel()

cryptoSvc, err := tinkcrypto.New()
require.NoError(t, err)
for _, tt := range tests {
tc := tt
t.Run(fmt.Sprintf("running %s", tc.name), func(t *testing.T) {
_, recipientsKeys, keyHandles := createRecipientsByKeyType(t, k, 3, tc.keyType)

authPacker, err := New(newMockProvider(mockStoreProvider, k, cryptoSvc), jose.A256GCM)
require.NoError(t, err)
skid, senderKey, _ := createAndMarshalKeyByKeyType(t, k, tc.keyType)

// add sender key in thirdPartyKS (prep step before Authcrypt.Pack()/Unpack())
fromWrappedKID := prefix.StorageKIDPrefix + skid
thirdPartyKeyStore[fromWrappedKID] = senderKey
thirdPartyKeyStore := make(map[string][]byte)
mockStoreProvider := &mockstorage.MockStoreProvider{Store: &mockstorage.MockStore{
Store: thirdPartyKeyStore,
}}

origMsg := []byte("secret message")
ct, err := authPacker.Pack(origMsg, []byte(skid), recipientsKeys)
require.NoError(t, err)
cryptoSvc, err := tinkcrypto.New()
require.NoError(t, err)

msg, err := authPacker.Unpack(ct)
require.NoError(t, err)
authPacker, err := New(newMockProvider(mockStoreProvider, k, cryptoSvc), tc.encAlg)
require.NoError(t, err)

recKey, err := exportPubKeyBytes(keyHandles[0])
require.NoError(t, err)
// add sender key in thirdPartyKS (prep step before Authcrypt.Pack()/Unpack())
fromWrappedKID := prefix.StorageKIDPrefix + skid
thirdPartyKeyStore[fromWrappedKID] = senderKey

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)
origMsg := []byte("secret message")
ct, err := authPacker.Pack(origMsg, []byte(skid), recipientsKeys)
require.NoError(t, err)

// try with only 1 recipient
ct, err = authPacker.Pack(origMsg, []byte(skid), [][]byte{recipientsKeys[0]})
require.NoError(t, err)
msg, err := authPacker.Unpack(ct)
require.NoError(t, err)

msg, err = authPacker.Unpack(ct)
require.NoError(t, err)
recKey, err := exportPubKeyBytes(keyHandles[0])
require.NoError(t, err)

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)
require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)

require.Equal(t, encodingType, authPacker.EncodingType())
// try with only 1 recipient to force compact JWE serialization
ct, err = authPacker.Pack(origMsg, []byte(skid), [][]byte{recipientsKeys[0]})
require.NoError(t, err)

msg, err = authPacker.Unpack(ct)
require.NoError(t, err)

require.EqualValues(t, &transport.Envelope{Message: origMsg, ToKey: recKey}, msg)

require.Equal(t, encodingType, authPacker.EncodingType())
})
}
}

func TestAuthryptPackerUsingKeysWithDifferentCurvesSuccess(t *testing.T) {
k := createKMS(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
// since authcrypt does ECDH kw using the sender key, the recipient keys must be on the same curve (for NIST P keys)
// and the same key type (for NIST P / X25519 keys) as the sender's.
// this is why recipient keys with different curves/type are not supported for authcrypt.
_, 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 +287,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 +300,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 +327,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: 2 additions & 0 deletions pkg/doc/jose/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ const (
// A256GCMALG is the default content encryption algorithm value as per
// the JWA specification: https://tools.ietf.org/html/rfc7518#section-5.1
A256GCMALG = "A256GCM"
// XC20PALG represented XChacha20Poly1305 content encryption algorithm value.
XC20PALG = "XC20P"
// DIDCommEncType representing the JWE 'Typ' protected type header.
DIDCommEncType = "didcomm-envelope-enc"
)
Expand Down
Loading

0 comments on commit c5c0f56

Please sign in to comment.