| // 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 <optional> | |
| #include <vector> | |
| #include <gtest/gtest.h> | |
| #include <openssl/aead.h> | |
| #include <openssl/cipher.h> | |
| #include <openssl/err.h> | |
| #include <openssl/span.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" | |
| BSSL_NAMESPACE_BEGIN | |
| 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 << 3; | |
| // kVariableTag indicates that the AEAD outputs a variable length tag. | |
| constexpr uint32_t kVariableTag = 1 << 4; | |
| // kSkipIOVec indicates that the test vectors should be skipped when running | |
| // iovec tests. | |
| constexpr uint32_t kSkipIOVec = 1 << 5; | |
| struct KnownAEAD { | |
| const char name[40]; | |
| const EVP_AEAD *(*func)(); | |
| const char *test_vectors; | |
| uint32_t flags; | |
| // Optional settings - leave out if not needed. | |
| std::optional<size_t> required_ad_length = std::nullopt; | |
| std::optional<size_t> minimum_tag_length = std::nullopt; | |
| std::string TestVectorPath() const { | |
| return std::string("crypto/cipher/test/") + test_vectors; | |
| } | |
| }; | |
| static const struct KnownAEAD kAEADs[] = { | |
| {"AES_128_GCM", EVP_aead_aes_128_gcm, "aes_128_gcm_tests.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}, | |
| // A set of 31,000 test vectors imported from NIST. We skip iovec tests | |
| // because multiplicatively testing these vectors against different iovec | |
| // splits takes a very long time and is of low value. Instead, we assume | |
| // that our normal test vectors provide sufficient iovec coverage, and just | |
| // run them through the non-iovec APIs to ensure the overall AEAD | |
| // implementation matches. | |
| {"AES_128_GCM_NIST", EVP_aead_aes_128_gcm, "nist_cavp/aes_128_gcm.txt", | |
| kCanTruncateTags | kVariableNonce | kSkipIOVec}, | |
| {"AES_256_GCM_NIST", EVP_aead_aes_256_gcm, "nist_cavp/aes_256_gcm.txt", | |
| kCanTruncateTags | kVariableNonce | kSkipIOVec}, | |
| {"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, | |
| /*required_ad_length=*/std::nullopt, /*minimum_tag_length=*/13}, | |
| {"AES_256_GCM_RandomNonce", EVP_aead_aes_256_gcm_randnonce, | |
| "aes_256_gcm_randnonce_tests.txt", kNondeterministic | kCanTruncateTags, | |
| /*required_ad_length=*/std::nullopt, /*minimum_tag_length=*/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 | kVariableTag, | |
| /*required_ad_length=*/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 | kVariableTag, /*required_ad_length=*/11}, | |
| {"AES_256_CBC_SHA1_TLS", EVP_aead_aes_256_cbc_sha1_tls, | |
| "aes_256_cbc_sha1_tls_tests.txt", kLimitedImplementation | kVariableTag, | |
| /*required_ad_length=*/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 | kVariableTag, /*required_ad_length=*/11}, | |
| {"DES_EDE3_CBC_SHA1_TLS", EVP_aead_des_ede3_cbc_sha1_tls, | |
| "des_ede3_cbc_sha1_tls_tests.txt", kLimitedImplementation | kVariableTag, | |
| /*required_ad_length=*/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 | kVariableTag, /*required_ad_length=*/11}, | |
| {"AES_128_CBC_SHA256_TLS", EVP_aead_aes_128_cbc_sha256_tls, | |
| "aes_128_cbc_sha256_tls_tests.txt", kLimitedImplementation | kVariableTag, | |
| /*required_ad_length=*/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) { | |
| FileTestGTest(GetParam().TestVectorPath().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); | |
| } | |
| 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(); | |
| FileTestGTest(GetParam().TestVectorPath().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")); | |
| 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); | |
| } | |
| 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++) { | |
| SCOPED_TRACE(extra_in_size); | |
| ScopedEVP_AEAD_CTX ctx; | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( | |
| ctx.get(), aead(), key.data(), key.size(), tag_len, evp_aead_seal)); | |
| size_t tag_bytes_written; | |
| 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)); | |
| // Bounds on the tag output should be checked. | |
| { | |
| std::vector<uint8_t> tag_buf(extra_in_size == 0 ? 0 | |
| : extra_in_size - 1); | |
| EXPECT_FALSE(EVP_AEAD_CTX_seal_scatter( | |
| ctx.get(), out.data(), tag_buf.data(), &tag_bytes_written, | |
| tag_buf.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())); | |
| EXPECT_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_BUFFER_TOO_SMALL}})); | |
| } | |
| { | |
| std::vector<uint8_t> tag_buf(extra_in_size + tag.size() - 1); | |
| EXPECT_FALSE(EVP_AEAD_CTX_seal_scatter( | |
| ctx.get(), out.data(), tag_buf.data(), &tag_bytes_written, | |
| tag_buf.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())); | |
| EXPECT_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_BUFFER_TOO_SMALL}})); | |
| } | |
| } | |
| }); | |
| } | |
| TEST_P(PerAEADTest, TestVectorScatterGather) { | |
| const KnownAEAD &aead_config = GetParam(); | |
| FileTestGTest(aead_config.TestVectorPath().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); | |
| } | |
| 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(); | |
| }); | |
| } | |
| class TestIOVecs { | |
| public: | |
| TestIOVecs() = default; | |
| TestIOVecs(TestIOVecs &&) = default; | |
| TestIOVecs &operator=(TestIOVecs &&) = default; | |
| TestIOVecs(const TestIOVecs &other) { *this = other.Clone(); } | |
| TestIOVecs &operator=(const TestIOVecs &other) { | |
| return (*this = other.Clone()); | |
| } | |
| static TestIOVecs Split(Span<const uint8_t> inp, std::vector<size_t> splits, | |
| bool in_place) { | |
| TestIOVecs ret; | |
| for (size_t i = 0; i <= splits.size(); i++) { | |
| size_t start = i == 0 ? 0 : splits[i - 1]; | |
| size_t end = i == splits.size() ? inp.size() : splits[i]; | |
| assert(start <= end); | |
| ret.Append(inp.subspan(start, end - start), in_place); | |
| } | |
| return ret; | |
| } | |
| Span<const CRYPTO_IOVEC> iovecs() const { return iovecs_; } | |
| Span<const CRYPTO_IVEC> ivecs() const { return ivecs_; } | |
| void Append(Span<const uint8_t> inp, bool in_place = false) { | |
| CRYPTO_IOVEC iovec; | |
| iovec.len = inp.size(); | |
| buffers_.emplace_back(inp.begin(), inp.end()); | |
| iovec.in = buffers_.back().data(); | |
| if (!in_place) { | |
| buffers_.emplace_back(inp.size(), 'X'); | |
| } | |
| iovec.out = buffers_.back().data(); | |
| iovecs_.push_back(iovec); | |
| CRYPTO_IVEC ivec; | |
| ivec.len = iovec.len; | |
| ivec.in = iovec.in; | |
| ivecs_.push_back(ivec); | |
| } | |
| std::vector<uint8_t> Output() const { | |
| std::vector<uint8_t> ret; | |
| for (const auto &iovec : iovecs_) { | |
| ret.insert(ret.end(), iovec.out, iovec.out + iovec.len); | |
| } | |
| return ret; | |
| } | |
| TestIOVecs Clone() const { | |
| TestIOVecs out; | |
| for (const CRYPTO_IOVEC &iovec : iovecs_) { | |
| out.Append(Span(iovec.in, iovec.len), | |
| /*in_place=*/iovec.in == iovec.out); | |
| } | |
| return out; | |
| } | |
| private: | |
| std::vector<CRYPTO_IOVEC> iovecs_; | |
| std::vector<CRYPTO_IVEC> ivecs_; | |
| std::vector<std::vector<uint8_t>> buffers_; | |
| }; | |
| std::vector<std::vector<size_t>> InterestingSplitsForLength(size_t length, | |
| size_t block_size) { | |
| std::set<std::set<size_t>> ideas; // Sort and dedup. | |
| const size_t second_block_start = block_size; | |
| const size_t unaligned_at_start = 1; | |
| const size_t unaligned_at_end = | |
| (length % block_size == 1) ? length - 2 : length - 1; | |
| const size_t last_block_start = (length - 1) / block_size * block_size; | |
| // 1 chunk. | |
| ideas.insert(std::set<size_t>{}); | |
| // 2 chunks. | |
| ideas.insert({0}); | |
| ideas.insert({unaligned_at_start}); | |
| ideas.insert({second_block_start}); | |
| ideas.insert({last_block_start}); | |
| ideas.insert({unaligned_at_end}); | |
| ideas.insert({length}); | |
| // 3 chunks. | |
| // Only one nonempty chunk. | |
| ideas.insert({unaligned_at_start, unaligned_at_start}); | |
| // Try something useful. | |
| ideas.insert({unaligned_at_start, last_block_start}); | |
| ideas.insert({second_block_start, last_block_start}); | |
| ideas.insert({unaligned_at_start, unaligned_at_end}); | |
| ideas.insert({second_block_start, unaligned_at_end}); | |
| // Convert to vector. | |
| std::vector<std::vector<size_t>> out; | |
| for (const auto &idea : ideas) { | |
| // Skip anything out of range. | |
| bool ok = true; | |
| for (size_t pos : idea) { | |
| if (pos > length) { | |
| ok = false; | |
| break; | |
| } | |
| } | |
| if (!ok) { | |
| continue; | |
| } | |
| out.push_back(std::vector<size_t>(idea.begin(), idea.end())); | |
| } | |
| return out; | |
| } | |
| std::string FormatSplits(const std::vector<size_t> &splits) { | |
| std::ostringstream tracebuf; | |
| for (size_t i = 0; i < splits.size(); ++i) { | |
| if (i != 0) { | |
| tracebuf << ", "; | |
| } | |
| tracebuf << splits[i]; | |
| } | |
| return tracebuf.str(); | |
| } | |
| void RunSealvTests(const KnownAEAD &aead_config, bool in_place) { | |
| if (aead_config.flags & kSkipIOVec) { | |
| return; | |
| } | |
| FileTestGTest(aead_config.TestVectorPath().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); | |
| } | |
| if (t->HasAttribute("NO_SEAL") || (aead_config.flags & kNondeterministic)) { | |
| t->SkipCurrent(); | |
| return; | |
| } | |
| for (const auto &adsplits : | |
| InterestingSplitsForLength(ad.size(), /*block_size=*/16)) { | |
| SCOPED_TRACE(FormatSplits(adsplits)); | |
| TestIOVecs advecs = TestIOVecs::Split(ad, adsplits, in_place); | |
| ScopedEVP_AEAD_CTX ctx; | |
| for (const auto &splits : | |
| InterestingSplitsForLength(in.size(), /*block_size=*/16)) { | |
| if (!adsplits.empty() && !splits.empty()) { | |
| // No need to test both with split AAD and split iovec. Each split | |
| // on its own should already hit everything interesting. | |
| continue; | |
| } | |
| SCOPED_TRACE(FormatSplits(splits)); | |
| ctx.Reset(); | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( | |
| ctx.get(), aead_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_seal)); | |
| TestIOVecs iovecs = TestIOVecs::Split(in, splits, in_place); | |
| std::vector<uint8_t> out_tag(EVP_AEAD_max_overhead(aead_config.func())); | |
| size_t out_tag_len; | |
| ASSERT_TRUE(EVP_AEAD_CTX_sealv( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| out_tag.data(), &out_tag_len, out_tag.size(), nonce.data(), | |
| nonce.size(), advecs.ivecs().data(), advecs.ivecs().size())); | |
| out_tag.resize(out_tag_len); | |
| EXPECT_EQ(Bytes(ct), Bytes(iovecs.Output())); | |
| EXPECT_EQ(Bytes(tag), Bytes(out_tag)); | |
| } | |
| } | |
| }); | |
| } | |
| void RunOpenvDetachedTests(const KnownAEAD &aead_config, bool in_place) { | |
| if (aead_config.flags & (kVariableTag | kSkipIOVec)) { | |
| // openv_detached is not supported for variable-length AEADs. | |
| return; | |
| } | |
| FileTestGTest(aead_config.TestVectorPath().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); | |
| } | |
| t->IgnoreAttribute("NO_SEAL"); | |
| for (const auto &adsplits : | |
| InterestingSplitsForLength(ad.size(), /*block_size=*/16)) { | |
| SCOPED_TRACE(FormatSplits(adsplits)); | |
| TestIOVecs advecs = TestIOVecs::Split(ad, adsplits, in_place); | |
| ScopedEVP_AEAD_CTX ctx; | |
| for (const auto &splits : | |
| InterestingSplitsForLength(ct.size(), /*block_size=*/16)) { | |
| if (!adsplits.empty() && !splits.empty()) { | |
| // No need to test both with split AAD and split iovec. Each split | |
| // on its own should already hit everything interesting. | |
| continue; | |
| } | |
| SCOPED_TRACE(FormatSplits(splits)); | |
| // 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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| TestIOVecs iovecs = TestIOVecs::Split(ct, splits, in_place); | |
| int ret = EVP_AEAD_CTX_openv_detached( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| nonce.data(), nonce.size(), tag.data(), tag.size(), | |
| advecs.ivecs().data(), advecs.ivecs().size()); | |
| if (t->HasAttribute("FAILS")) { | |
| ASSERT_FALSE(ret) << "Decrypted bad data"; | |
| ERR_clear_error(); | |
| continue; | |
| } | |
| ASSERT_TRUE(ret) << "Failed to decrypt: " | |
| << ERR_reason_error_string(ERR_get_error()); | |
| EXPECT_EQ(Bytes(in), Bytes(iovecs.Output())); | |
| // 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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Garbage at the end isn't ignored. | |
| std::vector<uint8_t> bad_tag = tag; | |
| bad_tag.push_back(0); | |
| iovecs = TestIOVecs::Split(ct, splits, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv_detached( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| nonce.data(), nonce.size(), bad_tag.data(), bad_tag.size(), | |
| advecs.ivecs().data(), advecs.ivecs().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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Verify integrity is checked. | |
| bad_tag = tag; | |
| bad_tag[0] ^= 0x80; | |
| iovecs = TestIOVecs::Split(ct, splits, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv_detached( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| nonce.data(), nonce.size(), bad_tag.data(), bad_tag.size(), | |
| advecs.ivecs().data(), advecs.ivecs().size())) | |
| << "Decrypted bad data with corrupted byte."; | |
| ERR_clear_error(); | |
| ctx.Reset(); | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( | |
| ctx.get(), aead_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Check a zero-length tag is rejected. | |
| iovecs = TestIOVecs::Split(ct, splits, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv_detached( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| nonce.data(), nonce.size(), nullptr, 0, advecs.ivecs().data(), | |
| advecs.ivecs().size())) | |
| << "Decrypted bad data with zero-length tag."; | |
| ERR_clear_error(); | |
| } | |
| } | |
| }); | |
| } | |
| void RunOpenvTests(const KnownAEAD &aead_config, bool in_place) { | |
| if (aead_config.flags & kSkipIOVec) { | |
| return; | |
| } | |
| FileTestGTest(aead_config.TestVectorPath().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); | |
| } | |
| t->IgnoreAttribute("NO_SEAL"); | |
| std::vector<uint8_t> combined = ct; | |
| combined.insert(combined.end(), tag.begin(), tag.end()); | |
| for (const auto &adsplits : | |
| InterestingSplitsForLength(ad.size(), /*block_size=*/16)) { | |
| SCOPED_TRACE(FormatSplits(adsplits)); | |
| TestIOVecs advecs = TestIOVecs::Split(ad, adsplits, in_place); | |
| ScopedEVP_AEAD_CTX ctx; | |
| for (const auto &splits : | |
| InterestingSplitsForLength(combined.size(), /*block_size=*/16)) { | |
| if (!adsplits.empty() && !splits.empty()) { | |
| // No need to test both with split AAD and split iovec. Each split | |
| // on its own should already hit everything interesting. | |
| continue; | |
| } | |
| SCOPED_TRACE(FormatSplits(splits)); | |
| // 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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| size_t plaintext_len; | |
| TestIOVecs iovecs = TestIOVecs::Split(combined, splits, in_place); | |
| int ret = EVP_AEAD_CTX_openv( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| &plaintext_len, nonce.data(), nonce.size(), advecs.ivecs().data(), | |
| advecs.ivecs().size()); | |
| if (t->HasAttribute("FAILS")) { | |
| ASSERT_FALSE(ret) << "Decrypted bad data"; | |
| ERR_clear_error(); | |
| continue; | |
| } | |
| ASSERT_TRUE(ret) << "Failed to decrypt: " | |
| << ERR_reason_error_string(ERR_get_error()); | |
| std::vector<uint8_t> out = iovecs.Output(); | |
| out.resize(plaintext_len); | |
| EXPECT_EQ(Bytes(in), Bytes(out)); | |
| // 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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Garbage at the end isn't ignored. | |
| std::vector<uint8_t> combined_wrecked(combined); | |
| combined_wrecked.push_back(0); | |
| iovecs = TestIOVecs::Split(combined_wrecked, splits, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| &plaintext_len, nonce.data(), nonce.size(), advecs.ivecs().data(), | |
| advecs.ivecs().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_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Verify integrity is checked by changing the last byte. | |
| combined_wrecked = combined; | |
| combined_wrecked.back() ^= 0x80; | |
| iovecs = TestIOVecs::Split(combined_wrecked, splits, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| &plaintext_len, nonce.data(), nonce.size(), advecs.ivecs().data(), | |
| advecs.ivecs().size())) | |
| << "Decrypted bad data with corrupted byte."; | |
| ERR_clear_error(); | |
| ctx.Reset(); | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction( | |
| ctx.get(), aead_config.func(), key.data(), key.size(), tag_len, | |
| evp_aead_open)); | |
| // Check edge case for tag length. | |
| combined_wrecked = combined; | |
| combined_wrecked.pop_back(); | |
| std::vector<size_t> splits_wrecked = splits; | |
| for (size_t &split : splits_wrecked) { | |
| split = std::min(split, combined_wrecked.size()); | |
| } | |
| iovecs = TestIOVecs::Split(combined_wrecked, splits_wrecked, in_place); | |
| EXPECT_FALSE(EVP_AEAD_CTX_openv( | |
| ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(), | |
| &plaintext_len, nonce.data(), nonce.size(), advecs.ivecs().data(), | |
| advecs.ivecs().size())) | |
| << "Decrypted bad data with corrupted byte."; | |
| ERR_clear_error(); | |
| } | |
| } | |
| }); | |
| } | |
| TEST_P(PerAEADTest, TestSealv) { | |
| RunSealvTests(GetParam(), /*in_place=*/false); | |
| } | |
| TEST_P(PerAEADTest, TestOpenv) { | |
| RunOpenvTests(GetParam(), /*in_place=*/false); | |
| } | |
| TEST_P(PerAEADTest, TestOpenvDetached) { | |
| RunOpenvDetachedTests(GetParam(), /*in_place=*/false); | |
| } | |
| TEST_P(PerAEADTest, TestSealvInPlace) { | |
| RunSealvTests(GetParam(), /*in_place=*/true); | |
| } | |
| TEST_P(PerAEADTest, TestOpenvInPlace) { | |
| RunOpenvTests(GetParam(), /*in_place=*/true); | |
| } | |
| TEST_P(PerAEADTest, TestOpenvDetachedInPlace) { | |
| RunOpenvDetachedTests(GetParam(), /*in_place=*/true); | |
| } | |
| 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 */, nullptr /* 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 */, nullptr /* 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) { | |
| 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); | |
| static const uint8_t ad[32] = {0}; | |
| const size_t ad_len = GetParam().required_ad_length.value_or(16); | |
| ASSERT_LE(ad_len, sizeof(ad)); | |
| size_t tag_len = GetParam().minimum_tag_length.value_or(1); | |
| if (!(GetParam().flags & kCanTruncateTags)) { | |
| // Can't truncate. Still worth running the tests to ensure memory | |
| // correctness. | |
| tag_len = EVP_AEAD_max_tag_len(aead()); | |
| } | |
| 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)); | |
| const size_t expected_overhead = | |
| tag_len + EVP_AEAD_max_overhead(aead()) - EVP_AEAD_max_tag_len(aead()); | |
| size_t expected_ciphertext_len = sizeof(plaintext) + expected_overhead; | |
| ScopedEVP_AEAD_CTX ctx; | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(ctx.get(), aead(), key, key_len, | |
| tag_len, evp_aead_seal)); | |
| if (EVP_AEAD_CTX_seal(ctx.get(), ciphertext, &ciphertext_len, | |
| expected_ciphertext_len - 1, nonce, nonce_len, | |
| plaintext, sizeof(plaintext), ad, ad_len)) { | |
| // Never write more bytes than the caller said is available. | |
| ASSERT_LE(ciphertext_len, expected_ciphertext_len - 1); | |
| 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."; | |
| } | |
| ASSERT_TRUE(GetParam().flags & kLimitedImplementation) | |
| << "Got a shorter ciphertext with shorter-than-expected AEAD length, " | |
| "even though this AEAD is meant to be full featured and should " | |
| "respect the tag length initially provided to init perfectly."; | |
| } | |
| OPENSSL_memset(ciphertext, kSentinel, sizeof(ciphertext)); | |
| ctx.Reset(); | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(ctx.get(), aead(), key, key_len, | |
| tag_len, evp_aead_seal)); | |
| ASSERT_TRUE(EVP_AEAD_CTX_seal(ctx.get(), ciphertext, &ciphertext_len, | |
| expected_ciphertext_len, nonce, nonce_len, | |
| plaintext, sizeof(plaintext), ad, ad_len)); | |
| // Never write more bytes than the caller said is available. | |
| ASSERT_LE(ciphertext_len, expected_ciphertext_len); | |
| 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."; | |
| } | |
| if (!(GetParam().flags & kLimitedImplementation)) { | |
| EXPECT_EQ(ciphertext_len, expected_ciphertext_len) | |
| << "AEAD is probably ignoring request to truncate tags."; | |
| } | |
| uint8_t plaintext2[sizeof(plaintext) + 64]; | |
| OPENSSL_memset(plaintext2, kSentinel, sizeof(plaintext2)); | |
| ctx.Reset(); | |
| ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(ctx.get(), aead(), key, key_len, | |
| tag_len, evp_aead_open)); | |
| size_t plaintext2_len; | |
| ASSERT_TRUE(EVP_AEAD_CTX_open(ctx.get(), plaintext2, &plaintext2_len, | |
| sizeof(plaintext2), nonce, nonce_len, | |
| ciphertext, ciphertext_len, ad, ad_len)) | |
| << "Opening with truncated tag didn't work."; | |
| size_t max_touched_len = plaintext2_len; | |
| if (GetParam().flags & kLimitedImplementation) { | |
| // Limited AEADs may use additional buffer space up to the ciphertext | |
| // length, provided it fits in the plaintext buffer. | |
| max_touched_len = std::max(max_touched_len, ciphertext_len); | |
| } | |
| for (size_t i = max_touched_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'); | |
| 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_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT}})); | |
| 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)); | |
| EXPECT_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT}})); | |
| 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_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT}})); | |
| 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)); | |
| EXPECT_TRUE( | |
| ErrorsAreAndClear({{ERR_LIB_CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT}})); | |
| // 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 = GetParam().required_ad_length.value_or(sizeof(ad) - 1); | |
| ASSERT_GE(sizeof(ad) - 1, ad_len); | |
| // Encrypt some input. | |
| 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)); | |
| 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 = GetParam().required_ad_length.value_or(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); | |
| 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)); | |
| ScopedEVP_AEAD_CTX ctx_seal; | |
| ASSERT_TRUE( | |
| CHECK_ABI(aead_ctx_init_for_seal, ctx_seal.get(), aead(), key, key_len)); | |
| 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; | |
| const size_t ad_len = | |
| GetParam().required_ad_length.value_or(sizeof(ad_buf) - 1); | |
| ASSERT_LE(ad_len, 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 | |
| } | |
| } | |
| #if defined(AES_GCM_SIV_ASM) | |
| TEST(AESGCMSIVTest, ABI) { | |
| if (!aes_gcm_siv_asm_capable()) { | |
| return; | |
| } | |
| uint8_t key128[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; | |
| uint8_t key256[32] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | |
| 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, | |
| 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; | |
| aead_aes_gcm_siv_asm_ctx ctx128, ctx256; | |
| CHECK_ABI(aes128gcmsiv_aes_ks, key128, &ctx128.key[0]); | |
| ctx128.is_128_bit = 1; | |
| CHECK_ABI(aes256gcmsiv_aes_ks, key256, &ctx256.key[0]); | |
| ctx256.is_128_bit = 0; | |
| alignas(16) uint8_t block[16] = {1, 2, 3, 4, 5, 6, 7, 8, | |
| 9, 10, 11, 12, 13, 14, 15, 16}; | |
| alignas(16) uint8_t out_block[16]; | |
| alignas(16) uint8_t expanded_key[16 * 15]; | |
| alignas(16) uint64_t record_enc_key[4] = {1, 2, 3, 4}; | |
| CHECK_ABI(aes128gcmsiv_aes_ks_enc_x1, block, out_block, expanded_key, | |
| record_enc_key); | |
| CHECK_ABI(aes256gcmsiv_aes_ks_enc_x1, block, out_block, expanded_key, | |
| record_enc_key); | |
| CHECK_ABI(aes128gcmsiv_ecb_enc_block, block, out_block, &ctx128); | |
| CHECK_ABI(aes256gcmsiv_ecb_enc_block, block, out_block, &ctx256); | |
| alignas(16) uint8_t padded_nonce[16] = {1, 2, 3, 4, 5, 6, 7, 8, | |
| 9, 10, 11, 12, 0, 0, 0, 0}; | |
| alignas(16) uint64_t out_key_material[12]; | |
| CHECK_ABI(aes128gcmsiv_kdf, padded_nonce, out_key_material, &ctx128.key[0]); | |
| CHECK_ABI(aes256gcmsiv_kdf, padded_nonce, out_key_material, &ctx256.key[0]); | |
| alignas(16) uint8_t auth_key[16] = {1, 2, 3, 4, 5, 6, 7, 8, | |
| 9, 10, 11, 12, 13, 14, 15, 16}; | |
| alignas(16) uint8_t htable8[16 * 8]; | |
| alignas(16) uint8_t htable6[16 * 6]; | |
| CHECK_ABI(aesgcmsiv_htable_init, htable8, auth_key); | |
| CHECK_ABI(aesgcmsiv_htable6_init, htable6, auth_key); | |
| uint8_t buf[256] = {0}; | |
| uint8_t out_buf[256] = {0}; | |
| alignas(16) uint8_t poly[16] = {0}; | |
| for (size_t blocks : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) { | |
| CHECK_ABI(aesgcmsiv_polyval_horner, poly, auth_key, buf, blocks); | |
| } | |
| alignas(16) uint8_t tag[16] = {0}; | |
| alignas(16) uint8_t calculated_tag_and_scratch[16 * 8] = {0}; | |
| for (size_t len : {0, 16, 32, 48, 64, 80, 96, 112, 128, 144}) { | |
| CHECK_ABI(aesgcmsiv_htable_polyval, htable8, buf, len, poly); | |
| CHECK_ABI(aes128gcmsiv_enc_msg_x4, buf, out_buf, tag, &ctx128, len); | |
| CHECK_ABI(aes256gcmsiv_enc_msg_x4, buf, out_buf, tag, &ctx256, len); | |
| CHECK_ABI(aes128gcmsiv_enc_msg_x8, buf, out_buf, tag, &ctx128, len); | |
| CHECK_ABI(aes256gcmsiv_enc_msg_x8, buf, out_buf, tag, &ctx256, len); | |
| CHECK_ABI(aes128gcmsiv_dec, buf, out_buf, calculated_tag_and_scratch, | |
| htable6, &ctx128, len); | |
| CHECK_ABI(aes256gcmsiv_dec, buf, out_buf, calculated_tag_and_scratch, | |
| htable6, &ctx256, len); | |
| } | |
| } | |
| #endif // AES_GCM_SIV_ASM | |
| #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. | |
| 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()); | |
| 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())); | |
| EXPECT_TRUE(ErrorsAreAndClear({{ERR_LIB_CIPHER, std::nullopt}})); | |
| // 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())); | |
| EXPECT_TRUE(ErrorsAreAndClear({{ERR_LIB_CIPHER, std::nullopt}})); | |
| } | |
| } | |
| 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); } | |
| TEST(AEADTest, ForEachBlockRange) { | |
| auto ebg13 = [](const uint8_t *in, uint8_t *out, size_t len) { | |
| while (len > 0) { | |
| if ((*in >= 'A' && *in <= 'M') || (*in >= 'a' && *in <= 'm')) { | |
| *out = *in + 13; | |
| } else if ((*in >= 'N' && *in <= 'Z') || (*in >= 'n' && *in <= 'z')) { | |
| *out = *in - 13; | |
| } else { | |
| *out = *in; | |
| } | |
| --len; | |
| ++in; | |
| ++out; | |
| } | |
| }; | |
| for (const std::string in_str : { | |
| "", | |
| "A", | |
| "Abcdefghijklmno", | |
| "Abcdefghijklmnop", | |
| "AbcdefghijklmnopA", | |
| "AbcdefghijklmnopAbcdefghijklmnop", | |
| "BRLOGENSHFEGLE doesn't lol. The quick brown fox jumped over the " | |
| "lazy sleeping dog's back then sat on a brute-force attack.", | |
| }) { | |
| SCOPED_TRACE(in_str); | |
| Span<const uint8_t> in_span = StringAsBytes(in_str); | |
| std::vector<uint8_t> want(in_str.size(), 'X'); | |
| ebg13(in_span.data(), want.data(), in_span.size()); | |
| std::string_view want_str = BytesAsStringView(want); | |
| for (const auto &splits : | |
| InterestingSplitsForLength(in_span.size(), /*block_size=*/16)) { | |
| SCOPED_TRACE(FormatSplits(splits)); | |
| TestIOVecs iovecs = | |
| TestIOVecs::Split(in_span, splits, /*in_place=*/false); | |
| int final_calls = 0; | |
| size_t bytes_processed = 0; | |
| bssl::iovec::ForEachBlockRange<16, /*WriteOut=*/true>( | |
| iovecs.iovecs(), | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| EXPECT_GE(len, size_t{1}); | |
| EXPECT_EQ(len % size_t{16}, size_t{0}); | |
| ebg13(in, out, len); | |
| bytes_processed += len; | |
| return true; | |
| }, | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| if (in_span.size() != 0) { | |
| EXPECT_GE(len, size_t{1}); | |
| } | |
| ebg13(in, out, len); | |
| ++final_calls; | |
| bytes_processed += len; | |
| return true; | |
| }); | |
| EXPECT_EQ(final_calls, 1); | |
| EXPECT_EQ(bytes_processed, in_str.size()); | |
| auto output = iovecs.Output(); | |
| std::string output_str(reinterpret_cast<char *>(output.data()), | |
| output.size()); | |
| EXPECT_EQ(output_str, want_str); | |
| } | |
| } | |
| } | |
| } // namespace | |
| BSSL_NAMESPACE_END |