diff --git a/pkg/didcomm/packager/package_test.go b/pkg/didcomm/packager/package_test.go index 77411958d9..e7e9a781cd 100644 --- a/pkg/didcomm/packager/package_test.go +++ b/pkg/didcomm/packager/package_test.go @@ -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. @@ -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 @@ -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 @@ -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 diff --git a/pkg/didcomm/packer/anoncrypt/pack_test.go b/pkg/didcomm/packer/anoncrypt/pack_test.go index 614a089130..25d550ceef 100644 --- a/pkg/didcomm/packer/anoncrypt/pack_test.go +++ b/pkg/didcomm/packer/anoncrypt/pack_test.go @@ -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])) @@ -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) @@ -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, diff --git a/pkg/didcomm/packer/authcrypt/pack_test.go b/pkg/didcomm/packer/authcrypt/pack_test.go index fd621375ed..1b548be470 100644 --- a/pkg/didcomm/packer/authcrypt/pack_test.go +++ b/pkg/didcomm/packer/authcrypt/pack_test.go @@ -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])) @@ -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) @@ -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, @@ -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) { diff --git a/pkg/doc/jose/common.go b/pkg/doc/jose/common.go index 8a119d6d1a..5e82fd4741 100644 --- a/pkg/doc/jose/common.go +++ b/pkg/doc/jose/common.go @@ -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" ) diff --git a/pkg/doc/jose/decrypter.go b/pkg/doc/jose/decrypter.go index ae334c2e50..ea6683f694 100644 --- a/pkg/doc/jose/decrypter.go +++ b/pkg/doc/jose/decrypter.go @@ -19,6 +19,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/api" "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh" "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/keyio" + ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" "github.com/hyperledger/aries-framework-go/pkg/kms" "github.com/hyperledger/aries-framework-go/pkg/storage" ) @@ -47,9 +48,13 @@ func NewJWEDecrypt(store storage.Store, c cryptoapi.Crypto, k kms.KeyManager) *J } } -func getECDHDecPrimitive(cek []byte) (api.CompositeDecrypt, error) { +func getECDHDecPrimitive(cek []byte, encAlg string) (api.CompositeDecrypt, error) { kt := ecdh.NISTPECDHAES256GCMKeyTemplateWithCEK(cek) + if encAlg == XC20PALG { + kt = ecdh.X25519ECDHXChachaKeyTemplateWithCEK(cek) + } + kh, err := keyset.NewHandle(kt) if err != nil { return nil, err @@ -95,13 +100,23 @@ func (jd *JWEDecrypt) unwrapCEK(recWK []*cryptoapi.RecipientWrappedKey, var cek []byte for _, rec := range recWK { + var unwrapOpts []cryptoapi.WrapKeyOpts + recKH, err := jd.kms.Get(rec.KID) if err != nil { continue } + if rec.EPK.Type == ecdhpb.KeyType_OKP.String() { + unwrapOpts = append(unwrapOpts, cryptoapi.WithXC20PKW()) + } + if senderOpt != nil { - cek, err = jd.crypto.UnwrapKey(rec, recKH, senderOpt) + unwrapOpts = append(unwrapOpts, senderOpt) + } + + if len(unwrapOpts) > 0 { + cek, err = jd.crypto.UnwrapKey(rec, recKH, unwrapOpts...) } else { cek, err = jd.crypto.UnwrapKey(rec, recKH) } @@ -119,7 +134,12 @@ func (jd *JWEDecrypt) unwrapCEK(recWK []*cryptoapi.RecipientWrappedKey, } func (jd *JWEDecrypt) decryptJWE(jwe *JSONWebEncryption, cek []byte) ([]byte, error) { - decPrimitive, err := getECDHDecPrimitive(cek) + encAlg, ok := jwe.ProtectedHeaders.Encryption() + if !ok { + return nil, fmt.Errorf("jwedecrypt: JWE 'enc' protected header is missing") + } + + decPrimitive, err := getECDHDecPrimitive(cek, encAlg) if err != nil { return nil, fmt.Errorf("jwedecrypt: failed to get decryption primitive: %w", err) } @@ -173,9 +193,8 @@ func (jd *JWEDecrypt) validateAndExtractProtectedHeaders(jwe *JSONWebEncryption) return fmt.Errorf("jwe is missing encryption algorithm 'enc' header") } - // TODO add support for Chacha content encryption, issue #1684 switch encAlg { - case string(A256GCM): + case string(A256GCM), string(XC20P): default: return fmt.Errorf("encryption algorithm '%s' not supported", encAlg) } @@ -294,6 +313,8 @@ func convertMarshalledJWKToRecKey(marshalledJWK []byte) (*cryptoapi.RecipientWra case *ecdsa.PublicKey: epk.X = key.X.Bytes() epk.Y = key.Y.Bytes() + case []byte: + epk.X = key default: return nil, fmt.Errorf("unsupported recipient key type") } diff --git a/pkg/doc/jose/encrypt_test.go b/pkg/doc/jose/encrypt_test.go index b3700f774c..8bb9805cf3 100644 --- a/pkg/doc/jose/encrypt_test.go +++ b/pkg/doc/jose/encrypt_test.go @@ -28,11 +28,21 @@ func TestFailConvertRecKeyToMarshalledJWK(t *testing.T) { recKey := &cryptoapi.RecipientWrappedKey{ EPK: cryptoapi.PublicKey{ Curve: "badCurveName", + Type: "EC", }, } _, err := convertRecEPKToMarshalledJWK(recKey) require.EqualError(t, err, "unsupported curve") + + recKey = &cryptoapi.RecipientWrappedKey{ + EPK: cryptoapi.PublicKey{ + Curve: "badCurveName", + }, + } + + _, err = convertRecEPKToMarshalledJWK(recKey) + require.EqualError(t, err, "invalid key type") } func TestBadSenderKeyType(t *testing.T) { @@ -68,7 +78,7 @@ func TestMergeSingleRecipientsHeadersFailureWithUnsetCurve(t *testing.T) { require.NoError(t, err) wk := &cryptoapi.RecipientWrappedKey{ - EPK: cryptoapi.PublicKey{}, + EPK: cryptoapi.PublicKey{Type: "EC"}, } // fail with aad not base64URL encoded diff --git a/pkg/doc/jose/encrypter.go b/pkg/doc/jose/encrypter.go index 8535fa7435..6f0406b590 100644 --- a/pkg/doc/jose/encrypter.go +++ b/pkg/doc/jose/encrypter.go @@ -34,6 +34,8 @@ type EncAlg string const ( // A256GCM for AES256GCM content encryption. A256GCM = EncAlg(A256GCMALG) + // XC20P for XChacha20Poly1305 content encryption. + XC20P = EncAlg(XC20PALG) ) // Encrypter interface to Encrypt/Decrypt JWE messages. @@ -64,9 +66,8 @@ func NewJWEEncrypt(encAlg EncAlg, encType, senderKID string, senderKH *keyset.Ha return nil, fmt.Errorf("empty recipientsPubKeys list") } - // TODO add support for Chacha content encryption, issue #1684 switch encAlg { - case A256GCM: + case A256GCM, XC20P: default: return nil, fmt.Errorf("encryption algorithm '%s' not supported", encAlg) } @@ -92,9 +93,13 @@ func NewJWEEncrypt(encAlg EncAlg, encType, senderKID string, senderKH *keyset.Ha }, nil } -func getECDHEncPrimitive(cek []byte) (api.CompositeEncrypt, error) { +func (je *JWEEncrypt) getECDHEncPrimitive(cek []byte) (api.CompositeEncrypt, error) { kt := ecdh.NISTPECDHAES256GCMKeyTemplateWithCEK(cek) + if je.encAlg == XC20P { + kt = ecdh.X25519ECDHXChachaKeyTemplateWithCEK(cek) + } + kh, err := keyset.NewHandle(kt) if err != nil { return nil, err @@ -127,7 +132,7 @@ func (je *JWEEncrypt) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryp cek := random.GetRandomBytes(uint32(cryptoapi.DefKeySize)) // creating the crypto primitive requires a pre-built cek - encPrimitive, err := getECDHEncPrimitive(cek) + encPrimitive, err := je.getECDHEncPrimitive(cek) if err != nil { return nil, fmt.Errorf("jweencrypt: failed to get encryption primitive: %w", err) } @@ -190,26 +195,20 @@ func (je *JWEEncrypt) wrapCEKForRecipients(cek, apu, apv, aad []byte, } var ( - senderOpt cryptoapi.WrapKeyOpts recipientsWK []*cryptoapi.RecipientWrappedKey singleRecipientAAD []byte ) - kwAlg := tinkcrypto.ECDHESA256KWAlg - - if je.skid != "" && je.senderKH != nil { - kwAlg = tinkcrypto.ECDH1PUA256KWAlg - senderOpt = cryptoapi.WithSender(je.senderKH) - } - for i, recPubKey := range je.recipientsKeys { var ( kek *cryptoapi.RecipientWrappedKey err error ) - if senderOpt != nil { - kek, err = je.crypto.WrapKey(cek, apu, apv, recPubKey, senderOpt) + kwAlg, wrapOpts := je.getWrapKeyOpts() + + if len(wrapOpts) > 0 { + kek, err = je.crypto.WrapKey(cek, apu, apv, recPubKey, wrapOpts...) } else { kek, err = je.crypto.WrapKey(cek, apu, apv, recPubKey) } @@ -232,6 +231,31 @@ func (je *JWEEncrypt) wrapCEKForRecipients(cek, apu, apv, aad []byte, return recipientsWK, singleRecipientAAD, nil } +func (je *JWEEncrypt) getWrapKeyOpts() (string, []cryptoapi.WrapKeyOpts) { + var wrapOpts []cryptoapi.WrapKeyOpts + + kwAlg := tinkcrypto.ECDHESA256KWAlg + + if je.encAlg == XC20P { + kwAlg = tinkcrypto.ECDHESXC20PKWAlg + } + + if je.skid != "" && je.senderKH != nil { + switch je.encAlg { + case A256GCM: + kwAlg = tinkcrypto.ECDH1PUA256KWAlg + case XC20P: + kwAlg = tinkcrypto.ECDH1PUXC20PKWAlg + + wrapOpts = append(wrapOpts, cryptoapi.WithXC20PKW()) + } + + wrapOpts = append(wrapOpts, cryptoapi.WithSender(je.senderKH)) + } + + return kwAlg, wrapOpts +} + // mergeSingleRecipientHeaders for single recipient encryption, recipient header info is available in the key, update // AAD with this info and return the marshalled merged result. func mergeSingleRecipientHeaders(recipientWK *cryptoapi.RecipientWrappedKey, @@ -332,23 +356,36 @@ func buildRecipientHeaders(rec *cryptoapi.RecipientWrappedKey) (*RecipientHeader } func convertRecEPKToMarshalledJWK(rec *cryptoapi.RecipientWrappedKey) ([]byte, error) { - var c elliptic.Curve + var ( + c elliptic.Curve + err error + key interface{} + ) - c, err := hybrid.GetCurve(rec.EPK.Curve) - if err != nil { - return nil, err + switch rec.EPK.Type { + case ecdhpb.KeyType_EC.String(): + c, err = hybrid.GetCurve(rec.EPK.Curve) + if err != nil { + return nil, err + } + + key = &ecdsa.PublicKey{ + Curve: c, + X: new(big.Int).SetBytes(rec.EPK.X), + Y: new(big.Int).SetBytes(rec.EPK.Y), + } + case ecdhpb.KeyType_OKP.String(): + key = rec.EPK.X + default: + return nil, errors.New("invalid key type") } recJWK := JWK{ JSONWebKey: jose.JSONWebKey{ Use: HeaderEncryption, - Key: &ecdsa.PublicKey{ - Curve: c, - X: new(big.Int).SetBytes(rec.EPK.X), - Y: new(big.Int).SetBytes(rec.EPK.Y), - }, + Key: key, }, - Kty: ecdhpb.KeyType_EC.String(), // TODO add support for X25519 (OKP) content encryption, issue #1684 + Kty: rec.EPK.Type, Crv: rec.EPK.Curve, } diff --git a/pkg/doc/jose/encrypter_decrypter_test.go b/pkg/doc/jose/encrypter_decrypter_test.go index 1673deaeb9..6e01add32e 100644 --- a/pkg/doc/jose/encrypter_decrypter_test.go +++ b/pkg/doc/jose/encrypter_decrypter_test.go @@ -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 diff --git a/pkg/doc/jose/jwe.go b/pkg/doc/jose/jwe.go index 557a3755ea..83b7567a12 100644 --- a/pkg/doc/jose/jwe.go +++ b/pkg/doc/jose/jwe.go @@ -64,7 +64,6 @@ type RecipientHeaders struct { Tag string `json:"tag,omitempty"` KID string `json:"kid,omitempty"` EPK json.RawMessage `json:"epk,omitempty"` - SPK json.RawMessage `json:"spk,omitempty"` } // rawJSONWebEncryption represents a RAW JWE that is used for serialization/deserialization. diff --git a/pkg/doc/jose/jwk.go b/pkg/doc/jose/jwk.go index e0cb8cc0d0..6c986d67db 100644 --- a/pkg/doc/jose/jwk.go +++ b/pkg/doc/jose/jwk.go @@ -21,6 +21,8 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/square/go-jose/v3" "golang.org/x/crypto/ed25519" + + "github.com/hyperledger/aries-framework-go/pkg/internal/cryptoutil" ) const ( @@ -29,6 +31,8 @@ const ( secp256k1Kty = "EC" secp256k1Size = 32 bitsPerByte = 8 + x25519Crv = "X25519" + x25519Kty = "OKP" ) // JWK (JSON Web Key) is a JSON data structure that represents a cryptographic key. @@ -62,6 +66,32 @@ func JWKFromPublicKey(pubKey interface{}) (*JWK, error) { return key, nil } +// JWEFromX25519Key is similar to JWKFromPublicKey but is specific to X25519 keys when using a public key as raw []byte. +// This builder function presets the curve and key type in the JWK. +// Using JWKFromPublicKey for X25519 raw keys will not have these fields set and will not provide the right JWK output. +func JWEFromX25519Key(pubKey []byte) (*JWK, error) { + key := &JWK{ + JSONWebKey: jose.JSONWebKey{ + Key: pubKey, + }, + Crv: "X25519", + Kty: "OKP", + } + + // marshal/unmarshal to get all JWK's fields other than Key filled. + keyBytes, err := key.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("create JWK: %w", err) + } + + err = key.UnmarshalJSON(keyBytes) + if err != nil { + return nil, fmt.Errorf("create JWK: %w", err) + } + + return key, nil +} + // PublicKeyBytes converts a public key to bytes. func (j *JWK) PublicKeyBytes() ([]byte, error) { if j.isSecp256k1() { @@ -81,6 +111,15 @@ func (j *JWK) PublicKeyBytes() ([]byte, error) { return pubKey.SerializeCompressed(), nil } + if j.isX25519() { + x25519Key, ok := j.Key.([]byte) + if !ok { + return nil, fmt.Errorf("invalid public key in kid '%s'", j.KeyID) + } + + return x25519Key, nil + } + switch pubKey := j.Public().Key.(type) { case ed25519.PublicKey: return pubKey, nil @@ -102,12 +141,20 @@ func (j *JWK) UnmarshalJSON(jwkBytes []byte) error { return fmt.Errorf("unable to read JWK: %w", marshalErr) } + // nolint: gocritic, nestif if isSecp256k1(key.Alg, key.Kty, key.Crv) { jwk, err := unmarshalSecp256k1(&key) if err != nil { return fmt.Errorf("unable to read JWK: %w", err) } + *j = *jwk + } else if isX25519(key.Kty, key.Crv) { + jwk, err := unmarshalX25519(&key) + if err != nil { + return fmt.Errorf("unable to read X25519 JWE: %w", err) + } + *j = *jwk } else { var joseJWK jose.JSONWebKey @@ -132,9 +179,22 @@ func (j *JWK) MarshalJSON() ([]byte, error) { return marshalSecp256k1(j) } + if j.isX25519() { + return marshalX25519(j) + } + return (&j.JSONWebKey).MarshalJSON() } +func (j *JWK) isX25519() bool { + switch j.Key.(type) { + case []byte: + return isX25519(j.Kty, j.Crv) + default: + return false + } +} + func (j *JWK) isSecp256k1() bool { return isSecp256k1Key(j.Key) || isSecp256k1(j.Algorithm, j.Kty, j.Crv) } @@ -150,6 +210,10 @@ func isSecp256k1Key(pubKey interface{}) bool { } } +func isX25519(kty, crv string) bool { + return strings.EqualFold(kty, x25519Kty) && strings.EqualFold(crv, x25519Crv) +} + func isSecp256k1(alg, kty, crv string) bool { return strings.EqualFold(alg, secp256k1Alg) || (strings.EqualFold(kty, secp256k1Kty) && strings.EqualFold(crv, secp256k1Crv)) @@ -211,6 +275,49 @@ func unmarshalSecp256k1(jwk *jsonWebKey) (*JWK, error) { }, nil } +func unmarshalX25519(jwk *jsonWebKey) (*JWK, error) { + if jwk.X == nil { + return nil, ErrInvalidKey + } + + if len(jwk.X.data) != cryptoutil.Curve25519KeySize { + return nil, ErrInvalidKey + } + + return &JWK{ + JSONWebKey: jose.JSONWebKey{ + Key: jwk.X.data, KeyID: jwk.Kid, Algorithm: jwk.Alg, Use: jwk.Use, + }, + Crv: jwk.Crv, + Kty: jwk.Kty, + }, nil +} + +func marshalX25519(jwk *JWK) ([]byte, error) { + var raw jsonWebKey + + key, ok := jwk.Key.([]byte) + if !ok { + return nil, errors.New("marshalX25519: invalid key") + } + + if len(key) != cryptoutil.Curve25519KeySize { + return nil, errors.New("marshalX25519: invalid key") + } + + raw = jsonWebKey{ + Kty: x25519Kty, + Crv: x25519Crv, + X: newFixedSizeBuffer(key, cryptoutil.Curve25519KeySize), + } + + raw.Kid = jwk.KeyID + raw.Alg = jwk.Algorithm + raw.Use = jwk.Use + + return json.Marshal(raw) +} + func marshalSecp256k1(jwk *JWK) ([]byte, error) { var raw jsonWebKey diff --git a/pkg/doc/jose/jwk_test.go b/pkg/doc/jose/jwk_test.go index 295a36250a..db278047e5 100644 --- a/pkg/doc/jose/jwk_test.go +++ b/pkg/doc/jose/jwk_test.go @@ -10,6 +10,7 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "strings" "testing" "github.com/btcsuite/btcd/btcec" @@ -76,6 +77,16 @@ func TestDecodePublicKey(t *testing.T) { "alg": "EdDSA" }`, }, + { + name: "get public key bytes X25519 JWK", + jwkJSON: `{ + "kty": "OKP", + "use": "enc", + "crv": "X25519", + "kid": "sample@sample.id", + "x": "sEHL6KXs8bUz9Ss2qSWWjhhRMHVjrog0lzFENM132R8" + }`, + }, { name: "get public key bytes RSA JWK", jwkJSON: `{ @@ -229,6 +240,16 @@ func TestDecodePublicKey(t *testing.T) { }`, err: "invalid JWK", }, + { + name: "X is not defined X25519", + jwkJSON: `{ + "kty": "OKP", + "use": "enc", + "crv": "X25519", + "kid": "sample@sample.id" + }`, + err: "invalid JWK", + }, { name: "Y is not defined", jwkJSON: `{ @@ -281,6 +302,17 @@ func TestDecodePublicKey(t *testing.T) { }`, err: "unable to read JWK", }, + { + name: "invalid X25519", + jwkJSON: `{ + "kty": "OKP", + "use": "enc", + "crv": "X25519", + "x": "wQehEGTVCu32yp8IwTaBCqPUIYslyd-WoFRsfDKE9IIrIJO8RmkExUecJ5i15L9OC7rl7pwmYFR8QQgdM1ERWO", + "kid": "sample@sample.id" + }`, + err: "unable to read X25519 JWE: invalid JWK", + }, } t.Parallel() @@ -318,6 +350,25 @@ func TestJWKFromPublicKeyFailure(t *testing.T) { require.Nil(t, key) } +func TestJWKFromX25519KeyFailure(t *testing.T) { + key, err := JWEFromX25519Key([]byte(strings.Repeat("a", 33))) // try to create a key larger than X25519 + require.EqualError(t, err, "create JWK: marshalX25519: invalid key") + require.Nil(t, key) + + key, err = JWEFromX25519Key(nil) // try to create a nil key + require.EqualError(t, err, "create JWK: marshalX25519: invalid key") + require.Nil(t, key) + + key = &JWK{ + JSONWebKey: jose.JSONWebKey{ + Key: "abc", // try to create an invalid X25519 key type (string instead of []byte) + }, + } + + _, err = marshalX25519(key) + require.EqualError(t, err, "marshalX25519: invalid key") +} + func TestJWK_PublicKeyBytesValidation(t *testing.T) { jwk := &JWK{ JSONWebKey: jose.JSONWebKey{ diff --git a/pkg/doc/util/jwkkid/kid_creator.go b/pkg/doc/util/jwkkid/kid_creator.go index 1781d42a5d..fd90faa707 100644 --- a/pkg/doc/util/jwkkid/kid_creator.go +++ b/pkg/doc/util/jwkkid/kid_creator.go @@ -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" ) @@ -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) @@ -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{ @@ -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) @@ -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) @@ -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() @@ -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) +} diff --git a/pkg/doc/util/jwkkid/kid_creator_test.go b/pkg/doc/util/jwkkid/kid_creator_test.go index 0688f82f7e..24653ee42a 100644 --- a/pkg/doc/util/jwkkid/kid_creator_test.go +++ b/pkg/doc/util/jwkkid/kid_creator_test.go @@ -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" ) @@ -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) @@ -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") +} diff --git a/pkg/internal/cryptoutil/utils.go b/pkg/internal/cryptoutil/utils.go index e913193745..c2c939e307 100644 --- a/pkg/internal/cryptoutil/utils.go +++ b/pkg/internal/cryptoutil/utils.go @@ -19,134 +19,11 @@ import ( "golang.org/x/crypto/curve25519" ) -// errEmptyRecipients is used when recipients list is empty. -var errEmptyRecipients = errors.New("empty recipients") - -// errInvalidKeypair is used when a keypair is invalid. -var errInvalidKeypair = errors.New("invalid keypair") - -// SignatureAlgorithm represents a signature algorithm. -type SignatureAlgorithm string - -// EncryptionAlgorithm represents a content encryption algorithm. -type EncryptionAlgorithm string - -const ( - // encryption key types. - - // Curve25519 encryption key type. - Curve25519 = EncryptionAlgorithm("Curve25519") - - // signing key types. - - // EdDSA signature key type. - EdDSA = SignatureAlgorithm("EdDSA") -) - -// VerifyKeys is a utility function that verifies if sender key pair and recipients keys are valid (not empty). -func VerifyKeys(sender KeyPair, recipients [][]byte) error { - if len(recipients) == 0 { - return errEmptyRecipients - } - - if !isKeyPairValid(sender) { - return errInvalidKeypair - } - - if !IsChachaKeyValid(sender.Priv) || !IsChachaKeyValid(sender.Pub) { - return ErrInvalidKey - } - - return nil -} - -// IsChachaKeyValid will return true if key size is the same as chacha20poly1305.keySize -// false otherwise. -func IsChachaKeyValid(key []byte) bool { - return len(key) == chacha.KeySize -} - -// KeyPair represents a private/public key pair. -type KeyPair struct { - // Priv is a private key - Priv []byte `json:"priv,omitempty"` - // Pub is a public key - Pub []byte `json:"pub,omitempty"` -} - -// EncKeyPair represents a private/public encryption key pair. -type EncKeyPair struct { - KeyPair `json:"keypair,omitempty"` - // Alg is the encryption algorithm of keys enclosed in this key pair - Alg EncryptionAlgorithm `json:"alg,omitempty"` -} - -// SigKeyPair represents a private/public signature (verification) key pair. -type SigKeyPair struct { - KeyPair `json:"keypair,omitempty"` - // Alg is the signature algorithm of keys enclosed in this key pair - Alg SignatureAlgorithm `json:"alg,omitempty"` -} - -// MessagingKeys represents a pair of key pairs, one for encryption and one for signature -// usually stored in a KMS, it helps prevent converting signing keys into encryption ones -// TODO refactor this structure and all KeyPair handling as per issue #596. -type MessagingKeys struct { - *EncKeyPair `json:"enckeypair,omitempty"` - *SigKeyPair `json:"sigkeypair,omitempty"` -} - -// isKeyPairValid is a utility function that validates a KeyPair. -func isKeyPairValid(kp KeyPair) bool { - if kp.Priv == nil || kp.Pub == nil { - return false - } - - return true -} - -// IsEncKeyPairValid is a utility function that validates an EncKeyPair. -func IsEncKeyPairValid(kp *EncKeyPair) bool { - if !isKeyPairValid(kp.KeyPair) { - return false - } - - switch kp.Alg { - case Curve25519: - return true - default: - return false - } -} - -// IsSigKeyPairValid is a utility function that validates an EncKeyPair. -func IsSigKeyPairValid(kp *SigKeyPair) bool { - if !isKeyPairValid(kp.KeyPair) { - return false - } - - switch kp.Alg { - case EdDSA: - return true - default: - return false - } -} - -// IsMessagingKeysValid is a utility function that validates a KeyPair. -func IsMessagingKeysValid(kpb *MessagingKeys) bool { - if !IsSigKeyPairValid(kpb.SigKeyPair) || !IsEncKeyPairValid(kpb.EncKeyPair) { - return false - } - - return true -} - // Derive25519KEK is a utility function that will derive an ephemeral // symmetric key (kek) using fromPrivKey and toPubKey. func Derive25519KEK(alg, apu, apv []byte, fromPrivKey, toPubKey *[chacha.KeySize]byte) ([]byte, error) { if fromPrivKey == nil || toPubKey == nil { - return nil, ErrInvalidKey + return nil, errors.New("invalid key") } const ( @@ -251,9 +128,3 @@ func SecretEd25519toCurve25519(priv []byte) ([]byte, error) { return sKOut[:], nil } - -// ErrKeyNotFound is returned when key not found. -var ErrKeyNotFound = errors.New("key not found") - -// ErrInvalidKey is used when a key is invalid. -var ErrInvalidKey = errors.New("invalid key") diff --git a/pkg/internal/cryptoutil/utils_test.go b/pkg/internal/cryptoutil/utils_test.go index 634d992841..73136e5fd4 100644 --- a/pkg/internal/cryptoutil/utils_test.go +++ b/pkg/internal/cryptoutil/utils_test.go @@ -15,66 +15,9 @@ import ( chacha "golang.org/x/crypto/chacha20poly1305" ) -func TestIsKeyPairValid(t *testing.T) { - require.False(t, isKeyPairValid(KeyPair{})) - - pubKey := []byte("testpublickey") - privKey := []byte("testprivatekey") - - validChachaKey, err := base64.RawURLEncoding.DecodeString("c8CSJr_27PN9xWCpzXNmepRndD6neQcnO9DS0YWjhNs") - require.NoError(t, err) - - require.False(t, isKeyPairValid(KeyPair{Priv: privKey, Pub: nil})) - require.False(t, isKeyPairValid(KeyPair{Priv: nil, Pub: pubKey})) - require.True(t, isKeyPairValid(KeyPair{Priv: privKey, Pub: pubKey})) - - require.EqualError(t, - VerifyKeys( - KeyPair{Priv: privKey, Pub: pubKey}, - [][]byte{[]byte("abc"), []byte("def")}), - ErrInvalidKey.Error()) - require.EqualError(t, - VerifyKeys( - KeyPair{Priv: privKey, Pub: pubKey}, - [][]byte{}), - errEmptyRecipients.Error()) - require.EqualError(t, VerifyKeys(KeyPair{}, [][]byte{[]byte("abc"), []byte("def")}), errInvalidKeypair.Error()) - require.NoError(t, VerifyKeys(KeyPair{Priv: validChachaKey, Pub: validChachaKey}, [][]byte{validChachaKey})) - - require.False(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: privKey, Pub: nil}, Curve25519}, - &SigKeyPair{KeyPair{Priv: nil, Pub: pubKey}, EdDSA}, - })) - - require.False(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: nil, Pub: pubKey}, Curve25519}, - &SigKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, EdDSA}, - })) - - require.False(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: nil, Pub: pubKey}, Curve25519}, - &SigKeyPair{KeyPair{Priv: privKey, Pub: nil}, EdDSA}, - })) - - require.False(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, ""}, - &SigKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, EdDSA}, - })) - - require.False(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, Curve25519}, - &SigKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, ""}, - })) - - require.True(t, IsMessagingKeysValid(&MessagingKeys{ - &EncKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, Curve25519}, - &SigKeyPair{KeyPair{Priv: privKey, Pub: pubKey}, EdDSA}, - })) -} - func TestDeriveKEK_Util(t *testing.T) { kek, err := Derive25519KEK(nil, nil, nil, nil, nil) - require.EqualError(t, err, ErrInvalidKey.Error()) + require.EqualError(t, err, "invalid key") require.Empty(t, kek) validChachaKey, err := base64.RawURLEncoding.DecodeString("c8CSJr_27PN9xWCpzXNmepRndD6neQcnO9DS0YWjhNs") @@ -83,7 +26,7 @@ func TestDeriveKEK_Util(t *testing.T) { chachaKey := new([chacha.KeySize]byte) copy(chachaKey[:], validChachaKey) kek, err = Derive25519KEK(nil, nil, nil, chachaKey, nil) - require.EqualError(t, err, ErrInvalidKey.Error()) + require.EqualError(t, err, "invalid key") require.Empty(t, kek) validChachaKey2, err := base64.RawURLEncoding.DecodeString("AAjrHjiFLw6kf6CZ5zqH1ooG3y2aQhuqxmUvqJnIvDI") diff --git a/pkg/kms/api.go b/pkg/kms/api.go index 2fdfa346a8..30a9640bed 100644 --- a/pkg/kms/api.go +++ b/pkg/kms/api.go @@ -110,12 +110,14 @@ const ( RSAPS256 = "RSAPS256" // HMACSHA256Tag256 key type value. HMACSHA256Tag256 = "HMACSHA256Tag256" - // ECDH256KWAES256GCM key type value. - ECDH256KWAES256GCM = "ECDH256KWAES256GCM" - // ECDH384KWAES256GCM key type value. - ECDH384KWAES256GCM = "ECDH384KWAES256GCM" - // ECDH521KWAES256GCM key type value. - ECDH521KWAES256GCM = "ECDH521KWAES256GCM" + // NISTP256ECDHKW key type value. + NISTP256ECDHKW = "NISTP256ECDHKW" + // NISTP384ECDHKW key type value. + NISTP384ECDHKW = "NISTP384ECDHKW" + // NISTP521ECDHKW key type value. + NISTP521ECDHKW = "NISTP521ECDHKW" + // X25519ECDHKW key type value. + X25519ECDHKW = "X25519ECDHKW" ) // KeyType represents a key type supported by the KMS. @@ -154,12 +156,14 @@ const ( RSAPS256Type = KeyType(RSAPS256) // HMACSHA256Tag256Type key type value. HMACSHA256Tag256Type = KeyType(HMACSHA256Tag256) - // ECDH256KWAES256GCMType key type value. - ECDH256KWAES256GCMType = KeyType(ECDH256KWAES256GCM) - // ECDH384KWAES256GCMType key type value. - ECDH384KWAES256GCMType = KeyType(ECDH384KWAES256GCM) - // ECDH521KWAES256GCMType key type value. - ECDH521KWAES256GCMType = KeyType(ECDH521KWAES256GCM) + // NISTP256ECDHKWType key type value. + NISTP256ECDHKWType = KeyType(NISTP256ECDHKW) + // NISTP384ECDHKWType key type value. + NISTP384ECDHKWType = KeyType(NISTP384ECDHKW) + // NISTP521ECDHKWType key type value. + NISTP521ECDHKWType = KeyType(NISTP521ECDHKW) + // X25519ECDHKWType key type value. + X25519ECDHKWType = KeyType(X25519ECDHKW) ) // CryptoBox is a libsodium crypto service used by legacy authcrypt packer. diff --git a/pkg/kms/localkms/kid_creator_test.go b/pkg/kms/localkms/kid_creator_test.go index 82e20af3a9..915777f82e 100644 --- a/pkg/kms/localkms/kid_creator_test.go +++ b/pkg/kms/localkms/kid_creator_test.go @@ -9,10 +9,13 @@ package localkms import ( "crypto/ed25519" "crypto/rand" + "encoding/json" "testing" "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/kms" ) @@ -28,12 +31,28 @@ func TestCreateKID(t *testing.T) { 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") + + randomKey := make([]byte, 32) + _, err = rand.Read(randomKey) + require.NoError(t, err) + + x25519Key := &cryptoapi.PublicKey{ + Curve: "X25519", + Type: ecdhpb.KeyType_OKP.String(), + X: randomKey, + } + mX25519Key, err := json.Marshal(x25519Key) + require.NoError(t, err) + + kid, err = CreateKID(mX25519Key, kms.X25519ECDHKWType) + require.NoError(t, err) + require.NotEmpty(t, kid) } diff --git a/pkg/kms/localkms/localkms.go b/pkg/kms/localkms/localkms.go index cb747985aa..e910fce33c 100644 --- a/pkg/kms/localkms/localkms.go +++ b/pkg/kms/localkms/localkms.go @@ -204,12 +204,14 @@ func getKeyTemplate(keyType kms.KeyType) (*tinkpb.KeyTemplate, error) { return signature.ED25519KeyWithoutPrefixTemplate(), nil case kms.HMACSHA256Tag256Type: return mac.HMACSHA256Tag256KeyTemplate(), nil - case kms.ECDH256KWAES256GCMType: + case kms.NISTP256ECDHKWType: return ecdh.NISTP256ECDHKWKeyTemplate(), nil - case kms.ECDH384KWAES256GCMType: + case kms.NISTP384ECDHKWType: return ecdh.NISTP384ECDHKWKeyTemplate(), nil - case kms.ECDH521KWAES256GCMType: + case kms.NISTP521ECDHKWType: return ecdh.NISTP521ECDHKWKeyTemplate(), nil + case kms.X25519ECDHKWType: + return ecdh.X25519ECDHKWKeyTemplate(), nil default: return nil, fmt.Errorf("getKeyTemplate: key type '%s' unrecognized", keyType) } diff --git a/pkg/kms/localkms/localkms_test.go b/pkg/kms/localkms/localkms_test.go index 2a78fd31f1..0f38da728e 100644 --- a/pkg/kms/localkms/localkms_test.go +++ b/pkg/kms/localkms/localkms_test.go @@ -260,15 +260,16 @@ func TestLocalKMS_Success(t *testing.T) { kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363, kms.ED25519Type, - kms.ECDH256KWAES256GCMType, - kms.ECDH384KWAES256GCMType, - kms.ECDH521KWAES256GCMType, + kms.NISTP256ECDHKWType, + kms.NISTP384ECDHKWType, + kms.NISTP521ECDHKWType, + kms.X25519ECDHKWType, } for _, v := range keyTemplates { // test Create() a new key keyID, newKeyHandle, e := kmsService.Create(v) - require.NoError(t, e) + require.NoError(t, e, "failed on template %v", v) require.NotEmpty(t, newKeyHandle) require.NotEmpty(t, keyID) diff --git a/pkg/kms/localkms/pubkey_writer.go b/pkg/kms/localkms/pubkey_writer.go index 8bf9a7b189..a83f92e9ff 100644 --- a/pkg/kms/localkms/pubkey_writer.go +++ b/pkg/kms/localkms/pubkey_writer.go @@ -25,9 +25,10 @@ import ( ) const ( - ecdsaVerifierTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey" - ed25519VerifierTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey" - ecdhAESPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.NistPEcdhKwPublicKey" + ecdsaVerifierTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey" + ed25519VerifierTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey" + nistPECDHKWPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.NistPEcdhKwPublicKey" + x25519ECDHKWPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.X25519EcdhKwPublicKey" ) // PubKeyWriter will write the raw bytes of a Tink KeySet's primary public key @@ -70,7 +71,7 @@ func write(w io.Writer, msg *tinkpb.Keyset) error { if err != nil { return err } - case ecdhAESPublicKeyTypeURL: + case nistPECDHKWPublicKeyTypeURL, x25519ECDHKWPublicKeyTypeURL: pkW := keyio.NewWriter(w) err = pkW.Write(msg) @@ -97,7 +98,8 @@ func write(w io.Writer, msg *tinkpb.Keyset) error { func writePubKey(w io.Writer, key *tinkpb.Keyset_Key) (bool, error) { var marshaledRawPubKey []byte - // TODO add other key types than the ones below and other than ecdhAESPublicKeyTypeURL + // TODO add other key types than the ones below and other than nistPECDHKWPublicKeyTypeURL and + // TODO x25519ECDHKWPublicKeyTypeURL(eg: BBS+, secp256k1 when introduced in KMS). switch key.KeyData.TypeUrl { case ecdsaVerifierTypeURL: pubKeyProto := new(ecdsapb.EcdsaPublicKey)