blob: a8d2dbd8c28bf3fc2ee57724150f72823bc14e11 [file] [log] [blame]
/* Copyright (c) 2014, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <vector>
#include <gtest/gtest.h>
#include <openssl/aead.h>
#include <openssl/cipher.h>
#include <openssl/err.h>
#include "../fipsmodule/cipher/internal.h"
#include "internal.h"
#include "../internal.h"
#include "../test/abi_test.h"
#include "../test/file_test.h"
#include "../test/test_util.h"
#include "../test/wycheproof_util.h"
// kLimitedImplementation indicates that tests that assume a generic AEAD
// interface should not be performed. For example, the key-wrap AEADs only
// handle inputs that are a multiple of eight bytes in length and the TLS CBC
// AEADs have the concept of “direction”.
constexpr uint32_t kLimitedImplementation = 1 << 0;
// kCanTruncateTags indicates that the AEAD supports truncatating tags to
// arbitrary lengths.
constexpr uint32_t kCanTruncateTags = 1 << 1;
// kVariableNonce indicates that the AEAD supports a variable-length nonce.
constexpr uint32_t kVariableNonce = 1 << 2;
// kNondeterministic indicates that the AEAD performs randomised encryption thus
// one cannot assume that encrypting the same data will result in the same
// ciphertext.
constexpr uint32_t kNondeterministic = 1 << 7;
// RequiresADLength encodes an AD length requirement into flags.
constexpr uint32_t RequiresADLength(size_t length) {
assert(length < 16);
return static_cast<uint32_t>((length & 0xf) << 3);
}
// RequiredADLength returns the AD length requirement encoded in |flags|, or
// zero if there isn't one.
constexpr size_t RequiredADLength(uint32_t flags) {
return (flags >> 3) & 0xf;
}
constexpr uint32_t RequiresMinimumTagLength(size_t length) {
assert(length < 16);
return static_cast<uint32_t>((length & 0xf) << 8);
}
constexpr size_t MinimumTagLength(uint32_t flags) {
return ((flags >> 8) & 0xf) == 0 ? 1 : ((flags >> 8) & 0xf);
}
struct KnownAEAD {
const char name[40];
const EVP_AEAD *(*func)(void);
const char *test_vectors;
uint32_t flags;
};
static const struct KnownAEAD kAEADs[] = {
{"AES_128_GCM", EVP_aead_aes_128_gcm, "aes_128_gcm_tests.txt",
kCanTruncateTags | kVariableNonce},
{"AES_128_GCM_NIST", EVP_aead_aes_128_gcm, "nist_cavp/aes_128_gcm.txt",
kCanTruncateTags | kVariableNonce},
{"AES_192_GCM", EVP_aead_aes_192_gcm, "aes_192_gcm_tests.txt",
kCanTruncateTags | kVariableNonce},
{"AES_256_GCM", EVP_aead_aes_256_gcm, "aes_256_gcm_tests.txt",
kCanTruncateTags | kVariableNonce},
{"AES_256_GCM_NIST", EVP_aead_aes_256_gcm, "nist_cavp/aes_256_gcm.txt",
kCanTruncateTags | kVariableNonce},
{"AES_128_GCM_SIV", EVP_aead_aes_128_gcm_siv, "aes_128_gcm_siv_tests.txt",
0},
{"AES_256_GCM_SIV", EVP_aead_aes_256_gcm_siv, "aes_256_gcm_siv_tests.txt",
0},
{"AES_128_GCM_RandomNonce", EVP_aead_aes_128_gcm_randnonce,
"aes_128_gcm_randnonce_tests.txt",
kNondeterministic | kCanTruncateTags | RequiresMinimumTagLength(13)},
{"AES_256_GCM_RandomNonce", EVP_aead_aes_256_gcm_randnonce,
"aes_256_gcm_randnonce_tests.txt",
kNondeterministic | kCanTruncateTags | RequiresMinimumTagLength(13)},
{"ChaCha20Poly1305", EVP_aead_chacha20_poly1305,
"chacha20_poly1305_tests.txt", kCanTruncateTags},
{"XChaCha20Poly1305", EVP_aead_xchacha20_poly1305,
"xchacha20_poly1305_tests.txt", kCanTruncateTags},
{"AES_128_CBC_SHA1_TLS", EVP_aead_aes_128_cbc_sha1_tls,
"aes_128_cbc_sha1_tls_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"AES_128_CBC_SHA1_TLSImplicitIV",
EVP_aead_aes_128_cbc_sha1_tls_implicit_iv,
"aes_128_cbc_sha1_tls_implicit_iv_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"AES_256_CBC_SHA1_TLS", EVP_aead_aes_256_cbc_sha1_tls,
"aes_256_cbc_sha1_tls_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"AES_256_CBC_SHA1_TLSImplicitIV",
EVP_aead_aes_256_cbc_sha1_tls_implicit_iv,
"aes_256_cbc_sha1_tls_implicit_iv_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"DES_EDE3_CBC_SHA1_TLS", EVP_aead_des_ede3_cbc_sha1_tls,
"des_ede3_cbc_sha1_tls_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"DES_EDE3_CBC_SHA1_TLSImplicitIV",
EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv,
"des_ede3_cbc_sha1_tls_implicit_iv_tests.txt",
kLimitedImplementation | RequiresADLength(11)},
{"AES_128_CTR_HMAC_SHA256", EVP_aead_aes_128_ctr_hmac_sha256,
"aes_128_ctr_hmac_sha256.txt", kCanTruncateTags},
{"AES_256_CTR_HMAC_SHA256", EVP_aead_aes_256_ctr_hmac_sha256,
"aes_256_ctr_hmac_sha256.txt", kCanTruncateTags},
{"AES_128_CCM_BLUETOOTH", EVP_aead_aes_128_ccm_bluetooth,
"aes_128_ccm_bluetooth_tests.txt", 0},
{"AES_128_CCM_BLUETOOTH_8", EVP_aead_aes_128_ccm_bluetooth_8,
"aes_128_ccm_bluetooth_8_tests.txt", 0},
{"AES_128_CCM_Matter", EVP_aead_aes_128_ccm_matter,
"aes_128_ccm_matter_tests.txt", 0},
};
class PerAEADTest : public testing::TestWithParam<KnownAEAD> {
public:
const EVP_AEAD *aead() { return GetParam().func(); }
};
INSTANTIATE_TEST_SUITE_P(All, PerAEADTest, testing::ValuesIn(kAEADs),
[](const testing::TestParamInfo<KnownAEAD> &params)
-> std::string { return params.param.name; });
// Tests an AEAD against a series of test vectors from a file, using the
// FileTest format. As an example, here's a valid test case:
//
// KEY: 5a19f3173586b4c42f8412f4d5a786531b3231753e9e00998aec12fda8df10e4
// NONCE: 978105dfce667bf4
// IN: 6a4583908d
// AD: b654574932
// CT: 5294265a60
// TAG: 1d45758621762e061368e68868e2f929
TEST_P(PerAEADTest, TestVector) {
std::string test_vectors = "crypto/cipher_extra/test/";
test_vectors += GetParam().test_vectors;
FileTestGTest(test_vectors.c_str(), [&](FileTest *t) {
std::vector<uint8_t> key, nonce, in, ad, ct, tag;
ASSERT_TRUE(t->GetBytes(&key, "KEY"));
ASSERT_TRUE(t->GetBytes(&nonce, "NONCE"));
ASSERT_TRUE(t->GetBytes(&in, "IN"));
ASSERT_TRUE(t->GetBytes(&ad, "AD"));
ASSERT_TRUE(t->GetBytes(&ct, "CT"));
ASSERT_TRUE(t->GetBytes(&tag, "TAG"));
size_t tag_len = tag.size();
if (t->HasAttribute("TAG_LEN")) {
// Legacy AEADs are MAC-then-encrypt and may include padding in the TAG
// field. TAG_LEN contains the actual size of the digest in that case.
std::string tag_len_str;
ASSERT_TRUE(t->GetAttribute(&tag_len_str, "TAG_LEN"));
tag_len = strtoul(tag_len_str.c_str(), nullptr, 10);
ASSERT_TRUE(tag_len);
}
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_seal));
std::vector<uint8_t> out(in.size() + EVP_AEAD_max_overhead(aead()));
if (!t->HasAttribute("NO_SEAL") &&
!(GetParam().flags & kNondeterministic)) {
size_t out_len;
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
nonce.data(), nonce.size(), in.data(),
in.size(), ad.data(), ad.size()));
out.resize(out_len);
ASSERT_EQ(out.size(), ct.size() + tag.size());
EXPECT_EQ(Bytes(ct), Bytes(out.data(), ct.size()));
EXPECT_EQ(Bytes(tag), Bytes(out.data() + ct.size(), tag.size()));
} else {
out.resize(ct.size() + tag.size());
OPENSSL_memcpy(out.data(), ct.data(), ct.size());
OPENSSL_memcpy(out.data() + ct.size(), tag.data(), tag.size());
}
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
std::vector<uint8_t> out2(out.size());
size_t out2_len;
int ret = EVP_AEAD_CTX_open(ctx.get(), out2.data(), &out2_len, out2.size(),
nonce.data(), nonce.size(), out.data(),
out.size(), ad.data(), ad.size());
if (t->HasAttribute("FAILS")) {
ASSERT_FALSE(ret) << "Decrypted bad data.";
ERR_clear_error();
return;
}
ASSERT_TRUE(ret) << "Failed to decrypt.";
out2.resize(out2_len);
EXPECT_EQ(Bytes(in), Bytes(out2));
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
// Garbage at the end isn't ignored.
out.push_back(0);
out2.resize(out.size());
EXPECT_FALSE(EVP_AEAD_CTX_open(
ctx.get(), out2.data(), &out2_len, out2.size(), nonce.data(),
nonce.size(), out.data(), out.size(), ad.data(), ad.size()))
<< "Decrypted bad data with trailing garbage.";
ERR_clear_error();
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
// Verify integrity is checked.
out[0] ^= 0x80;
out.resize(out.size() - 1);
out2.resize(out.size());
EXPECT_FALSE(EVP_AEAD_CTX_open(
ctx.get(), out2.data(), &out2_len, out2.size(), nonce.data(),
nonce.size(), out.data(), out.size(), ad.data(), ad.size()))
<< "Decrypted bad data with corrupted byte.";
ERR_clear_error();
});
}
TEST_P(PerAEADTest, TestExtraInput) {
const KnownAEAD &aead_config = GetParam();
if (!aead()->seal_scatter_supports_extra_in) {
return;
}
const std::string test_vectors =
"crypto/cipher_extra/test/" + std::string(aead_config.test_vectors);
FileTestGTest(test_vectors.c_str(), [&](FileTest *t) {
if (t->HasAttribute("NO_SEAL") ||
t->HasAttribute("FAILS") ||
(aead_config.flags & kNondeterministic)) {
t->SkipCurrent();
return;
}
std::vector<uint8_t> key, nonce, in, ad, ct, tag;
ASSERT_TRUE(t->GetBytes(&key, "KEY"));
ASSERT_TRUE(t->GetBytes(&nonce, "NONCE"));
ASSERT_TRUE(t->GetBytes(&in, "IN"));
ASSERT_TRUE(t->GetBytes(&ad, "AD"));
ASSERT_TRUE(t->GetBytes(&ct, "CT"));
ASSERT_TRUE(t->GetBytes(&tag, "TAG"));
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), aead(), key.data(), key.size(),
tag.size(), nullptr));
std::vector<uint8_t> out_tag(EVP_AEAD_max_overhead(aead()) + in.size());
std::vector<uint8_t> out(in.size());
for (size_t extra_in_size = 0; extra_in_size < in.size(); extra_in_size++) {
size_t tag_bytes_written;
SCOPED_TRACE(extra_in_size);
ASSERT_TRUE(EVP_AEAD_CTX_seal_scatter(
ctx.get(), out.data(), out_tag.data(), &tag_bytes_written,
out_tag.size(), nonce.data(), nonce.size(), in.data(),
in.size() - extra_in_size, in.data() + in.size() - extra_in_size,
extra_in_size, ad.data(), ad.size()));
ASSERT_EQ(tag_bytes_written, extra_in_size + tag.size());
memcpy(out.data() + in.size() - extra_in_size, out_tag.data(),
extra_in_size);
EXPECT_EQ(Bytes(ct), Bytes(out.data(), in.size()));
EXPECT_EQ(Bytes(tag), Bytes(out_tag.data() + extra_in_size,
tag_bytes_written - extra_in_size));
}
});
}
TEST_P(PerAEADTest, TestVectorScatterGather) {
std::string test_vectors = "crypto/cipher_extra/test/";
const KnownAEAD &aead_config = GetParam();
test_vectors += aead_config.test_vectors;
FileTestGTest(test_vectors.c_str(), [&](FileTest *t) {
std::vector<uint8_t> key, nonce, in, ad, ct, tag;
ASSERT_TRUE(t->GetBytes(&key, "KEY"));
ASSERT_TRUE(t->GetBytes(&nonce, "NONCE"));
ASSERT_TRUE(t->GetBytes(&in, "IN"));
ASSERT_TRUE(t->GetBytes(&ad, "AD"));
ASSERT_TRUE(t->GetBytes(&ct, "CT"));
ASSERT_TRUE(t->GetBytes(&tag, "TAG"));
size_t tag_len = tag.size();
if (t->HasAttribute("TAG_LEN")) {
// Legacy AEADs are MAC-then-encrypt and may include padding in the TAG
// field. TAG_LEN contains the actual size of the digest in that case.
std::string tag_len_str;
ASSERT_TRUE(t->GetAttribute(&tag_len_str, "TAG_LEN"));
tag_len = strtoul(tag_len_str.c_str(), nullptr, 10);
ASSERT_TRUE(tag_len);
}
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_seal));
std::vector<uint8_t> out(in.size());
std::vector<uint8_t> out_tag(EVP_AEAD_max_overhead(aead()));
if (!t->HasAttribute("NO_SEAL") &&
!(aead_config.flags & kNondeterministic)) {
size_t out_tag_len;
ASSERT_TRUE(EVP_AEAD_CTX_seal_scatter(
ctx.get(), out.data(), out_tag.data(), &out_tag_len, out_tag.size(),
nonce.data(), nonce.size(), in.data(), in.size(), nullptr, 0,
ad.data(), ad.size()));
out_tag.resize(out_tag_len);
ASSERT_EQ(out.size(), ct.size());
ASSERT_EQ(out_tag.size(), tag.size());
EXPECT_EQ(Bytes(ct), Bytes(out.data(), ct.size()));
EXPECT_EQ(Bytes(tag), Bytes(out_tag.data(), tag.size()));
} else {
out.resize(ct.size());
out_tag.resize(tag.size());
OPENSSL_memcpy(out.data(), ct.data(), ct.size());
OPENSSL_memcpy(out_tag.data(), tag.data(), tag.size());
}
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
std::vector<uint8_t> out2(out.size());
int ret = EVP_AEAD_CTX_open_gather(
ctx.get(), out2.data(), nonce.data(), nonce.size(), out.data(),
out.size(), out_tag.data(), out_tag.size(), ad.data(), ad.size());
// Skip decryption for AEADs that don't implement open_gather().
if (!ret) {
int err = ERR_peek_error();
if (ERR_GET_LIB(err) == ERR_LIB_CIPHER &&
ERR_GET_REASON(err) == CIPHER_R_CTRL_NOT_IMPLEMENTED) {
t->SkipCurrent();
return;
}
}
if (t->HasAttribute("FAILS")) {
ASSERT_FALSE(ret) << "Decrypted bad data";
ERR_clear_error();
return;
}
ASSERT_TRUE(ret) << "Failed to decrypt: "
<< ERR_reason_error_string(ERR_get_error());
EXPECT_EQ(Bytes(in), Bytes(out2));
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
// Garbage at the end isn't ignored.
out_tag.push_back(0);
out2.resize(out.size());
EXPECT_FALSE(EVP_AEAD_CTX_open_gather(
ctx.get(), out2.data(), nonce.data(), nonce.size(), out.data(),
out.size(), out_tag.data(), out_tag.size(), ad.data(), ad.size()))
<< "Decrypted bad data with trailing garbage.";
ERR_clear_error();
// The "stateful" AEADs for implementing pre-AEAD cipher suites need to be
// reset after each operation.
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
// Verify integrity is checked.
out_tag[0] ^= 0x80;
out_tag.resize(out_tag.size() - 1);
out2.resize(out.size());
EXPECT_FALSE(EVP_AEAD_CTX_open_gather(
ctx.get(), out2.data(), nonce.data(), nonce.size(), out.data(),
out.size(), out_tag.data(), out_tag.size(), ad.data(), ad.size()))
<< "Decrypted bad data with corrupted byte.";
ERR_clear_error();
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_open));
// Check edge case for tag length.
EXPECT_FALSE(EVP_AEAD_CTX_open_gather(
ctx.get(), out2.data(), nonce.data(), nonce.size(), out.data(),
out.size(), out_tag.data(), 0, ad.data(), ad.size()))
<< "Decrypted bad data with corrupted byte.";
ERR_clear_error();
});
}
TEST_P(PerAEADTest, CleanupAfterInitFailure) {
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
OPENSSL_memset(key, 0, sizeof(key));
const size_t key_len = EVP_AEAD_key_length(aead());
ASSERT_GE(sizeof(key), key_len);
EVP_AEAD_CTX ctx;
ASSERT_FALSE(EVP_AEAD_CTX_init(
&ctx, aead(), key, key_len,
9999 /* a silly tag length to trigger an error */, NULL /* ENGINE */));
ERR_clear_error();
// Running a second, failed _init should not cause a memory leak.
ASSERT_FALSE(EVP_AEAD_CTX_init(
&ctx, aead(), key, key_len,
9999 /* a silly tag length to trigger an error */, NULL /* ENGINE */));
ERR_clear_error();
// Calling _cleanup on an |EVP_AEAD_CTX| after a failed _init should be a
// no-op.
EVP_AEAD_CTX_cleanup(&ctx);
}
TEST_P(PerAEADTest, TruncatedTags) {
if (!(GetParam().flags & kCanTruncateTags)) {
return;
}
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
OPENSSL_memset(key, 0, sizeof(key));
const size_t key_len = EVP_AEAD_key_length(aead());
ASSERT_GE(sizeof(key), key_len);
uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
OPENSSL_memset(nonce, 0, sizeof(nonce));
const size_t nonce_len = EVP_AEAD_nonce_length(aead());
ASSERT_GE(sizeof(nonce), nonce_len);
const size_t tag_len = MinimumTagLength(GetParam().flags);
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), aead(), key, key_len,
tag_len, NULL /* ENGINE */));
const uint8_t plaintext[1] = {'A'};
uint8_t ciphertext[128];
size_t ciphertext_len;
constexpr uint8_t kSentinel = 42;
OPENSSL_memset(ciphertext, kSentinel, sizeof(ciphertext));
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), ciphertext, &ciphertext_len,
sizeof(ciphertext), nonce, nonce_len, plaintext,
sizeof(plaintext), nullptr /* ad */, 0));
for (size_t i = ciphertext_len; i < sizeof(ciphertext); i++) {
// Sealing must not write past where it said it did.
EXPECT_EQ(kSentinel, ciphertext[i])
<< "Sealing wrote off the end of the buffer.";
}
const size_t overhead_used = ciphertext_len - sizeof(plaintext);
const size_t expected_overhead =
tag_len + EVP_AEAD_max_overhead(aead()) - EVP_AEAD_max_tag_len(aead());
EXPECT_EQ(overhead_used, expected_overhead)
<< "AEAD is probably ignoring request to truncate tags.";
uint8_t plaintext2[sizeof(plaintext) + 16];
OPENSSL_memset(plaintext2, kSentinel, sizeof(plaintext2));
size_t plaintext2_len;
ASSERT_TRUE(EVP_AEAD_CTX_open(
ctx.get(), plaintext2, &plaintext2_len, sizeof(plaintext2), nonce,
nonce_len, ciphertext, ciphertext_len, nullptr /* ad */, 0))
<< "Opening with truncated tag didn't work.";
for (size_t i = plaintext2_len; i < sizeof(plaintext2); i++) {
// Likewise, opening should also stay within bounds.
EXPECT_EQ(kSentinel, plaintext2[i])
<< "Opening wrote off the end of the buffer.";
}
EXPECT_EQ(Bytes(plaintext), Bytes(plaintext2, plaintext2_len));
}
TEST_P(PerAEADTest, AliasedBuffers) {
if (GetParam().flags & kLimitedImplementation) {
return;
}
const size_t key_len = EVP_AEAD_key_length(aead());
const size_t nonce_len = EVP_AEAD_nonce_length(aead());
const size_t max_overhead = EVP_AEAD_max_overhead(aead());
std::vector<uint8_t> key(key_len, 'a');
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), aead(), key.data(), key_len,
EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr));
static const uint8_t kPlaintext[260] =
"testing123456testing123456testing123456testing123456testing123456testing"
"123456testing123456testing123456testing123456testing123456testing123456t"
"esting123456testing123456testing123456testing123456testing123456testing1"
"23456testing123456testing123456testing12345";
const std::vector<size_t> offsets = {
0, 1, 2, 8, 15, 16, 17, 31, 32, 33, 63,
64, 65, 95, 96, 97, 127, 128, 129, 255, 256, 257,
};
std::vector<uint8_t> nonce(nonce_len, 'b');
std::vector<uint8_t> valid_encryption(sizeof(kPlaintext) + max_overhead);
size_t valid_encryption_len;
ASSERT_TRUE(EVP_AEAD_CTX_seal(
ctx.get(), valid_encryption.data(), &valid_encryption_len,
sizeof(kPlaintext) + max_overhead, nonce.data(), nonce_len, kPlaintext,
sizeof(kPlaintext), nullptr, 0))
<< "EVP_AEAD_CTX_seal failed with disjoint buffers.";
// Test with out != in which we expect to fail.
std::vector<uint8_t> buffer(2 + valid_encryption_len);
uint8_t *in = buffer.data() + 1;
uint8_t *out1 = buffer.data();
uint8_t *out2 = buffer.data() + 2;
OPENSSL_memcpy(in, kPlaintext, sizeof(kPlaintext));
size_t out_len;
EXPECT_FALSE(EVP_AEAD_CTX_seal(
ctx.get(), out1 /* in - 1 */, &out_len, sizeof(kPlaintext) + max_overhead,
nonce.data(), nonce_len, in, sizeof(kPlaintext), nullptr, 0));
EXPECT_FALSE(EVP_AEAD_CTX_seal(
ctx.get(), out2 /* in + 1 */, &out_len, sizeof(kPlaintext) + max_overhead,
nonce.data(), nonce_len, in, sizeof(kPlaintext), nullptr, 0));
ERR_clear_error();
OPENSSL_memcpy(in, valid_encryption.data(), valid_encryption_len);
EXPECT_FALSE(EVP_AEAD_CTX_open(ctx.get(), out1 /* in - 1 */, &out_len,
valid_encryption_len, nonce.data(), nonce_len,
in, valid_encryption_len, nullptr, 0));
EXPECT_FALSE(EVP_AEAD_CTX_open(ctx.get(), out2 /* in + 1 */, &out_len,
valid_encryption_len, nonce.data(), nonce_len,
in, valid_encryption_len, nullptr, 0));
ERR_clear_error();
// Test with out == in, which we expect to work.
OPENSSL_memcpy(in, kPlaintext, sizeof(kPlaintext));
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), in, &out_len,
sizeof(kPlaintext) + max_overhead, nonce.data(),
nonce_len, in, sizeof(kPlaintext), nullptr, 0));
if (!(GetParam().flags & kNondeterministic)) {
EXPECT_EQ(Bytes(valid_encryption.data(), valid_encryption_len),
Bytes(in, out_len));
}
OPENSSL_memcpy(in, valid_encryption.data(), valid_encryption_len);
ASSERT_TRUE(EVP_AEAD_CTX_open(ctx.get(), in, &out_len, valid_encryption_len,
nonce.data(), nonce_len, in,
valid_encryption_len, nullptr, 0));
EXPECT_EQ(Bytes(kPlaintext), Bytes(in, out_len));
}
TEST_P(PerAEADTest, UnalignedInput) {
alignas(16) uint8_t key[EVP_AEAD_MAX_KEY_LENGTH + 1];
alignas(16) uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH + 1];
alignas(16) uint8_t plaintext[32 + 1];
alignas(16) uint8_t ad[32 + 1];
OPENSSL_memset(key, 'K', sizeof(key));
OPENSSL_memset(nonce, 'N', sizeof(nonce));
OPENSSL_memset(plaintext, 'P', sizeof(plaintext));
OPENSSL_memset(ad, 'A', sizeof(ad));
const size_t key_len = EVP_AEAD_key_length(aead());
ASSERT_GE(sizeof(key) - 1, key_len);
const size_t nonce_len = EVP_AEAD_nonce_length(aead());
ASSERT_GE(sizeof(nonce) - 1, nonce_len);
const size_t ad_len = RequiredADLength(GetParam().flags) != 0
? RequiredADLength(GetParam().flags)
: sizeof(ad) - 1;
ASSERT_GE(sizeof(ad) - 1, ad_len);
// Encrypt some input.
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key + 1, key_len, EVP_AEAD_DEFAULT_TAG_LENGTH,
evp_aead_seal));
alignas(16) uint8_t ciphertext[sizeof(plaintext) + EVP_AEAD_MAX_OVERHEAD];
size_t ciphertext_len;
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), ciphertext + 1, &ciphertext_len,
sizeof(ciphertext) - 1, nonce + 1, nonce_len,
plaintext + 1, sizeof(plaintext) - 1, ad + 1,
ad_len));
// It must successfully decrypt.
alignas(16) uint8_t out[sizeof(ciphertext)];
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), key + 1, key_len, EVP_AEAD_DEFAULT_TAG_LENGTH,
evp_aead_open));
size_t out_len;
ASSERT_TRUE(EVP_AEAD_CTX_open(ctx.get(), out + 1, &out_len, sizeof(out) - 1,
nonce + 1, nonce_len, ciphertext + 1,
ciphertext_len, ad + 1, ad_len));
EXPECT_EQ(Bytes(plaintext + 1, sizeof(plaintext) - 1),
Bytes(out + 1, out_len));
}
TEST_P(PerAEADTest, Overflow) {
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
OPENSSL_memset(key, 'K', sizeof(key));
bssl::ScopedEVP_AEAD_CTX ctx;
const size_t max_tag_len = EVP_AEAD_max_tag_len(aead());
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(ctx.get(), aead(), key,
EVP_AEAD_key_length(aead()),
max_tag_len, evp_aead_seal));
uint8_t plaintext[1] = {0};
uint8_t ciphertext[1024] = {0};
size_t ciphertext_len;
// The AEAD must not overflow when calculating the ciphertext length.
ASSERT_FALSE(EVP_AEAD_CTX_seal(
ctx.get(), ciphertext, &ciphertext_len, sizeof(ciphertext), nullptr, 0,
plaintext, std::numeric_limits<size_t>::max() - max_tag_len + 1, nullptr,
0));
ERR_clear_error();
// (Can't test the scatter interface because it'll attempt to zero the output
// buffer on error and the primary output buffer is implicitly the same size
// as the input.)
}
TEST_P(PerAEADTest, InvalidNonceLength) {
size_t valid_nonce_len = EVP_AEAD_nonce_length(aead());
std::vector<size_t> nonce_lens;
if (valid_nonce_len != 0) {
// Other than the implicit IV TLS "AEAD"s, none of our AEADs allow empty
// nonces. In particular, although AES-GCM was incorrectly specified with
// variable-length nonces, it does not allow the empty nonce.
nonce_lens.push_back(0);
}
if (!(GetParam().flags & kVariableNonce)) {
nonce_lens.push_back(valid_nonce_len + 1);
if (valid_nonce_len != 0) {
nonce_lens.push_back(valid_nonce_len - 1);
}
}
static const uint8_t kZeros[EVP_AEAD_MAX_KEY_LENGTH] = {0};
const size_t ad_len = RequiredADLength(GetParam().flags) != 0
? RequiredADLength(GetParam().flags)
: 16;
ASSERT_LE(ad_len, sizeof(kZeros));
for (size_t nonce_len : nonce_lens) {
SCOPED_TRACE(nonce_len);
uint8_t buf[256];
size_t len;
std::vector<uint8_t> nonce(nonce_len);
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), kZeros, EVP_AEAD_key_length(aead()),
EVP_AEAD_DEFAULT_TAG_LENGTH, evp_aead_seal));
EXPECT_FALSE(EVP_AEAD_CTX_seal(ctx.get(), buf, &len, sizeof(buf),
nonce.data(), nonce.size(), nullptr /* in */,
0, kZeros /* ad */, ad_len));
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_CIPHER, ERR_GET_LIB(err));
// TODO(davidben): Merge these errors. https://crbug.com/boringssl/129.
if (ERR_GET_REASON(err) != CIPHER_R_UNSUPPORTED_NONCE_SIZE) {
EXPECT_EQ(CIPHER_R_INVALID_NONCE_SIZE, ERR_GET_REASON(err));
}
ctx.Reset();
ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
ctx.get(), aead(), kZeros, EVP_AEAD_key_length(aead()),
EVP_AEAD_DEFAULT_TAG_LENGTH, evp_aead_open));
EXPECT_FALSE(EVP_AEAD_CTX_open(ctx.get(), buf, &len, sizeof(buf),
nonce.data(), nonce.size(), kZeros /* in */,
sizeof(kZeros), kZeros /* ad */, ad_len));
err = ERR_get_error();
EXPECT_EQ(ERR_LIB_CIPHER, ERR_GET_LIB(err));
if (ERR_GET_REASON(err) != CIPHER_R_UNSUPPORTED_NONCE_SIZE) {
EXPECT_EQ(CIPHER_R_INVALID_NONCE_SIZE, ERR_GET_REASON(err));
}
}
}
#if defined(SUPPORTS_ABI_TEST)
// CHECK_ABI can't pass enums, i.e. |evp_aead_seal| and |evp_aead_open|. Thus
// these two wrappers.
static int aead_ctx_init_for_seal(EVP_AEAD_CTX *ctx, const EVP_AEAD *aead,
const uint8_t *key, size_t key_len) {
return EVP_AEAD_CTX_init_with_direction(ctx, aead, key, key_len, 0,
evp_aead_seal);
}
static int aead_ctx_init_for_open(EVP_AEAD_CTX *ctx, const EVP_AEAD *aead,
const uint8_t *key, size_t key_len) {
return EVP_AEAD_CTX_init_with_direction(ctx, aead, key, key_len, 0,
evp_aead_open);
}
// CHECK_ABI can pass, at most, eight arguments. Thus these wrappers that
// figure out the output length from the input length, and take the nonce length
// from the configuration of the AEAD.
static int aead_ctx_seal(EVP_AEAD_CTX *ctx, uint8_t *out_ciphertext,
size_t *out_ciphertext_len, const uint8_t *nonce,
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *ad, size_t ad_len) {
const size_t nonce_len = EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(ctx));
return EVP_AEAD_CTX_seal(ctx, out_ciphertext, out_ciphertext_len,
plaintext_len + EVP_AEAD_MAX_OVERHEAD, nonce,
nonce_len, plaintext, plaintext_len, ad, ad_len);
}
static int aead_ctx_open(EVP_AEAD_CTX *ctx, uint8_t *out_plaintext,
size_t *out_plaintext_len, const uint8_t *nonce,
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *ad, size_t ad_len) {
const size_t nonce_len = EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(ctx));
return EVP_AEAD_CTX_open(ctx, out_plaintext, out_plaintext_len,
ciphertext_len, nonce, nonce_len, ciphertext,
ciphertext_len, ad, ad_len);
}
TEST_P(PerAEADTest, ABI) {
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
OPENSSL_memset(key, 'K', sizeof(key));
const size_t key_len = EVP_AEAD_key_length(aead());
ASSERT_LE(key_len, sizeof(key));
bssl::ScopedEVP_AEAD_CTX ctx_seal;
ASSERT_TRUE(
CHECK_ABI(aead_ctx_init_for_seal, ctx_seal.get(), aead(), key, key_len));
bssl::ScopedEVP_AEAD_CTX ctx_open;
ASSERT_TRUE(
CHECK_ABI(aead_ctx_init_for_open, ctx_open.get(), aead(), key, key_len));
alignas(2) uint8_t plaintext[512];
OPENSSL_memset(plaintext, 'P', sizeof(plaintext));
alignas(2) uint8_t ad_buf[512];
OPENSSL_memset(ad_buf, 'A', sizeof(ad_buf));
const uint8_t *const ad = ad_buf + 1;
ASSERT_LE(RequiredADLength(GetParam().flags), sizeof(ad_buf) - 1);
const size_t ad_len = RequiredADLength(GetParam().flags) != 0
? RequiredADLength(GetParam().flags)
: sizeof(ad_buf) - 1;
uint8_t nonce[EVP_AEAD_MAX_NONCE_LENGTH];
OPENSSL_memset(nonce, 'N', sizeof(nonce));
const size_t nonce_len = EVP_AEAD_nonce_length(aead());
ASSERT_LE(nonce_len, sizeof(nonce));
alignas(2) uint8_t ciphertext[sizeof(plaintext) + EVP_AEAD_MAX_OVERHEAD + 1];
size_t ciphertext_len;
// Knock plaintext, ciphertext, and AD off alignment and give odd lengths for
// plaintext and AD. This hopefully triggers any edge-cases in the assembly.
ASSERT_TRUE(CHECK_ABI(aead_ctx_seal, ctx_seal.get(), ciphertext + 1,
&ciphertext_len, nonce, plaintext + 1,
sizeof(plaintext) - 1, ad, ad_len));
alignas(2) uint8_t plaintext2[sizeof(ciphertext) + 1];
size_t plaintext2_len;
ASSERT_TRUE(CHECK_ABI(aead_ctx_open, ctx_open.get(), plaintext2 + 1,
&plaintext2_len, nonce, ciphertext + 1, ciphertext_len,
ad, ad_len));
EXPECT_EQ(Bytes(plaintext + 1, sizeof(plaintext) - 1),
Bytes(plaintext2 + 1, plaintext2_len));
}
TEST(ChaChaPoly1305Test, ABI) {
if (!chacha20_poly1305_asm_capable()) {
return;
}
std::unique_ptr<uint8_t[]> buf(new uint8_t[1024]);
for (size_t len = 0; len <= 1024; len += 5) {
SCOPED_TRACE(len);
union chacha20_poly1305_open_data open_ctx = {};
CHECK_ABI(chacha20_poly1305_open, buf.get(), buf.get(), len, buf.get(),
len % 128, &open_ctx);
}
for (size_t len = 0; len <= 1024; len += 5) {
SCOPED_TRACE(len);
union chacha20_poly1305_seal_data seal_ctx = {};
CHECK_ABI(chacha20_poly1305_seal, buf.get(), buf.get(), len, buf.get(),
len % 128, &seal_ctx);
}
}
#endif // SUPPORTS_ABI_TEST
TEST(AEADTest, AESCCMLargeAD) {
static const std::vector<uint8_t> kKey(16, 'A');
static const std::vector<uint8_t> kNonce(13, 'N');
static const std::vector<uint8_t> kAD(65536, 'D');
static const std::vector<uint8_t> kPlaintext = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
static const std::vector<uint8_t> kCiphertext = {
0xa2, 0x12, 0x3f, 0x0b, 0x07, 0xd5, 0x02, 0xff,
0xa9, 0xcd, 0xa0, 0xf3, 0x69, 0x1c, 0x49, 0x0c};
static const std::vector<uint8_t> kTag = {0x4a, 0x31, 0x82, 0x96};
// Test AES-128-CCM-Bluetooth.
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), EVP_aead_aes_128_ccm_bluetooth(),
kKey.data(), kKey.size(),
EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr));
std::vector<uint8_t> out(kCiphertext.size() + kTag.size());
size_t out_len;
EXPECT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
kNonce.data(), kNonce.size(), kPlaintext.data(),
kPlaintext.size(), kAD.data(), kAD.size()));
ASSERT_EQ(out_len, kCiphertext.size() + kTag.size());
EXPECT_EQ(Bytes(kCiphertext), Bytes(out.data(), kCiphertext.size()));
EXPECT_EQ(Bytes(kTag), Bytes(out.data() + kCiphertext.size(), kTag.size()));
EXPECT_TRUE(EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
kNonce.data(), kNonce.size(), out.data(),
out.size(), kAD.data(), kAD.size()));
ASSERT_EQ(out_len, kPlaintext.size());
EXPECT_EQ(Bytes(kPlaintext), Bytes(out.data(), kPlaintext.size()));
}
static void RunWycheproofTestCase(FileTest *t, const EVP_AEAD *aead) {
t->IgnoreInstruction("ivSize");
std::vector<uint8_t> aad, ct, iv, key, msg, tag;
ASSERT_TRUE(t->GetBytes(&aad, "aad"));
ASSERT_TRUE(t->GetBytes(&ct, "ct"));
ASSERT_TRUE(t->GetBytes(&iv, "iv"));
ASSERT_TRUE(t->GetBytes(&key, "key"));
ASSERT_TRUE(t->GetBytes(&msg, "msg"));
ASSERT_TRUE(t->GetBytes(&tag, "tag"));
std::string tag_size_str;
ASSERT_TRUE(t->GetInstruction(&tag_size_str, "tagSize"));
size_t tag_size = static_cast<size_t>(atoi(tag_size_str.c_str()));
ASSERT_EQ(0u, tag_size % 8);
tag_size /= 8;
WycheproofResult result;
ASSERT_TRUE(GetWycheproofResult(t, &result));
std::vector<uint8_t> ct_and_tag = ct;
ct_and_tag.insert(ct_and_tag.end(), tag.begin(), tag.end());
bssl::ScopedEVP_AEAD_CTX ctx;
ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), aead, key.data(), key.size(),
tag_size, nullptr));
std::vector<uint8_t> out(msg.size());
size_t out_len;
// Wycheproof tags small AES-GCM IVs as "acceptable" and otherwise does not
// use it in AEADs. Any AES-GCM IV that isn't 96 bits is absurd, but our API
// supports those, so we treat SmallIv tests as valid.
if (result.IsValid({"SmallIv"})) {
// Decryption should succeed.
ASSERT_TRUE(EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), ct_and_tag.data(),
ct_and_tag.size(), aad.data(), aad.size()));
EXPECT_EQ(Bytes(msg), Bytes(out.data(), out_len));
// Decryption in-place should succeed.
out = ct_and_tag;
ASSERT_TRUE(EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), out.data(), out.size(),
aad.data(), aad.size()));
EXPECT_EQ(Bytes(msg), Bytes(out.data(), out_len));
// AEADs are deterministic, so encryption should produce the same result.
out.resize(ct_and_tag.size());
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), msg.data(), msg.size(),
aad.data(), aad.size()));
EXPECT_EQ(Bytes(ct_and_tag), Bytes(out.data(), out_len));
// Encrypt in-place.
out = msg;
out.resize(ct_and_tag.size());
ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), out.data(), msg.size(),
aad.data(), aad.size()));
EXPECT_EQ(Bytes(ct_and_tag), Bytes(out.data(), out_len));
} else {
// Decryption should fail.
EXPECT_FALSE(EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), ct_and_tag.data(),
ct_and_tag.size(), aad.data(), aad.size()));
// Decryption in-place should also fail.
out = ct_and_tag;
EXPECT_FALSE(EVP_AEAD_CTX_open(ctx.get(), out.data(), &out_len, out.size(),
iv.data(), iv.size(), out.data(), out.size(),
aad.data(), aad.size()));
}
}
TEST(AEADTest, WycheproofAESGCMSIV) {
FileTestGTest("third_party/wycheproof_testvectors/aes_gcm_siv_test.txt",
[](FileTest *t) {
std::string key_size_str;
ASSERT_TRUE(t->GetInstruction(&key_size_str, "keySize"));
const EVP_AEAD *aead;
switch (atoi(key_size_str.c_str())) {
case 128:
aead = EVP_aead_aes_128_gcm_siv();
break;
case 256:
aead = EVP_aead_aes_256_gcm_siv();
break;
default:
FAIL() << "Unknown key size: " << key_size_str;
}
RunWycheproofTestCase(t, aead);
});
}
TEST(AEADTest, WycheproofAESGCM) {
FileTestGTest("third_party/wycheproof_testvectors/aes_gcm_test.txt",
[](FileTest *t) {
std::string key_size_str;
ASSERT_TRUE(t->GetInstruction(&key_size_str, "keySize"));
const EVP_AEAD *aead;
switch (atoi(key_size_str.c_str())) {
case 128:
aead = EVP_aead_aes_128_gcm();
break;
case 192:
aead = EVP_aead_aes_192_gcm();
break;
case 256:
aead = EVP_aead_aes_256_gcm();
break;
default:
FAIL() << "Unknown key size: " << key_size_str;
}
RunWycheproofTestCase(t, aead);
});
}
TEST(AEADTest, WycheproofChaCha20Poly1305) {
FileTestGTest("third_party/wycheproof_testvectors/chacha20_poly1305_test.txt",
[](FileTest *t) {
t->IgnoreInstruction("keySize");
RunWycheproofTestCase(t, EVP_aead_chacha20_poly1305());
});
}
TEST(AEADTest, WycheproofXChaCha20Poly1305) {
FileTestGTest(
"third_party/wycheproof_testvectors/xchacha20_poly1305_test.txt",
[](FileTest *t) {
t->IgnoreInstruction("keySize");
RunWycheproofTestCase(t, EVP_aead_xchacha20_poly1305());
});
}
TEST(AEADTest, FreeNull) {
EVP_AEAD_CTX_free(nullptr);
}