Checking for an error on the error queue is very tedious. You have to check both library and reason code separately, which often means you need to save the error code to a local variable. Make an ERR_equals function. I've gone ahead and just made it public API because even within BoringSSL, it seems to come up everywhere. Change-Id: Ia963c9ae743ac5c6939846f8f64bbc98b495ce0b Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81627 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: David Benjamin <davidben@google.com> Auto-Submit: David Benjamin <davidben@google.com>
1064 lines
41 KiB
C++
1064 lines
41 KiB
C++
// Copyright 2014 The BoringSSL Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#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 "../test/abi_test.h"
|
|
#include "../test/file_test.h"
|
|
#include "../test/test_util.h"
|
|
#include "../test/wycheproof_util.h"
|
|
#include "internal.h"
|
|
|
|
namespace {
|
|
|
|
// 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},
|
|
|
|
{"AES_128_EAX", EVP_aead_aes_128_eax, "aes_128_eax_test.txt",
|
|
kVariableNonce},
|
|
|
|
{"AES_256_EAX", EVP_aead_aes_256_eax, "aes_256_eax_test.txt",
|
|
kVariableNonce},
|
|
};
|
|
|
|
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> ¶ms)
|
|
-> 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/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/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/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 && ERR_equals(ERR_peek_error(), ERR_LIB_CIPHER,
|
|
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);
|
|
ASSERT_EQ(out2.size(), 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);
|
|
ASSERT_EQ(out2.size(), 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();
|
|
// TODO(davidben): Merge these errors. https://crbug.com/boringssl/129.
|
|
if (!ErrorEquals(err, ERR_LIB_CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE)) {
|
|
EXPECT_TRUE(
|
|
ErrorEquals(err, ERR_LIB_CIPHER, CIPHER_R_INVALID_NONCE_SIZE));
|
|
}
|
|
|
|
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();
|
|
if (!ErrorEquals(err, ERR_LIB_CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE)) {
|
|
EXPECT_TRUE(
|
|
ErrorEquals(err, ERR_LIB_CIPHER, CIPHER_R_INVALID_NONCE_SIZE));
|
|
}
|
|
}
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
auto buf = std::make_unique<uint8_t[]>(1024);
|
|
for (size_t len = 0; len <= 1024; len += 5) {
|
|
SCOPED_TRACE(len);
|
|
union chacha20_poly1305_open_data open_ctx = {};
|
|
#if defined(OPENSSL_X86_64)
|
|
CHECK_ABI(chacha20_poly1305_open_sse41, buf.get(), buf.get(), len,
|
|
buf.get(), len % 128, &open_ctx);
|
|
if (CRYPTO_is_AVX2_capable() && CRYPTO_is_BMI2_capable()) {
|
|
CHECK_ABI(chacha20_poly1305_open_avx2, buf.get(), buf.get(), len,
|
|
buf.get(), len % 128, &open_ctx);
|
|
}
|
|
#else
|
|
CHECK_ABI(chacha20_poly1305_open, buf.get(), buf.get(), len, buf.get(),
|
|
len % 128, &open_ctx);
|
|
#endif
|
|
}
|
|
|
|
for (size_t len = 0; len <= 1024; len += 5) {
|
|
SCOPED_TRACE(len);
|
|
union chacha20_poly1305_seal_data seal_ctx = {};
|
|
#if defined(OPENSSL_X86_64)
|
|
CHECK_ABI(chacha20_poly1305_seal_sse41, buf.get(), buf.get(), len,
|
|
buf.get(), len % 128, &seal_ctx);
|
|
if (CRYPTO_is_AVX2_capable() && CRYPTO_is_BMI2_capable()) {
|
|
CHECK_ABI(chacha20_poly1305_seal_avx2, buf.get(), buf.get(), len,
|
|
buf.get(), len % 128, &seal_ctx);
|
|
}
|
|
#else
|
|
CHECK_ABI(chacha20_poly1305_seal, buf.get(), buf.get(), len, buf.get(),
|
|
len % 128, &seal_ctx);
|
|
#endif
|
|
}
|
|
}
|
|
#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, WycheproofAESEAX) {
|
|
FileTestGTest(
|
|
"third_party/wycheproof_testvectors/aes_eax_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_eax();
|
|
break;
|
|
case 256:
|
|
aead = EVP_aead_aes_256_eax();
|
|
break;
|
|
default:
|
|
t->SkipCurrent();
|
|
GTEST_SKIP() << "Unsupported key size: " << key_size_str;
|
|
}
|
|
|
|
std::string nonce_size_str;
|
|
ASSERT_TRUE(t->GetInstruction(&nonce_size_str, "ivSize"));
|
|
// Skip tests with invalid nonce size.
|
|
if (nonce_size_str != "96" && nonce_size_str != "128") {
|
|
t->SkipCurrent();
|
|
GTEST_SKIP() << "Unsupported nonce size: " << nonce_size_str;
|
|
}
|
|
|
|
RunWycheproofTestCase(t, aead);
|
|
});
|
|
}
|
|
|
|
TEST(AEADTest, FreeNull) { EVP_AEAD_CTX_free(nullptr); }
|
|
|
|
} // namespace
|