/* Copyright (c) 2017, 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. */ // cavp_aes_test processes a NIST CAVP AES test vector request file and emits // the corresponding response. An optional sample vector file can be passed to // verify the result. #include #include #include #include #include "../crypto/test/file_test.h" #include "cavp_test_util.h" namespace { struct TestCtx { const EVP_CIPHER *cipher; std::unique_ptr response_sample; bool has_iv; enum Mode { kKAT, // Known Answer Test kMCT, // Monte Carlo Test }; Mode mode; }; } static bool MonteCarlo(const TestCtx *ctx, FileTest *t, const EVP_CIPHER *cipher, std::vector *out, bool encrypt, std::vector key, std::vector iv, std::vector in) { const std::string in_label = encrypt ? "PLAINTEXT" : "CIPHERTEXT", result_label = encrypt ? "CIPHERTEXT" : "PLAINTEXT"; std::vector prev_result, result, prev_in; for (int i = 0; i < 100; i++) { printf("COUNT = %d\r\nKEY = %s\r\n", i, EncodeHex(key.data(), key.size()).c_str()); if (ctx->has_iv) { printf("IV = %s\r\n", EncodeHex(iv.data(), iv.size()).c_str()); } printf("%s = %s\r\n", in_label.c_str(), EncodeHex(in.data(), in.size()).c_str()); if (!ctx->has_iv) { // ECB mode for (int j = 0; j < 1000; j++) { prev_result = result; if (!CipherOperation(cipher, &result, encrypt, key, iv, in)) { return false; } in = result; } } else { for (int j = 0; j < 1000; j++) { prev_result = result; if (j > 0) { if (encrypt) { iv = result; } else { iv = prev_in; } } if (!CipherOperation(cipher, &result, encrypt, key, iv, in)) { return false; } prev_in = in; if (j == 0) { in = iv; } else { in = prev_result; } } } printf("%s = %s\r\n\r\n", result_label.c_str(), EncodeHex(result.data(), result.size()).c_str()); // Check if sample response file matches. if (ctx->response_sample) { if (ctx->response_sample->ReadNext() != FileTest::kReadSuccess) { t->PrintLine("invalid sample file"); return false; } std::string expected_count; std::vector expected_key, expected_result; if (!ctx->response_sample->GetBytes(&expected_key, "KEY") || !t->ExpectBytesEqual(expected_key.data(), expected_key.size(), key.data(), key.size()) || !ctx->response_sample->GetBytes(&expected_result, result_label) || !t->ExpectBytesEqual(expected_result.data(), expected_result.size(), result.data(), result.size())) { t->PrintLine("result doesn't match"); return false; } } const size_t key_len = key.size() * 8; if (key_len == 128) { for (size_t k = 0; k < key.size(); k++) { key[k] ^= result[k]; } } else if (key_len == 192) { for (size_t k = 0; k < key.size(); k++) { // Key[i+1] = Key[i] xor (last 64-bits of CT[j-1] || CT[j]) if (k < 8) { key[k] ^= prev_result[prev_result.size() - 8 + k]; } else { key[k] ^= result[k - 8]; } } } else { // key_len == 256 for (size_t k = 0; k < key.size(); k++) { // Key[i+1] = Key[i] xor (CT[j-1] || CT[j]) if (k < 16) { key[k] ^= prev_result[k]; } else { key[k] ^= result[k - 16]; } } } if (ctx->has_iv) { iv = result; in = prev_result; } else { in = result; } } return true; } static bool TestCipher(FileTest *t, void *arg) { TestCtx *ctx = reinterpret_cast(arg); if (t->HasInstruction("ENCRYPT") == t->HasInstruction("DECRYPT")) { t->PrintLine("Want either ENCRYPT or DECRYPT"); return false; } enum { kEncrypt, kDecrypt, } operation = t->HasInstruction("ENCRYPT") ? kEncrypt : kDecrypt; std::string count; std::vector key, iv, in, result; if (!t->GetAttribute(&count, "COUNT") || !t->GetBytes(&key, "KEY") || (ctx->has_iv && !t->GetBytes(&iv, "IV"))) { return false; } const EVP_CIPHER *cipher = ctx->cipher; if (operation == kEncrypt) { if (!t->GetBytes(&in, "PLAINTEXT")) { return false; } } else { // operation == kDecrypt if (!t->GetBytes(&in, "CIPHERTEXT")) { return false; } } if (ctx->mode == TestCtx::kKAT) { if (!CipherOperation(cipher, &result, operation == kEncrypt, key, iv, in)) { return false; } const std::string label = operation == kEncrypt ? "CIPHERTEXT" : "PLAINTEXT"; printf("%s%s = %s\r\n\r\n", t->CurrentTestToString().c_str(), label.c_str(), EncodeHex(result.data(), result.size()).c_str()); // Check if sample response file matches. if (ctx->response_sample) { if (ctx->response_sample->ReadNext() != FileTest::kReadSuccess) { t->PrintLine("invalid sample file"); return false; } std::string expected_count; std::vector expected_result; if (!ctx->response_sample->GetAttribute(&expected_count, "COUNT") || count != expected_count || (operation == kEncrypt && (!ctx->response_sample->GetBytes(&expected_result, "CIPHERTEXT") || !t->ExpectBytesEqual(expected_result.data(), expected_result.size(), result.data(), result.size()))) || (operation == kDecrypt && (!ctx->response_sample->GetBytes(&expected_result, "PLAINTEXT") || !t->ExpectBytesEqual(expected_result.data(), expected_result.size(), result.data(), result.size())))) { t->PrintLine("result doesn't match"); return false; } } } else { // ctx->mode == kMCT const std::string op_label = operation == kEncrypt ? "[ENCRYPT]" : "[DECRYPT]"; printf("%s\r\n\r\n", op_label.c_str()); if (!MonteCarlo(ctx, t, cipher, &result, operation == kEncrypt, key, iv, in)) { return false; } if (operation == kEncrypt) { // MCT tests contain a stray blank line after the ENCRYPT section. printf("\r\n"); } } return true; } static int usage(char *arg) { fprintf(stderr, "usage: %s (kat|mct) []\n", arg); return 1; } int cavp_aes_test_main(int argc, char **argv) { if (argc < 4 || argc > 5) { return usage(argv[0]); } const std::string tm(argv[1]); enum TestCtx::Mode test_mode; if (tm == "kat") { test_mode = TestCtx::kKAT; } else if (tm == "mct") { test_mode = TestCtx::kMCT; } else { fprintf(stderr, "invalid test_mode: %s\n", tm.c_str()); return usage(argv[0]); } const std::string cipher_name(argv[2]); const EVP_CIPHER *cipher = GetCipher(argv[2]); if (cipher == nullptr) { fprintf(stderr, "invalid cipher: %s\n", argv[2]); return 1; } const bool has_iv = (cipher_name != "aes-128-ecb" && cipher_name != "aes-192-ecb" && cipher_name != "aes-256-ecb"); TestCtx ctx = {cipher, nullptr, has_iv, test_mode}; if (argc == 5) { ctx.response_sample.reset(new FileTest(argv[4])); if (!ctx.response_sample->is_open()) { return 1; } ctx.response_sample->SetIgnoreUnusedAttributes(true); } printf("# Generated by"); for (int i = 0; i < argc; i++) { printf(" %s", argv[i]); } printf("\r\n\r\n"); return FileTestMainSilent(TestCipher, &ctx, argv[3]); }