diff --git a/VERSION.txt b/VERSION.txt index 32bd932..feaae22 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.12.0 \ No newline at end of file +1.13.0 diff --git a/aes/aes_cipher.go b/aes/aes_cipher.go index 89e7f13..ca95af5 100644 --- a/aes/aes_cipher.go +++ b/aes/aes_cipher.go @@ -20,6 +20,7 @@ import ( "errors" "strings" + "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap" "github.com/hashicorp/go-uuid" ) @@ -89,3 +90,13 @@ func (c *Cipher) VerifyCheckValue(checkValue string) bool { return strings.EqualFold(derivedCheckValue, checkValue) } + +// KeyWrap implements AES Key Wrap (RFC 3394). +func (c *Cipher) KeyWrap(plainKeyBytes []byte) ([]byte, error) { + return keywrap.Wrap(c.KeyBytes, plainKeyBytes) +} + +// KeyUnwrap implements AES Key Unwrap (RFC 3394). +func (c *Cipher) KeyUnwrap(wrappedKeyBytes []byte) ([]byte, error) { + return keywrap.Unwrap(c.KeyBytes, wrappedKeyBytes) +} diff --git a/aes/aes_cipher_test.go b/aes/aes_cipher_test.go index e707bc6..f37a3b4 100644 --- a/aes/aes_cipher_test.go +++ b/aes/aes_cipher_test.go @@ -12,6 +12,7 @@ package aes import ( + "bytes" "encoding/hex" "testing" @@ -85,6 +86,56 @@ func TestAESVerifyCheckValue(t *testing.T) { } } +func TestAESCipher_KeyWrapAndUnwrap(t *testing.T) { + kekBytes, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F") + cipher, _ := New(kekBytes) + + plainKeyBytes, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF") + + wrapped, err := cipher.KeyWrap(plainKeyBytes) + if err != nil { + t.Fatalf("Did not expect a KeyWrap error but got %q", err) + } + + unwrapped, err := cipher.KeyUnwrap(wrapped) + if err != nil { + t.Fatalf("Did not expect a KeyUnwrap error but got %q", err) + } + + if !bytes.Equal(plainKeyBytes, unwrapped) { + t.Errorf("Expected %s but got %s", hex.EncodeToString(plainKeyBytes), hex.EncodeToString(unwrapped)) + } +} + +func TestAESCipher_KeyWrapRFC3394Vector(t *testing.T) { + // RFC 3394 test vector: 256-bit KEK, 128-bit key data + kekBytes, _ := hex.DecodeString("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F") + cipher, _ := New(kekBytes) + + plainKeyBytes, _ := hex.DecodeString("00112233445566778899AABBCCDDEEFF") + expectedWrapped, _ := hex.DecodeString("64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7") + + wrapped, err := cipher.KeyWrap(plainKeyBytes) + if err != nil { + t.Fatalf("Did not expect a KeyWrap error but got %q", err) + } + + if !bytes.Equal(wrapped, expectedWrapped) { + t.Errorf("Expected wrapped %s but got %s", hex.EncodeToString(expectedWrapped), hex.EncodeToString(wrapped)) + } +} + +func TestAESCipher_KeyUnwrapInvalidData(t *testing.T) { + kekBytes, _ := uuid.GenerateRandomBytes(32) + cipher, _ := New(kekBytes) + + badData := make([]byte, 24) + _, err := cipher.KeyUnwrap(badData) + if err == nil { + t.Error("Expected an error for invalid wrapped data") + } +} + func TestAESCipher_EncryptAndDecryptPrefixNonce(t *testing.T) { keyBytes, _ := uuid.GenerateRandomBytes(32) cipher, _ := New(keyBytes) diff --git a/go.mod b/go.mod index d9307fc..1b00700 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/transferwise/crypto go 1.26 require ( + github.com/ProtonMail/go-crypto v1.4.0 github.com/ProtonMail/gopenpgp/v2 v2.9.0 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/vault/sdk v0.23.0 ) require ( - github.com/ProtonMail/go-crypto v1.4.0 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/pkg/errors v0.9.1 // indirect