Skip to content

Commit

Permalink
Add AES-KWP
Browse files Browse the repository at this point in the history
KWP is Key Wrap with Padding, defined in RFC 5649 and SP 800-38F. Like
Key Wrap, it's a poor-man's AEAD and shouldn't be used. However, some
existing systems use it and we need to interoperate.

The interface of the added functions is a little unfortunate, but they
match the interfaces of the existing Key Wrap functions which, in turn,
match functions in OpenSSL. Hopefully this way, if OpenSSL ever add
support, we'll line up.

Change-Id: I3496c288f32230a891261586ca2e9c4ee8456c09
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36324
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
  • Loading branch information
Adam Langley authored and CQ bot account: commit-bot@chromium.org committed Jun 13, 2019
1 parent 18254e2 commit 8f574c3
Show file tree
Hide file tree
Showing 7 changed files with 1,776 additions and 9 deletions.
70 changes: 70 additions & 0 deletions crypto/fipsmodule/aes/aes_test.cc
Expand Up @@ -122,12 +122,40 @@ static void TestKeyWrap(FileTest *t) {
ciphertext.data(), ciphertext.size()));
}

static void TestKeyWrapWithPadding(FileTest *t) {
std::vector<uint8_t> key, plaintext, ciphertext;
ASSERT_TRUE(t->GetBytes(&key, "Key"));
ASSERT_TRUE(t->GetBytes(&plaintext, "Plaintext"));
ASSERT_TRUE(t->GetBytes(&ciphertext, "Ciphertext"));

// Test encryption.
AES_KEY aes_key;
ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key));
std::unique_ptr<uint8_t[]> buf(new uint8_t[plaintext.size() + 15]);
size_t len;
ASSERT_TRUE(AES_wrap_key_padded(&aes_key, buf.get(), &len,
plaintext.size() + 15, plaintext.data(),
plaintext.size()));
EXPECT_EQ(Bytes(ciphertext), Bytes(buf.get(), static_cast<size_t>(len)));

// Test decryption
ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key));
buf.reset(new uint8_t[ciphertext.size() - 8]);
ASSERT_TRUE(AES_unwrap_key_padded(&aes_key, buf.get(), &len,
ciphertext.size() - 8, ciphertext.data(),
ciphertext.size()));
ASSERT_EQ(len, plaintext.size());
EXPECT_EQ(Bytes(plaintext), Bytes(buf.get(), static_cast<size_t>(len)));
}

TEST(AESTest, TestVectors) {
FileTestGTest("crypto/fipsmodule/aes/aes_tests.txt", [](FileTest *t) {
if (t->GetParameter() == "Raw") {
TestRaw(t);
} else if (t->GetParameter() == "KeyWrap") {
TestKeyWrap(t);
} else if (t->GetParameter() == "KeyWrapWithPadding") {
TestKeyWrapWithPadding(t);
} else {
ADD_FAILURE() << "Unknown mode " << t->GetParameter();
}
Expand Down Expand Up @@ -172,6 +200,48 @@ TEST(AESTest, WycheproofKeyWrap) {
});
}

TEST(AESTest, WycheproofKeyWrapWithPadding) {
FileTestGTest("third_party/wycheproof_testvectors/kwp_test.txt",
[](FileTest *t) {
std::string key_size;
ASSERT_TRUE(t->GetInstruction(&key_size, "keySize"));
std::vector<uint8_t> ct, key, msg;
ASSERT_TRUE(t->GetBytes(&ct, "ct"));
ASSERT_TRUE(t->GetBytes(&key, "key"));
ASSERT_TRUE(t->GetBytes(&msg, "msg"));
ASSERT_EQ(static_cast<unsigned>(atoi(key_size.c_str())), key.size() * 8);
WycheproofResult result;
ASSERT_TRUE(GetWycheproofResult(t, &result));

// Wycheproof contains test vectors with empty messages that it believes
// should pass. However, both RFC 5649 and SP 800-38F section 5.3.1 say that
// the minimum length is one. Therefore we consider test cases with an empty
// message to be invalid.
if (result != WycheproofResult::kInvalid && !msg.empty()) {
AES_KEY aes;
ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes));
std::vector<uint8_t> out(ct.size() - 8);
size_t len;
ASSERT_TRUE(AES_unwrap_key_padded(&aes, out.data(), &len, ct.size() - 8,
ct.data(), ct.size()));
EXPECT_EQ(Bytes(msg), Bytes(out.data(), len));

out.resize(msg.size() + 15);
ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes));
ASSERT_TRUE(AES_wrap_key_padded(&aes, out.data(), &len, msg.size() + 15,
msg.data(), msg.size()));
EXPECT_EQ(Bytes(ct), Bytes(out.data(), len));
} else {
AES_KEY aes;
ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes));
std::vector<uint8_t> out(ct.size());
size_t len;
ASSERT_FALSE(AES_unwrap_key_padded(&aes, out.data(), &len, ct.size(),
ct.data(), ct.size()));
}
});
}

TEST(AESTest, WrapBadLengths) {
uint8_t key[128/8] = {0};
AES_KEY aes;
Expand Down
13 changes: 13 additions & 0 deletions crypto/fipsmodule/aes/aes_tests.txt
Expand Up @@ -48,3 +48,16 @@ Mode = KeyWrap
Key = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
Plaintext = 00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f
Ciphertext = 28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21


# Test vectors from https://tools.ietf.org/html/rfc5649#section-6

Mode = KeyWrapWithPadding
Key = 5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8
Plaintext = c37b7e6492584340bed12207808941155068f738
Ciphertext = 138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a

Mode = KeyWrapWithPadding
Key = 5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8
Plaintext = 466f7250617369
Ciphertext = afbeb0f07dfbf5419200f2ccb50bb24f
111 changes: 103 additions & 8 deletions crypto/fipsmodule/aes/key_wrap.c
Expand Up @@ -48,6 +48,7 @@

#include <openssl/aes.h>

#include <assert.h>
#include <limits.h>
#include <string.h>

Expand Down Expand Up @@ -100,18 +101,17 @@ int AES_wrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
return (int)in_len + 8;
}

int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
const uint8_t *in, size_t in_len) {
// aes_unwrap_key_inner performs steps one and two from
// https://tools.ietf.org/html/rfc3394#section-2.2.2
static int aes_unwrap_key_inner(const AES_KEY *key, uint8_t *out,
uint8_t out_iv[8], const uint8_t *in,
size_t in_len) {
// See RFC 3394, section 2.2.2. Additionally, note that section 2 requires the
// plaintext be at least two 8-byte blocks, so the ciphertext must be at least
// three blocks.

if (in_len > INT_MAX || in_len < 24 || in_len % 8 != 0) {
return -1;
}

if (iv == NULL) {
iv = kDefaultIV;
return 0;
}

uint8_t A[AES_BLOCK_SIZE];
Expand All @@ -133,9 +133,104 @@ int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
}
}

if (CRYPTO_memcmp(A, iv, 8) != 0) {
memcpy(out_iv, A, 8);
return 1;
}

int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
const uint8_t *in, size_t in_len) {
uint8_t calculated_iv[8];
if (!aes_unwrap_key_inner(key, out, calculated_iv, in, in_len)) {
return -1;
}

if (iv == NULL) {
iv = kDefaultIV;
}
if (CRYPTO_memcmp(calculated_iv, iv, 8) != 0) {
return -1;
}

return (int)in_len - 8;
}

// kPaddingConstant is used in Key Wrap with Padding. See
// https://tools.ietf.org/html/rfc5649#section-3
static const uint8_t kPaddingConstant[4] = {0xa6, 0x59, 0x59, 0xa6};

int AES_wrap_key_padded(const AES_KEY *key, uint8_t *out, size_t *out_len,
size_t max_out, const uint8_t *in, size_t in_len) {
// See https://tools.ietf.org/html/rfc5649#section-4.1
const uint32_t in_len32_be = CRYPTO_bswap4(in_len);
const uint64_t in_len64 = in_len;
const size_t padded_len = (in_len + 7) & ~7;

*out_len = 0;
if (in_len == 0 || in_len64 > 0xffffffffu || in_len + 7 < in_len ||
padded_len + 8 < padded_len || max_out < padded_len + 8) {
return 0;
}

uint8_t block[AES_BLOCK_SIZE];
memcpy(block, kPaddingConstant, sizeof(kPaddingConstant));
memcpy(block + 4, &in_len32_be, sizeof(in_len32_be));

if (in_len <= 8) {
memset(block + 8, 0, 8);
memcpy(block + 8, in, in_len);
AES_encrypt(block, out, key);
*out_len = AES_BLOCK_SIZE;
return 1;
}

uint8_t *padded_in = OPENSSL_malloc(padded_len);
if (padded_in == NULL) {
return 0;
}
assert(padded_len >= 8);
memset(padded_in + padded_len - 8, 0, 8);
memcpy(padded_in, in, in_len);
const int ret = AES_wrap_key(key, block, out, padded_in, padded_len);
OPENSSL_free(padded_in);
if (ret < 0) {
return 0;
}
*out_len = ret;
return 1;
}

int AES_unwrap_key_padded(const AES_KEY *key, uint8_t *out, size_t *out_len,
size_t max_out, const uint8_t *in, size_t in_len) {
*out_len = 0;
if (in_len < AES_BLOCK_SIZE || max_out < in_len - 8) {
return 0;
}

uint8_t iv[8];
if (in_len == AES_BLOCK_SIZE) {
uint8_t block[AES_BLOCK_SIZE];
AES_decrypt(in, block, key);
memcpy(iv, block, sizeof(iv));
memcpy(out, block + 8, 8);
} else if (!aes_unwrap_key_inner(key, out, iv, in, in_len)) {
return 0;
}
assert(in_len % 8 == 0);

crypto_word_t ok = constant_time_eq_int(
CRYPTO_memcmp(iv, kPaddingConstant, sizeof(kPaddingConstant)), 0);

uint32_t claimed_len32;
memcpy(&claimed_len32, iv + 4, sizeof(claimed_len32));
const size_t claimed_len = CRYPTO_bswap4(claimed_len32);
ok &= ~constant_time_is_zero_w(claimed_len);
ok &= constant_time_eq_w((claimed_len - 1) >> 3, (in_len - 9) >> 3);

// Check that padding bytes are all zero.
for (size_t i = in_len - 15; i < in_len - 8; i++) {
ok &= constant_time_is_zero_w(constant_time_ge_8(i, claimed_len) & out[i]);
}

*out_len = constant_time_select_w(ok, claimed_len, 0);
return ok & 1;
}
27 changes: 26 additions & 1 deletion include/openssl/aes.h
Expand Up @@ -6,7 +6,7 @@
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
Expand Down Expand Up @@ -163,6 +163,31 @@ OPENSSL_EXPORT int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv,
size_t in_len);


// AES key wrap with padding.
//
// These functions implement AES Key Wrap with Padding mode, as defined in RFC
// 5649. They should never be used except to interoperate with existing systems
// that use this mode.

// AES_wrap_key_padded performs a padded AES key wrap on |in| which must be
// between 1 and 2^32-1 bytes. |key| must have been configured for encryption.
// On success it writes at most |max_out| bytes of ciphertext to |out|, sets
// |*out_len| to the number of bytes written, and returns one. On failure it
// returns zero. To ensure success, set |max_out| to at least |in_len| + 15.
OPENSSL_EXPORT int AES_wrap_key_padded(const AES_KEY *key, uint8_t *out,
size_t *out_len, size_t max_out,
const uint8_t *in, size_t in_len);

// AES_unwrap_key_padded performs a padded AES key unwrap on |in| which must be
// a multiple of 8 bytes. |key| must have been configured for decryption. On
// success it writes at most |max_out| bytes to |out|, sets |*out_len| to the
// number of bytes written, and returns one. On failure it returns zero. Setting
// |max_out| to |in_len| is a sensible estimate.
OPENSSL_EXPORT int AES_unwrap_key_padded(const AES_KEY *key, uint8_t *out,
size_t *out_len, size_t max_out,
const uint8_t *in, size_t in_len);


#if defined(__cplusplus)
} // extern C
#endif
Expand Down
1 change: 1 addition & 0 deletions sources.cmake
Expand Up @@ -80,6 +80,7 @@ set(
third_party/wycheproof_testvectors/ecdsa_secp384r1_sha512_test.txt
third_party/wycheproof_testvectors/ecdsa_secp521r1_sha512_test.txt
third_party/wycheproof_testvectors/eddsa_test.txt
third_party/wycheproof_testvectors/kwp_test.txt
third_party/wycheproof_testvectors/kw_test.txt
third_party/wycheproof_testvectors/rsa_pss_2048_sha1_mgf1_20_test.txt
third_party/wycheproof_testvectors/rsa_pss_2048_sha256_mgf1_0_test.txt
Expand Down

0 comments on commit 8f574c3

Please sign in to comment.