fipsoracle: Add cavp_tdes_test (KAT mode only).
Change-Id: I560051d554760feab8b432de429ccbef7bc19c0a
Reviewed-on: https://boringssl-review.googlesource.com/15684
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/crypto/test/file_test.cc b/crypto/test/file_test.cc
index a730a66..b48100e 100644
--- a/crypto/test/file_test.cc
+++ b/crypto/test/file_test.cc
@@ -100,6 +100,7 @@
std::unique_ptr<char[]> buf(new char[kBufLen]);
bool in_instruction_block = false;
+ is_at_new_instruction_block_ = false;
while (true) {
// Read the next line.
@@ -137,6 +138,7 @@
// comment because the FIPS lab's request files are hopelessly
// inconsistent.
} else if (buf[0] == '[') { // Inside an instruction block.
+ is_at_new_instruction_block_ = true;
if (start_line_ != 0) {
// Instructions should be separate blocks.
fprintf(stderr, "Line %u is an instruction in a test case.\n", line_);
@@ -170,11 +172,12 @@
kv = kv.substr(idx + 1);
}
} else {
+ // Parsing a test case.
if (in_instruction_block) {
- // Test cases should be separate blocks.
- fprintf(stderr, "Line %u is a test case attribute in an instruction block.\n",
- line_);
- return kReadError;
+ // Some NIST CAVP test files (TDES) have a test case immediately
+ // following an instruction block, without a separate blank line, some
+ // of the time.
+ in_instruction_block = false;
}
current_test_ += std::string(buf.get(), len);
@@ -360,6 +363,10 @@
unused_instructions_.erase(key);
}
+bool FileTest::IsAtNewInstructionBlock() const {
+ return is_at_new_instruction_block_;
+}
+
void FileTest::SetIgnoreUnusedAttributes(bool ignore) {
ignore_unused_attributes_ = ignore;
}
diff --git a/crypto/test/file_test.h b/crypto/test/file_test.h
index aac9289..8a3108e 100644
--- a/crypto/test/file_test.h
+++ b/crypto/test/file_test.h
@@ -139,6 +139,10 @@
bool ExpectBytesEqual(const uint8_t *expected, size_t expected_len,
const uint8_t *actual, size_t actual_len);
+ // AtNewInstructionBlock returns true if the current test was immediately
+ // preceded by an instruction block.
+ bool IsAtNewInstructionBlock() const;
+
// HasInstruction returns true if the current test has an instruction.
bool HasInstruction(const std::string &key);
@@ -185,6 +189,8 @@
std::string current_test_;
+ bool is_at_new_instruction_block_ = false;
+
bool ignore_unused_attributes_ = false;
FileTest(const FileTest &) = delete;
diff --git a/fipsoracle/CMakeLists.txt b/fipsoracle/CMakeLists.txt
index 732ba86..683bb99 100644
--- a/fipsoracle/CMakeLists.txt
+++ b/fipsoracle/CMakeLists.txt
@@ -53,7 +53,14 @@
cavp_hmac_test
cavp_hmac_test.cc
- cavp_test_util.h
+ cavp_test_util.cc
+ $<TARGET_OBJECTS:test_support>
+ )
+
+ add_executable(
+ cavp_tdes_test
+
+ cavp_tdes_test.cc
cavp_test_util.cc
$<TARGET_OBJECTS:test_support>
)
@@ -62,7 +69,6 @@
cavp_sha_test
cavp_sha_test.cc
- cavp_test_util.h
cavp_test_util.cc
$<TARGET_OBJECTS:test_support>
)
@@ -71,7 +77,6 @@
cavp_sha_monte_test
cavp_sha_monte_test.cc
- cavp_test_util.h
cavp_test_util.cc
$<TARGET_OBJECTS:test_support>
)
@@ -87,16 +92,13 @@
target_link_libraries(cavp_aes_test crypto)
target_link_libraries(cavp_aes_gcm_test crypto)
-
+ target_link_libraries(cavp_ctr_drbg_test crypto)
target_link_libraries(cavp_ecdsa2_keypair_test crypto)
target_link_libraries(cavp_ecdsa2_pkv_test crypto)
target_link_libraries(cavp_ecdsa2_siggen_test crypto)
target_link_libraries(cavp_ecdsa2_sigver_test crypto)
-
target_link_libraries(cavp_hmac_test crypto)
-
target_link_libraries(cavp_sha_test crypto)
target_link_libraries(cavp_sha_monte_test crypto)
-
- target_link_libraries(cavp_ctr_drbg_test crypto)
+ target_link_libraries(cavp_tdes_test crypto)
endif()
diff --git a/fipsoracle/cavp_tdes_test.cc b/fipsoracle/cavp_tdes_test.cc
new file mode 100644
index 0000000..31e850c
--- /dev/null
+++ b/fipsoracle/cavp_tdes_test.cc
@@ -0,0 +1,164 @@
+/* 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_tdes_test processes a NIST TMOVS test vector request file and emits the
+// corresponding response. An optional sample vector file can be passed to
+// verify the result.
+
+#include <stdlib.h>
+
+#include <openssl/cipher.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+
+#include "../crypto/test/file_test.h"
+#include "cavp_test_util.h"
+
+
+struct TestCtx {
+ const EVP_CIPHER *cipher;
+ std::unique_ptr<FileTest> response_sample;
+ enum Mode {
+ kKAT, // Known Answer Test
+ kMCT, // Monte Carlo Test
+ kMMT, // Multi Message Test
+ };
+ bool has_iv;
+ Mode mode;
+};
+
+static bool TestKAT(FileTest *t, void *arg) {
+ TestCtx *ctx = reinterpret_cast<TestCtx *>(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<uint8_t> key, iv, in, result;
+ const std::string op_label = operation == kEncrypt ? "PLAINTEXT" : "CIPHERTEXT";
+ if (!t->GetAttribute(&count, "COUNT") ||
+ !t->GetBytes(&key, "KEYs") ||
+ (ctx->has_iv && !t->GetBytes(&iv, "IV")) ||
+ !t->GetBytes(&in, op_label)) {
+ return false;
+ }
+ std::vector<uint8_t> triple_key(key);
+ triple_key.insert(triple_key.end(), key.begin(), key.end());
+ triple_key.insert(triple_key.end(), key.begin(), key.end());
+
+ const EVP_CIPHER *cipher = ctx->cipher;
+
+ if (!CipherOperation(cipher, &result, operation == kEncrypt, triple_key, iv,
+ in)) {
+ return false;
+ }
+ const std::string result_label =
+ operation == kEncrypt ? "CIPHERTEXT" : "PLAINTEXT";
+
+ // TDES fax files output format differs from its input format, so we
+ // construct it manually rather than printing CurrentTestToString().
+ if (t->IsAtNewInstructionBlock()) {
+ std::string header = operation == kEncrypt ? "[ENCRYPT]" : "[DECRYPT]";
+ printf("%s\r\n", header.c_str());
+ }
+ printf("COUNT = %s\r\nKEYs = %s\r\n", count.c_str(),
+ 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%s = %s\r\n\r\n", op_label.c_str(),
+ EncodeHex(in.data(), in.size()).c_str(), 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<uint8_t> expected_result;
+ if (!ctx->response_sample->GetAttribute(&expected_count, "COUNT") ||
+ count != expected_count ||
+ (!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;
+ }
+ }
+
+ return true;
+}
+
+static int usage(char *arg) {
+ fprintf(
+ stderr,
+ "usage: %s (kat|mct|mmt) <cipher> <test file> [<sample response file>]\n",
+ arg);
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ CRYPTO_library_init();
+
+ 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 == "mmt") {
+ test_mode = TestCtx::kMMT;
+ } 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;
+ }
+ bool has_iv = cipher_name != "des-ede3";
+ 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");
+
+ // TODO(martinkr): Add MMT, MCT.
+ return FileTestMainSilent(TestKAT, &ctx, argv[3]);
+}
diff --git a/fipsoracle/run_cavp.go b/fipsoracle/run_cavp.go
index 9ff81e7..6ef4dab 100644
--- a/fipsoracle/run_cavp.go
+++ b/fipsoracle/run_cavp.go
@@ -167,6 +167,31 @@
[]test{{"CTR_DRBG", nil, false}},
}
+var tdesTests = testSuite{
+ "TDES",
+ "cavp_tdes_test",
+ []test{
+ // {"TCBCMMT2", []string{"mmt"}, false},
+ // {"TCBCMMT3", []string{"mmt"}, false},
+ // {"TCBCMonte2", []string{"mct"}, false},
+ // {"TCBCMonte3", []string{"mct"}, false},
+ {"TCBCinvperm", []string{"kat", "des-ede3-cbc"}, false},
+ {"TCBCpermop", []string{"kat", "des-ede3-cbc"}, false},
+ {"TCBCsubtab", []string{"kat", "des-ede3-cbc"}, false},
+ {"TCBCvarkey", []string{"kat", "des-ede3-cbc"}, false},
+ {"TCBCvartext", []string{"kat", "des-ede3-cbc"}, false},
+ // {"TECBMMT2", []string{"mmt"}, false},
+ // {"TECBMMT3", []string{"mmt"}, false},
+ // {"TECBMonte2", []string{"mct"}, false},
+ // {"TECBMonte3", []string{"mct"}, false},
+ {"TECBinvperm", []string{"kat", "des-ede3"}, false},
+ {"TECBpermop", []string{"kat", "des-ede3"}, false},
+ {"TECBsubtab", []string{"kat", "des-ede3"}, false},
+ {"TECBvarkey", []string{"kat", "des-ede3"}, false},
+ {"TECBvartext", []string{"kat", "des-ede3"}, false},
+ },
+}
+
var allTestSuites = []*testSuite{
&aesGCMTests,
&aesTests,
@@ -178,6 +203,7 @@
&hmacTests,
&shaTests,
&shaMonteTests,
+ &tdesTests,
}
func main() {
@@ -224,7 +250,7 @@
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
- return fmt.Errorf("cannot run command for %q %q: %s", suite.getDirectory(), test.inFile, err)
+ return fmt.Errorf("cannot run command for %q %q (%s): %s", suite.getDirectory(), test.inFile, strings.Join(append([]string{binary}, args...), " "), err)
}
return nil