| /* Copyright (c) 2014, Google Inc. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
| |
| #include <assert.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <vector> |
| |
| #include <openssl/aead.h> |
| #include <openssl/crypto.h> |
| #include <openssl/err.h> |
| |
| #include "../internal.h" |
| #include "../test/file_test.h" |
| |
| |
| #if defined(OPENSSL_SMALL) |
| const EVP_AEAD* EVP_aead_aes_128_gcm_siv(void) { |
| return nullptr; |
| } |
| const EVP_AEAD* EVP_aead_aes_256_gcm_siv(void) { |
| return nullptr; |
| } |
| #endif |
| |
| // This program 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 |
| |
| static bool TestAEAD(FileTest *t, void *arg) { |
| const EVP_AEAD *aead = reinterpret_cast<const EVP_AEAD*>(arg); |
| |
| std::vector<uint8_t> key, nonce, in, ad, ct, tag; |
| if (!t->GetBytes(&key, "KEY") || |
| !t->GetBytes(&nonce, "NONCE") || |
| !t->GetBytes(&in, "IN") || |
| !t->GetBytes(&ad, "AD") || |
| !t->GetBytes(&ct, "CT") || |
| !t->GetBytes(&tag, "TAG")) { |
| return false; |
| } |
| |
| bssl::ScopedEVP_AEAD_CTX ctx; |
| if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(), |
| tag.size(), evp_aead_seal)) { |
| t->PrintLine("Failed to init AEAD."); |
| return false; |
| } |
| |
| std::vector<uint8_t> out(in.size() + EVP_AEAD_max_overhead(aead)); |
| if (!t->HasAttribute("NO_SEAL")) { |
| size_t out_len; |
| if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(), |
| nonce.data(), nonce.size(), in.data(), in.size(), |
| ad.data(), ad.size())) { |
| t->PrintLine("Failed to run AEAD."); |
| return false; |
| } |
| out.resize(out_len); |
| |
| if (out.size() != ct.size() + tag.size()) { |
| t->PrintLine("Bad output length: %u vs %u.", (unsigned)out_len, |
| (unsigned)(ct.size() + tag.size())); |
| return false; |
| } |
| if (!t->ExpectBytesEqual(ct.data(), ct.size(), out.data(), ct.size()) || |
| !t->ExpectBytesEqual(tag.data(), tag.size(), out.data() + ct.size(), |
| tag.size())) { |
| return false; |
| } |
| } 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(); |
| if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(), |
| tag.size(), evp_aead_open)) { |
| t->PrintLine("Failed to init AEAD."); |
| return false; |
| } |
| |
| 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")) { |
| if (ret) { |
| t->PrintLine("Decrypted bad data."); |
| return false; |
| } |
| ERR_clear_error(); |
| return true; |
| } |
| |
| if (!ret) { |
| t->PrintLine("Failed to decrypt."); |
| return false; |
| } |
| out2.resize(out2_len); |
| if (!t->ExpectBytesEqual(in.data(), in.size(), out2.data(), out2.size())) { |
| return false; |
| } |
| |
| // The "stateful" AEADs for implementing pre-AEAD cipher suites need to be |
| // reset after each operation. |
| ctx.Reset(); |
| if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(), |
| tag.size(), evp_aead_open)) { |
| t->PrintLine("Failed to init AEAD."); |
| return false; |
| } |
| |
| // Garbage at the end isn't ignored. |
| out.push_back(0); |
| out2.resize(out.size()); |
| if (EVP_AEAD_CTX_open(ctx.get(), out2.data(), &out2_len, out2.size(), |
| nonce.data(), nonce.size(), out.data(), out.size(), |
| ad.data(), ad.size())) { |
| t->PrintLine("Decrypted bad data with trailing garbage."); |
| return false; |
| } |
| ERR_clear_error(); |
| |
| // The "stateful" AEADs for implementing pre-AEAD cipher suites need to be |
| // reset after each operation. |
| ctx.Reset(); |
| if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(), |
| tag.size(), evp_aead_open)) { |
| t->PrintLine("Failed to init AEAD."); |
| return false; |
| } |
| |
| // Verify integrity is checked. |
| out[0] ^= 0x80; |
| out.resize(out.size() - 1); |
| out2.resize(out.size()); |
| if (EVP_AEAD_CTX_open(ctx.get(), out2.data(), &out2_len, out2.size(), |
| nonce.data(), nonce.size(), out.data(), out.size(), |
| ad.data(), ad.size())) { |
| t->PrintLine("Decrypted bad data with corrupted byte."); |
| return false; |
| } |
| ERR_clear_error(); |
| |
| return true; |
| } |
| |
| static int TestCleanupAfterInitFailure(const EVP_AEAD *aead) { |
| 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(sizeof(key) >= key_len); |
| |
| EVP_AEAD_CTX ctx; |
| if (EVP_AEAD_CTX_init(&ctx, aead, key, key_len, |
| 9999 /* a silly tag length to trigger an error */, |
| NULL /* ENGINE */) != 0) { |
| fprintf(stderr, "A silly tag length didn't trigger an error!\n"); |
| return 0; |
| } |
| ERR_clear_error(); |
| |
| /* Running a second, failed _init should not cause a memory leak. */ |
| if (EVP_AEAD_CTX_init(&ctx, aead, key, key_len, |
| 9999 /* a silly tag length to trigger an error */, |
| NULL /* ENGINE */) != 0) { |
| fprintf(stderr, "A silly tag length didn't trigger an error!\n"); |
| return 0; |
| } |
| ERR_clear_error(); |
| |
| /* Calling _cleanup on an |EVP_AEAD_CTX| after a failed _init should be a |
| * no-op. */ |
| EVP_AEAD_CTX_cleanup(&ctx); |
| return 1; |
| } |
| |
| static int TestTruncatedTags(const EVP_AEAD *aead) { |
| 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(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(sizeof(nonce) >= nonce_len); |
| |
| bssl::ScopedEVP_AEAD_CTX ctx; |
| if (!EVP_AEAD_CTX_init(ctx.get(), aead, key, key_len, 1 /* one byte tag */, |
| NULL /* ENGINE */)) { |
| fprintf(stderr, "Couldn't initialise AEAD with truncated tag.\n"); |
| return 1; |
| } |
| |
| 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)); |
| |
| if (!EVP_AEAD_CTX_seal(ctx.get(), ciphertext, &ciphertext_len, |
| sizeof(ciphertext), nonce, nonce_len, plaintext, |
| sizeof(plaintext), nullptr /* ad */, 0)) { |
| fprintf(stderr, "Sealing with truncated tag didn't work.\n"); |
| return 0; |
| } |
| |
| for (size_t i = ciphertext_len; i < sizeof(ciphertext); i++) { |
| // Sealing must not write past where it said it did. |
| if (ciphertext[i] != kSentinel) { |
| fprintf(stderr, "Sealing wrote off the end of the buffer.\n"); |
| return 0; |
| } |
| } |
| |
| const size_t overhead_used = ciphertext_len - sizeof(plaintext); |
| if (overhead_used != 1) { |
| fprintf(stderr, "AEAD is probably ignoring request to truncate tags.\n"); |
| return 0; |
| } |
| |
| uint8_t plaintext2[sizeof(plaintext) + 16]; |
| OPENSSL_memset(plaintext2, kSentinel, sizeof(plaintext2)); |
| |
| size_t plaintext2_len; |
| if (!EVP_AEAD_CTX_open(ctx.get(), plaintext2, &plaintext2_len, |
| sizeof(plaintext2), nonce, nonce_len, ciphertext, |
| ciphertext_len, nullptr /* ad */, 0)) { |
| fprintf(stderr, "Opening with truncated tag didn't work.\n"); |
| return 0; |
| } |
| |
| for (size_t i = plaintext2_len; i < sizeof(plaintext2); i++) { |
| // Likewise, opening should also stay within bounds. |
| if (plaintext2[i] != kSentinel) { |
| fprintf(stderr, "Opening wrote off the end of the buffer.\n"); |
| return 0; |
| } |
| } |
| |
| if (plaintext2_len != sizeof(plaintext) || |
| OPENSSL_memcmp(plaintext2, plaintext, sizeof(plaintext)) != 0) { |
| fprintf(stderr, "Opening with truncated tag gave wrong result.\n"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static bool TestWithAliasedBuffers(const EVP_AEAD *aead) { |
| 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; |
| if (!EVP_AEAD_CTX_init(ctx.get(), aead, key.data(), key_len, |
| EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr)) { |
| return false; |
| } |
| |
| 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; |
| if (!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)) { |
| fprintf(stderr, "EVP_AEAD_CTX_seal failed with disjoint buffers.\n"); |
| return false; |
| } |
| |
| // 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; |
| if (EVP_AEAD_CTX_seal(ctx.get(), out1, &out_len, |
| sizeof(kPlaintext) + max_overhead, nonce.data(), |
| nonce_len, in, sizeof(kPlaintext), nullptr, 0) || |
| EVP_AEAD_CTX_seal(ctx.get(), out2, &out_len, |
| sizeof(kPlaintext) + max_overhead, nonce.data(), |
| nonce_len, in, sizeof(kPlaintext), nullptr, 0)) { |
| fprintf(stderr, "EVP_AEAD_CTX_seal unexpectedly succeeded.\n"); |
| return false; |
| } |
| ERR_clear_error(); |
| |
| OPENSSL_memcpy(in, valid_encryption.data(), valid_encryption_len); |
| if (EVP_AEAD_CTX_open(ctx.get(), out1, &out_len, valid_encryption_len, |
| nonce.data(), nonce_len, in, valid_encryption_len, |
| nullptr, 0) || |
| EVP_AEAD_CTX_open(ctx.get(), out2, &out_len, valid_encryption_len, |
| nonce.data(), nonce_len, in, valid_encryption_len, |
| nullptr, 0)) { |
| fprintf(stderr, "EVP_AEAD_CTX_open unexpectedly succeeded.\n"); |
| return false; |
| } |
| ERR_clear_error(); |
| |
| // Test with out == in, which we expect to work. |
| OPENSSL_memcpy(in, kPlaintext, sizeof(kPlaintext)); |
| |
| if (!EVP_AEAD_CTX_seal(ctx.get(), in, &out_len, |
| sizeof(kPlaintext) + max_overhead, nonce.data(), |
| nonce_len, in, sizeof(kPlaintext), nullptr, 0)) { |
| fprintf(stderr, "EVP_AEAD_CTX_seal failed in-place.\n"); |
| return false; |
| } |
| |
| if (out_len != valid_encryption_len || |
| OPENSSL_memcmp(in, valid_encryption.data(), out_len) != 0) { |
| fprintf(stderr, "EVP_AEAD_CTX_seal produced bad output in-place.\n"); |
| return false; |
| } |
| |
| OPENSSL_memcpy(in, valid_encryption.data(), valid_encryption_len); |
| if (!EVP_AEAD_CTX_open(ctx.get(), in, &out_len, valid_encryption_len, |
| nonce.data(), nonce_len, in, valid_encryption_len, |
| nullptr, 0)) { |
| fprintf(stderr, "EVP_AEAD_CTX_open failed in-place.\n"); |
| return false; |
| } |
| |
| if (out_len != sizeof(kPlaintext) || |
| OPENSSL_memcmp(in, kPlaintext, out_len) != 0) { |
| fprintf(stderr, "EVP_AEAD_CTX_open produced bad output in-place.\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| struct KnownAEAD { |
| const char name[40]; |
| const EVP_AEAD *(*func)(void); |
| // limited_implementation 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 |
| // SSLv3/TLS AEADs have the concept of “direction”. |
| bool limited_implementation; |
| // truncated_tags is true if the AEAD supports truncating tags to arbitrary |
| // lengths. |
| bool truncated_tags; |
| }; |
| |
| static const struct KnownAEAD kAEADs[] = { |
| { "aes-128-gcm", EVP_aead_aes_128_gcm, false, true }, |
| { "aes-256-gcm", EVP_aead_aes_256_gcm, false, true }, |
| { "aes-128-gcm-siv", EVP_aead_aes_128_gcm_siv, false, false }, |
| { "aes-256-gcm-siv", EVP_aead_aes_256_gcm_siv, false, false }, |
| { "chacha20-poly1305", EVP_aead_chacha20_poly1305, false, true }, |
| { "aes-128-cbc-sha1-tls", EVP_aead_aes_128_cbc_sha1_tls, true, false }, |
| { "aes-128-cbc-sha1-tls-implicit-iv", EVP_aead_aes_128_cbc_sha1_tls_implicit_iv, true, false }, |
| { "aes-128-cbc-sha256-tls", EVP_aead_aes_128_cbc_sha256_tls, true, false }, |
| { "aes-256-cbc-sha1-tls", EVP_aead_aes_256_cbc_sha1_tls, true, false }, |
| { "aes-256-cbc-sha1-tls-implicit-iv", EVP_aead_aes_256_cbc_sha1_tls_implicit_iv, true, false }, |
| { "aes-256-cbc-sha256-tls", EVP_aead_aes_256_cbc_sha256_tls, true, false }, |
| { "aes-256-cbc-sha384-tls", EVP_aead_aes_256_cbc_sha384_tls, true, false }, |
| { "des-ede3-cbc-sha1-tls", EVP_aead_des_ede3_cbc_sha1_tls, true, false }, |
| { "des-ede3-cbc-sha1-tls-implicit-iv", EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv, true, false }, |
| { "aes-128-cbc-sha1-ssl3", EVP_aead_aes_128_cbc_sha1_ssl3, true, false }, |
| { "aes-256-cbc-sha1-ssl3", EVP_aead_aes_256_cbc_sha1_ssl3, true, false }, |
| { "des-ede3-cbc-sha1-ssl3", EVP_aead_des_ede3_cbc_sha1_ssl3, true, false }, |
| { "aes-128-ctr-hmac-sha256", EVP_aead_aes_128_ctr_hmac_sha256, false, true }, |
| { "aes-256-ctr-hmac-sha256", EVP_aead_aes_256_ctr_hmac_sha256, false, true }, |
| { "", NULL, false, false }, |
| }; |
| |
| int main(int argc, char **argv) { |
| CRYPTO_library_init(); |
| |
| if (argc != 3) { |
| fprintf(stderr, "%s <aead> <test file.txt>\n", argv[0]); |
| return 1; |
| } |
| |
| const struct KnownAEAD *known_aead; |
| for (unsigned i = 0;; i++) { |
| known_aead = &kAEADs[i]; |
| if (known_aead->func == NULL) { |
| fprintf(stderr, "Unknown AEAD: %s\n", argv[1]); |
| return 2; |
| } |
| if (strcmp(known_aead->name, argv[1]) == 0) { |
| break; |
| } |
| } |
| |
| const EVP_AEAD *const aead = known_aead->func(); |
| if (aead == NULL) { |
| // AEAD is not compiled in this configuration. |
| printf("PASS\n"); |
| return 0; |
| } |
| |
| if (!TestCleanupAfterInitFailure(aead)) { |
| return 1; |
| } |
| |
| if (known_aead->truncated_tags && !TestTruncatedTags(aead)) { |
| fprintf(stderr, "Truncated tags test failed for %s.\n", known_aead->name); |
| return 1; |
| } |
| |
| if (!known_aead->limited_implementation && !TestWithAliasedBuffers(aead)) { |
| fprintf(stderr, "Aliased buffers test failed for %s.\n", known_aead->name); |
| return 1; |
| } |
| |
| return FileTestMain(TestAEAD, const_cast<EVP_AEAD*>(aead), argv[2]); |
| } |