Add AES Key Wrap mode.
This is needed in order to support Web Crypto.
https://code.google.com/p/chromium/issues/detail?id=396407
Change-Id: I900d8cad2716c2e3341eeae153659502326c9173
Reviewed-on: https://boringssl-review.googlesource.com/1335
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/cipher/aead_test.c b/crypto/cipher/aead_test.c
index 516ec31..f0f3cf4 100644
--- a/crypto/cipher/aead_test.c
+++ b/crypto/cipher/aead_test.c
@@ -168,6 +168,10 @@
aead = EVP_aead_chacha20_poly1305();
} else if (strcmp(argv[1], "rc4-md5") == 0) {
aead = EVP_aead_rc4_md5_tls();
+ } else if (strcmp(argv[1], "aes-128-key-wrap") == 0) {
+ aead = EVP_aead_aes_128_key_wrap();
+ } else if (strcmp(argv[1], "aes-256-key-wrap") == 0) {
+ aead = EVP_aead_aes_256_key_wrap();
} else {
fprintf(stderr, "Unknown AEAD: %s\n", argv[1]);
return 2;
diff --git a/crypto/cipher/aes_128_key_wrap_tests.txt b/crypto/cipher/aes_128_key_wrap_tests.txt
new file mode 100644
index 0000000..561ec90
--- /dev/null
+++ b/crypto/cipher/aes_128_key_wrap_tests.txt
@@ -0,0 +1,9 @@
+# These test vectors have been taken from
+# http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf
+
+KEY: 000102030405060708090A0B0C0D0E0F
+NONCE:
+IN: 00112233445566778899AABBCCDDEEFF
+AD:
+CT: 1FA68B0A8112B447AEF34BD8FB5A7B82
+TAG: 9D3E862371D2CFE5
diff --git a/crypto/cipher/aes_256_key_wrap_tests.txt b/crypto/cipher/aes_256_key_wrap_tests.txt
new file mode 100644
index 0000000..92d3a04
--- /dev/null
+++ b/crypto/cipher/aes_256_key_wrap_tests.txt
@@ -0,0 +1,23 @@
+# These test vectors have been taken from
+# http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf
+
+KEY: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
+NONCE:
+IN: 00112233445566778899AABBCCDDEEFF
+AD:
+CT: 64E8C3F9CE0F5BA263E9777905818A2A
+TAG: 93C8191E7D6E8AE7
+
+KEY: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
+NONCE:
+IN: 00112233445566778899AABBCCDDEEFF0001020304050607
+AD:
+CT: A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB895
+TAG: 8CD5D17D6B254DA1
+
+KEY: 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
+NONCE:
+IN: 00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F
+AD:
+CT: 28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43B
+TAG: FB988B9B7A02DD21
diff --git a/crypto/cipher/cipher_error.c b/crypto/cipher/cipher_error.c
index fa28f63..ed72436 100644
--- a/crypto/cipher/cipher_error.c
+++ b/crypto/cipher/cipher_error.c
@@ -28,6 +28,9 @@
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_gcm_init, 0), "aead_aes_gcm_init"},
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_gcm_open, 0), "aead_aes_gcm_open"},
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_gcm_seal, 0), "aead_aes_gcm_seal"},
+ {ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_key_wrap_init, 0), "aead_aes_key_wrap_init"},
+ {ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_key_wrap_open, 0), "aead_aes_key_wrap_open"},
+ {ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_aes_key_wrap_seal, 0), "aead_aes_key_wrap_seal"},
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_chacha20_poly1305_init, 0), "aead_chacha20_poly1305_init"},
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_chacha20_poly1305_open, 0), "aead_chacha20_poly1305_open"},
{ERR_PACK(ERR_LIB_CIPHER, CIPHER_F_aead_chacha20_poly1305_seal, 0), "aead_chacha20_poly1305_seal"},
@@ -52,7 +55,11 @@
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_OUTPUT_ALIASES_INPUT), "OUTPUT_ALIASES_INPUT"},
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_TAG_TOO_LARGE), "TAG_TOO_LARGE"},
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_TOO_LARGE), "TOO_LARGE"},
+ {ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_UNSUPPORTED_AD_SIZE), "UNSUPPORTED_AD_SIZE"},
+ {ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_UNSUPPORTED_INPUT_SIZE), "UNSUPPORTED_INPUT_SIZE"},
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_UNSUPPORTED_KEY_SIZE), "UNSUPPORTED_KEY_SIZE"},
+ {ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_UNSUPPORTED_NONCE_SIZE), "UNSUPPORTED_NONCE_SIZE"},
+ {ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_UNSUPPORTED_TAG_SIZE), "UNSUPPORTED_TAG_SIZE"},
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_WRAP_MODE_NOT_ALLOWED), "WRAP_MODE_NOT_ALLOWED"},
{ERR_PACK(ERR_LIB_CIPHER, 0, CIPHER_R_WRONG_FINAL_BLOCK_LENGTH), "WRONG_FINAL_BLOCK_LENGTH"},
{0, NULL},
diff --git a/crypto/cipher/e_aes.c b/crypto/cipher/e_aes.c
index 741fd01..6e3fafb 100644
--- a/crypto/cipher/e_aes.c
+++ b/crypto/cipher/e_aes.c
@@ -986,3 +986,267 @@
const EVP_AEAD *EVP_aead_aes_128_gcm() { return &aead_aes_128_gcm; }
const EVP_AEAD *EVP_aead_aes_256_gcm() { return &aead_aes_256_gcm; }
+
+
+/* AES Key Wrap is specified in
+ * http://csrc.nist.gov/groups/ST/toolkit/documents/kms/key-wrap.pdf
+ * or https://tools.ietf.org/html/rfc3394 */
+
+struct aead_aes_key_wrap_ctx {
+ uint8_t key[32];
+ unsigned key_bits;
+};
+
+static int aead_aes_key_wrap_init(EVP_AEAD_CTX *ctx, const uint8_t *key,
+ size_t key_len, size_t tag_len) {
+ struct aead_aes_key_wrap_ctx *kw_ctx;
+ const size_t key_bits = key_len * 8;
+
+ if (key_bits != 128 && key_bits != 256) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_init, CIPHER_R_BAD_KEY_LENGTH);
+ return 0; /* EVP_AEAD_CTX_init should catch this. */
+ }
+
+ if (tag_len == EVP_AEAD_DEFAULT_TAG_LENGTH) {
+ tag_len = 8;
+ }
+
+ if (tag_len != 8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_init,
+ CIPHER_R_UNSUPPORTED_TAG_SIZE);
+ return 0;
+ }
+
+ kw_ctx = OPENSSL_malloc(sizeof(struct aead_aes_key_wrap_ctx));
+ if (kw_ctx == NULL) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_init, ERR_R_MALLOC_FAILURE);
+ return 0;
+ }
+
+ memcpy(kw_ctx->key, key, key_len);
+ kw_ctx->key_bits = key_bits;
+
+ ctx->aead_state = kw_ctx;
+ return 1;
+}
+
+static void aead_aes_key_wrap_cleanup(EVP_AEAD_CTX *ctx) {
+ struct aead_aes_key_wrap_ctx *kw_ctx = ctx->aead_state;
+ OPENSSL_cleanse(kw_ctx, sizeof(struct aead_aes_key_wrap_ctx));
+ OPENSSL_free(kw_ctx);
+}
+
+/* kDefaultAESKeyWrapNonce is the default nonce value given in 2.2.3.1. */
+static const uint8_t kDefaultAESKeyWrapNonce[8] = {0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6};
+
+
+static int aead_aes_key_wrap_seal(const EVP_AEAD_CTX *ctx, uint8_t *out,
+ size_t *out_len, size_t max_out_len,
+ const uint8_t *nonce, size_t nonce_len,
+ const uint8_t *in, size_t in_len,
+ const uint8_t *ad, size_t ad_len) {
+ const struct aead_aes_key_wrap_ctx *kw_ctx = ctx->aead_state;
+ union {
+ double align;
+ AES_KEY ks;
+ } ks;
+ /* Variables in this function match up with the variables in the second half
+ * of section 2.2.1. */
+ unsigned i, j, n;
+ uint8_t A[AES_BLOCK_SIZE];
+
+ if (ad_len != 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_UNSUPPORTED_AD_SIZE);
+ return 0;
+ }
+
+ if (nonce_len == 0) {
+ nonce = kDefaultAESKeyWrapNonce;
+ nonce_len = sizeof(kDefaultAESKeyWrapNonce);
+ }
+
+ if (nonce_len != 8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_UNSUPPORTED_NONCE_SIZE);
+ return 0;
+ }
+
+ if (in_len % 8 != 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_UNSUPPORTED_INPUT_SIZE);
+ return 0;
+ }
+
+ /* The code below only handles a 32-bit |t| thus 6*|n| must be less than
+ * 2^32, where |n| is |in_len| / 8. So in_len < 4/3 * 2^32 and we
+ * conservatively cap it to 2^32-16 to stop 32-bit platforms complaining that
+ * a comparision is always true. */
+ if (in_len > 0xfffffff0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal, CIPHER_R_TOO_LARGE);
+ return 0;
+ }
+
+ n = in_len / 8;
+
+ if (n < 2) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_UNSUPPORTED_INPUT_SIZE);
+ return 0;
+ }
+
+ if (in_len + 8 < in_len) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal, CIPHER_R_TOO_LARGE);
+ return 0;
+ }
+
+ if (max_out_len < in_len + 8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ if (AES_set_encrypt_key(kw_ctx->key, kw_ctx->key_bits, &ks.ks) < 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_seal,
+ CIPHER_R_AES_KEY_SETUP_FAILED);
+ return 0;
+ }
+
+ memmove(out + 8, in, in_len);
+ memcpy(A, nonce, 8);
+
+ for (j = 0; j < 6; j++) {
+ for (i = 1; i <= n; i++) {
+ uint32_t t;
+
+ memcpy(A + 8, out + 8 * i, 8);
+ AES_encrypt(A, A, &ks.ks);
+ t = n * j + i;
+ A[7] ^= t & 0xff;
+ A[6] ^= (t >> 8) & 0xff;
+ A[5] ^= (t >> 16) & 0xff;
+ A[4] ^= (t >> 24) & 0xff;
+ memcpy(out + 8 * i, A + 8, 8);
+ }
+ }
+
+ memcpy(out, A, 8);
+ *out_len = in_len + 8;
+ return 1;
+}
+
+static int aead_aes_key_wrap_open(const EVP_AEAD_CTX *ctx, uint8_t *out,
+ size_t *out_len, size_t max_out_len,
+ const uint8_t *nonce, size_t nonce_len,
+ const uint8_t *in, size_t in_len,
+ const uint8_t *ad, size_t ad_len) {
+ const struct aead_aes_key_wrap_ctx *kw_ctx = ctx->aead_state;
+ union {
+ double align;
+ AES_KEY ks;
+ } ks;
+ /* Variables in this function match up with the variables in the second half
+ * of section 2.2.1. */
+ unsigned i, j, n;
+ uint8_t A[AES_BLOCK_SIZE];
+
+ if (ad_len != 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open,
+ CIPHER_R_UNSUPPORTED_AD_SIZE);
+ return 0;
+ }
+
+ if (nonce_len == 0) {
+ nonce = kDefaultAESKeyWrapNonce;
+ nonce_len = sizeof(kDefaultAESKeyWrapNonce);
+ }
+
+ if (nonce_len != 8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open,
+ CIPHER_R_UNSUPPORTED_NONCE_SIZE);
+ return 0;
+ }
+
+ if (in_len % 8 != 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open,
+ CIPHER_R_UNSUPPORTED_INPUT_SIZE);
+ return 0;
+ }
+
+ /* The code below only handles a 32-bit |t| thus 6*|n| must be less than
+ * 2^32, where |n| is |in_len| / 8. So in_len < 4/3 * 2^32 and we
+ * conservatively cap it to 2^32-8 to stop 32-bit platforms complaining that
+ * a comparision is always true. */
+ if (in_len > 0xfffffff8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open, CIPHER_R_TOO_LARGE);
+ return 0;
+ }
+
+ if (in_len < 24) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_gcm_open, CIPHER_R_BAD_DECRYPT);
+ return 0;
+ }
+
+ n = (in_len / 8) - 1;
+
+ if (max_out_len < in_len - 8) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open,
+ CIPHER_R_BUFFER_TOO_SMALL);
+ return 0;
+ }
+
+ if (AES_set_decrypt_key(kw_ctx->key, kw_ctx->key_bits, &ks.ks) < 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_key_wrap_open,
+ CIPHER_R_AES_KEY_SETUP_FAILED);
+ return 0;
+ }
+
+ memcpy(A, in, 8);
+ memmove(out, in + 8, in_len);
+
+ for (j = 5; j < 6; j--) {
+ for (i = n; i > 0; i--) {
+ uint32_t t;
+
+ t = n * j + i;
+ A[7] ^= t & 0xff;
+ A[6] ^= (t >> 8) & 0xff;
+ A[5] ^= (t >> 16) & 0xff;
+ A[4] ^= (t >> 24) & 0xff;
+ memcpy(A + 8, out + 8 * (i - 1), 8);
+ AES_decrypt(A, A, &ks.ks);
+ memcpy(out + 8 * (i - 1), A + 8, 8);
+ }
+ }
+
+ if (CRYPTO_memcmp(A, nonce, 8) != 0) {
+ OPENSSL_PUT_ERROR(CIPHER, aead_aes_gcm_open, CIPHER_R_BAD_DECRYPT);
+ return 0;
+ }
+
+ *out_len = in_len - 8;
+ return 1;
+}
+
+static const EVP_AEAD aead_aes_128_key_wrap = {
+ 16, /* key len */
+ 8, /* nonce len */
+ 8, /* overhead */
+ 8, /* max tag length */
+ aead_aes_key_wrap_init, aead_aes_key_wrap_cleanup,
+ aead_aes_key_wrap_seal, aead_aes_key_wrap_open,
+};
+
+static const EVP_AEAD aead_aes_256_key_wrap = {
+ 32, /* key len */
+ 8, /* nonce len */
+ 8, /* overhead */
+ 8, /* max tag length */
+ aead_aes_key_wrap_init, aead_aes_key_wrap_cleanup,
+ aead_aes_key_wrap_seal, aead_aes_key_wrap_open,
+};
+
+const EVP_AEAD *EVP_aead_aes_128_key_wrap() { return &aead_aes_128_key_wrap; }
+
+const EVP_AEAD *EVP_aead_aes_256_key_wrap() { return &aead_aes_256_key_wrap; }
diff --git a/include/openssl/aead.h b/include/openssl/aead.h
index 767ab78..0531ad9 100644
--- a/include/openssl/aead.h
+++ b/include/openssl/aead.h
@@ -101,6 +101,20 @@
/* EVP_aead_chacha20_poly1305 is an AEAD built from ChaCha20 and Poly1305. */
const EVP_AEAD *EVP_aead_chacha20_poly1305();
+/* EVP_aead_aes_128_key_wrap is AES-128 Key Wrap mode. This should never be
+ * used except to interoperate with existing systems that use this mode.
+ *
+ * If the nonce is emtpy then the default nonce will be used, otherwise it must
+ * be eight bytes long. The input must be a multiple of eight bytes long. No
+ * additional data can be given to this mode. */
+const EVP_AEAD *EVP_aead_aes_128_key_wrap();
+
+/* EVP_aead_aes_256_key_wrap is AES-256 in Key Wrap mode. This should never be
+ * used except to interoperate with existing systems that use this mode.
+ *
+ * See |EVP_aead_aes_128_key_wrap| for details. */
+const EVP_AEAD *EVP_aead_aes_256_key_wrap();
+
/* TLS specific AEAD algorithms.
*
diff --git a/include/openssl/cipher.h b/include/openssl/cipher.h
index 7d25005..191d44a 100644
--- a/include/openssl/cipher.h
+++ b/include/openssl/cipher.h
@@ -439,6 +439,9 @@
#define CIPHER_F_aead_rc4_md5_tls_init 116
#define CIPHER_F_aead_rc4_md5_tls_seal 117
#define CIPHER_F_aead_rc4_md5_tls_open 118
+#define CIPHER_F_aead_aes_key_wrap_seal 119
+#define CIPHER_F_aead_aes_key_wrap_init 120
+#define CIPHER_F_aead_aes_key_wrap_open 121
#define CIPHER_R_WRAP_MODE_NOT_ALLOWED 100
#define CIPHER_R_AES_KEY_SETUP_FAILED 101
#define CIPHER_R_INPUT_NOT_INITIALIZED 102
@@ -458,5 +461,9 @@
#define CIPHER_R_IV_TOO_LARGE 116
#define CIPHER_R_INVALID_AD_SIZE 117
#define CIPHER_R_INVALID_AD 118
+#define CIPHER_R_UNSUPPORTED_TAG_SIZE 119
+#define CIPHER_R_UNSUPPORTED_INPUT_SIZE 120
+#define CIPHER_R_UNSUPPORTED_AD_SIZE 121
+#define CIPHER_R_UNSUPPORTED_NONCE_SIZE 122
#endif /* OPENSSL_HEADER_CIPHER_H */
diff --git a/util/all_tests.sh b/util/all_tests.sh
index a7e961e..27405fb 100644
--- a/util/all_tests.sh
+++ b/util/all_tests.sh
@@ -19,6 +19,8 @@
./crypto/cipher/aead_test aes-256-gcm ../crypto/cipher/aes_256_gcm_tests.txt
./crypto/cipher/aead_test chacha20-poly1305 ../crypto/cipher/chacha20_poly1305_tests.txt
./crypto/cipher/aead_test rc4-md5 ../crypto/cipher/rc4_md5_tests.txt
+./crypto/cipher/aead_test aes-128-key-wrap ../crypto/cipher/aes_128_key_wrap_tests.txt
+./crypto/cipher/aead_test aes-256-key-wrap ../crypto/cipher/aes_256_key_wrap_tests.txt
./crypto/base64/base64_test
./crypto/bio/bio_test
./crypto/bn/bn_test