Move crypto/fipsoracle/ to the top-level.

This works better with util/generate_build_files.py.

Change-Id: Icb55dc74e0a004aca3e09978640455b66f0473ff
Reviewed-on: https://boringssl-review.googlesource.com/15648
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/fipsoracle/CMakeLists.txt b/fipsoracle/CMakeLists.txt
new file mode 100644
index 0000000..04634ac
--- /dev/null
+++ b/fipsoracle/CMakeLists.txt
@@ -0,0 +1,62 @@
+include_directories(../include)
+
+if (FIPS)
+  add_executable(
+    cavp_aes_test
+
+    cavp_aes_test.cc
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  add_executable(
+    cavp_aes_gcm_test
+
+    cavp_aes_gcm_test.cc
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  add_executable(
+    cavp_ecdsa2_pkv_test
+
+    cavp_ecdsa2_pkv_test.cc
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  add_executable(
+    cavp_ecdsa2_sigver_test
+
+    cavp_ecdsa2_sigver_test.cc
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  add_executable(
+    cavp_sha_test
+
+    cavp_sha_test.cc
+    cavp_test_util.h
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  add_executable(
+    cavp_sha_monte_test
+
+    cavp_sha_monte_test.cc
+    cavp_test_util.h
+    cavp_test_util.cc
+    $<TARGET_OBJECTS:test_support>
+  )
+
+  target_link_libraries(cavp_aes_test crypto)
+  target_link_libraries(cavp_aes_gcm_test crypto)
+
+  target_link_libraries(cavp_ecdsa2_pkv_test crypto)
+  target_link_libraries(cavp_ecdsa2_sigver_test crypto)
+
+  target_link_libraries(cavp_sha_test crypto)
+  target_link_libraries(cavp_sha_monte_test crypto)
+endif()
diff --git a/fipsoracle/cavp_aes_gcm_test.cc b/fipsoracle/cavp_aes_gcm_test.cc
new file mode 100644
index 0000000..9871b73
--- /dev/null
+++ b/fipsoracle/cavp_aes_gcm_test.cc
@@ -0,0 +1,211 @@
+/* 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_gcm_test processes a NIST CAVP AES GCM 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/aead.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_AEAD *aead;
+  std::unique_ptr<FileTest> response_sample;
+};
+
+static const EVP_AEAD *GetAEAD(const std::string &name, const bool enc) {
+  if (name == "aes-128-gcm") {
+    return EVP_aead_aes_128_gcm_fips_testonly();
+  } else if (name == "aes-256-gcm") {
+    return EVP_aead_aes_256_gcm_fips_testonly();
+  }
+  return nullptr;
+}
+
+static bool TestAEADEncrypt(FileTest *t, void *arg) {
+  TestCtx *ctx = reinterpret_cast<TestCtx *>(arg);
+
+  std::string key_len_str, iv_len_str, pt_len_str, aad_len_str, tag_len_str;
+  if (!t->GetInstruction(&key_len_str, "Keylen") ||
+      !t->GetInstruction(&iv_len_str, "IVlen") ||
+      !t->GetInstruction(&pt_len_str, "PTlen") ||
+      !t->GetInstruction(&aad_len_str, "AADlen") ||
+      !t->GetInstruction(&tag_len_str, "Taglen")) {
+    return false;
+  }
+
+  std::string count;
+  std::vector<uint8_t> key, iv, pt, aad, tag, ct;
+  if (!t->GetAttribute(&count, "Count") ||
+      !t->GetBytes(&key, "Key") ||
+      !t->GetBytes(&aad, "AAD") ||
+      !t->GetBytes(&pt, "PT") ||
+      key.size() * 8 != strtoul(key_len_str.c_str(), nullptr, 0) ||
+      pt.size() * 8 != strtoul(pt_len_str.c_str(), nullptr, 0) ||
+      aad.size() * 8 != strtoul(aad_len_str.c_str(), nullptr, 0)) {
+    return false;
+  }
+
+  size_t tag_len = strtoul(tag_len_str.c_str(), nullptr, 0) / 8;
+  if (!AEADEncrypt(ctx->aead, &ct, &tag, tag_len, key, pt, aad, &iv)) {
+    return false;
+  }
+  printf("%s", t->CurrentTestToString().c_str());
+  printf("IV = %s\r\n", EncodeHex(iv.data(), iv.size()).c_str());
+  printf("CT = %s\r\n", EncodeHex(ct.data(), ct.size()).c_str());
+  printf("Tag = %s\r\n\r\n", EncodeHex(tag.data(), tag.size()).c_str());
+
+  // Check if sample response file matches.
+  if (ctx->response_sample) {
+    ctx->response_sample->ReadNext();
+    std::string expected_count;
+    std::vector<uint8_t> expected_iv, expected_ct, expected_tag;
+    if (!ctx->response_sample->GetAttribute(&expected_count, "Count") ||
+        count != expected_count ||
+        !ctx->response_sample->GetBytes(&expected_iv, "IV") ||
+        !t->ExpectBytesEqual(expected_iv.data(), expected_iv.size(), iv.data(),
+                             iv.size()) ||
+        !ctx->response_sample->GetBytes(&expected_ct, "CT") ||
+        !t->ExpectBytesEqual(expected_ct.data(), expected_ct.size(), ct.data(),
+                             ct.size()) ||
+        !ctx->response_sample->GetBytes(&expected_tag, "Tag") ||
+        !t->ExpectBytesEqual(expected_tag.data(), expected_tag.size(),
+                             tag.data(), tag.size())) {
+      t->PrintLine("result doesn't match");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool TestAEADDecrypt(FileTest *t, void *arg) {
+  TestCtx *ctx = reinterpret_cast<TestCtx *>(arg);
+
+  std::string key_len, iv_len, pt_len_str, aad_len_str, tag_len;
+  if (!t->GetInstruction(&key_len, "Keylen") ||
+      !t->GetInstruction(&iv_len, "IVlen") ||
+      !t->GetInstruction(&pt_len_str, "PTlen") ||
+      !t->GetInstruction(&aad_len_str, "AADlen") ||
+      !t->GetInstruction(&tag_len, "Taglen")) {
+    t->PrintLine("Invalid instruction block.");
+    return false;
+  }
+  size_t aad_len = strtoul(aad_len_str.c_str(), nullptr, 0) / 8;
+  size_t pt_len = strtoul(pt_len_str.c_str(), nullptr, 0) / 8;
+
+  std::string count;
+  std::vector<uint8_t> key, iv, ct, aad, tag, pt;
+  if (!t->GetAttribute(&count, "Count") ||
+      !t->GetBytes(&key, "Key") ||
+      !t->GetBytes(&aad, "AAD") ||
+      !t->GetBytes(&tag, "Tag") ||
+      !t->GetBytes(&iv, "IV") ||
+      !t->GetBytes(&ct, "CT") ||
+      key.size() * 8 != strtoul(key_len.c_str(), nullptr, 0) ||
+      iv.size() * 8 != strtoul(iv_len.c_str(), nullptr, 0) ||
+      ct.size() != pt_len ||
+      aad.size() != aad_len ||
+      tag.size() * 8 != strtoul(tag_len.c_str(), nullptr, 0)) {
+    t->PrintLine("Invalid test case");
+    return false;
+  }
+
+  printf("%s", t->CurrentTestToString().c_str());
+  bool aead_result =
+      AEADDecrypt(ctx->aead, &pt, &aad, pt_len, aad_len, key, ct, tag, iv);
+  if (aead_result) {
+    printf("PT = %s\r\n\r\n", EncodeHex(pt.data(), pt.size()).c_str());
+  } else {
+    printf("FAIL\r\n\r\n");
+  }
+
+  // Check if sample response file matches.
+  if (ctx->response_sample) {
+    ctx->response_sample->ReadNext();
+    std::string expected_count;
+    std::vector<uint8_t> expected_pt;
+    if (!ctx->response_sample->GetAttribute(&expected_count, "Count") ||
+        count != expected_count ||
+        (!aead_result && (ctx->response_sample->HasAttribute("PT") ||
+                          !ctx->response_sample->HasAttribute("FAIL"))) ||
+        (aead_result &&
+         (ctx->response_sample->HasAttribute("FAIL") ||
+          !ctx->response_sample->GetBytes(&expected_pt, "PT") ||
+          !t->ExpectBytesEqual(expected_pt.data(), expected_pt.size(),
+                               pt.data(), pt.size())))) {
+      t->PrintLine("result doesn't match");
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static int usage(char *arg) {
+  fprintf(stderr,
+          "usage: %s (enc|dec) <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 mode(argv[1]);
+  bool (*test_fn)(FileTest * t, void *arg);
+  if (mode == "enc") {
+    test_fn = &TestAEADEncrypt;
+  } else if (mode == "dec") {
+    test_fn = &TestAEADDecrypt;
+  } else {
+    return usage(argv[0]);
+  }
+
+  const EVP_AEAD *aead = GetAEAD(argv[2], mode == "enc");
+  if (aead == nullptr) {
+    fprintf(stderr, "invalid aead: %s\n", argv[2]);
+    return 1;
+  }
+
+  TestCtx ctx = {aead, nullptr};
+
+  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("\n\n");
+
+  return FileTestMainSilent(test_fn, &ctx, argv[3]);
+}
diff --git a/fipsoracle/cavp_aes_test.cc b/fipsoracle/cavp_aes_test.cc
new file mode 100644
index 0000000..c341e85
--- /dev/null
+++ b/fipsoracle/cavp_aes_test.cc
@@ -0,0 +1,280 @@
+/* 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 <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;
+  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<uint8_t> *out,
+                       bool encrypt, std::vector<uint8_t> key,
+                       std::vector<uint8_t> iv, std::vector<uint8_t> in) {
+  const std::string in_label = encrypt ? "PLAINTEXT" : "CIPHERTEXT",
+                    result_label = encrypt ? "CIPHERTEXT" : "PLAINTEXT";
+  std::vector<uint8_t> 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<uint8_t> 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<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;
+  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<uint8_t> 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) <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 == "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]);
+}
diff --git a/fipsoracle/cavp_ecdsa2_pkv_test.cc b/fipsoracle/cavp_ecdsa2_pkv_test.cc
new file mode 100644
index 0000000..74c9da1
--- /dev/null
+++ b/fipsoracle/cavp_ecdsa2_pkv_test.cc
@@ -0,0 +1,70 @@
+/* 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_ecdsa2_pkv_test processes a NIST CAVP ECDSA2 PKV test vector request file
+// and emits the corresponding response. An optional sample vector file can be
+// passed to verify the result.
+
+#include <vector>
+
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec_key.h>
+#include <openssl/err.h>
+#include <openssl/nid.h>
+
+#include "../crypto/test/file_test.h"
+#include "cavp_test_util.h"
+
+
+static bool TestECDSA2PKV(FileTest *t, void *arg) {
+  int nid = GetECGroupNIDFromInstruction(t);
+  if (nid == NID_undef) {
+    return false;
+  }
+  bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(nid));
+  bssl::UniquePtr<BIGNUM> qx = GetBIGNUM(t, "Qx");
+  bssl::UniquePtr<BIGNUM> qy = GetBIGNUM(t, "Qy");
+  if (!key || !qx || !qy) {
+    return false;
+  }
+
+  if (EC_KEY_set_public_key_affine_coordinates(key.get(), qx.get(), qy.get())) {
+    printf("%sResult = P\r\n\r\n", t->CurrentTestToString().c_str());
+  } else {
+    char buf[256];
+    ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+    printf("%sResult = F (%s)\r\n\r\n", t->CurrentTestToString().c_str(), buf);
+  }
+  ERR_clear_error();
+  return true;
+}
+
+int main(int argc, char **argv) {
+  CRYPTO_library_init();
+
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s <test file>\n",
+            argv[0]);
+    return 1;
+  }
+
+  printf("# Generated by");
+  for (int i = 0; i < argc; i++) {
+    printf(" %s", argv[i]);
+  }
+  printf("\r\n\r\n");
+
+  return FileTestMainSilent(TestECDSA2PKV, nullptr, argv[1]);
+}
diff --git a/fipsoracle/cavp_ecdsa2_sigver_test.cc b/fipsoracle/cavp_ecdsa2_sigver_test.cc
new file mode 100644
index 0000000..9401854
--- /dev/null
+++ b/fipsoracle/cavp_ecdsa2_sigver_test.cc
@@ -0,0 +1,88 @@
+/* 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_ecdsa2_sigver_test processes a NIST CAVP ECDSA2 SigVer test vector
+// request file and emits the corresponding response. An optional sample vector
+// file can be passed to verify the result.
+
+#include <vector>
+
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/digest.h>
+#include <openssl/ec_key.h>
+#include <openssl/ecdsa.h>
+#include <openssl/err.h>
+#include <openssl/nid.h>
+
+#include "../crypto/test/file_test.h"
+#include "cavp_test_util.h"
+
+
+static bool TestECDSA2SigVer(FileTest *t, void *arg) {
+  int nid = GetECGroupNIDFromInstruction(t);
+  const EVP_MD *md = GetDigestFromInstruction(t);
+  if (nid == NID_undef || md == nullptr) {
+    return false;
+  }
+  bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
+  bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(nid));
+  bssl::UniquePtr<BIGNUM> qx = GetBIGNUM(t, "Qx");
+  bssl::UniquePtr<BIGNUM> qy = GetBIGNUM(t, "Qy");
+  bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
+  bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
+  std::vector<uint8_t> msg;
+  uint8_t digest[EVP_MAX_MD_SIZE];
+  unsigned digest_len;
+  if (!sig || !key || !qx || !qy || !r || !s ||
+      !EC_KEY_set_public_key_affine_coordinates(key.get(), qx.get(),
+                                                qy.get()) ||
+      !t->GetBytes(&msg, "Msg") ||
+      !EVP_Digest(msg.data(), msg.size(), digest, &digest_len, md, nullptr)) {
+    return false;
+  }
+
+  BN_free(sig->r);
+  sig->r = r.release();
+  BN_free(sig->s);
+  sig->s = s.release();
+
+  if (ECDSA_do_verify(digest, digest_len, sig.get(), key.get())) {
+    printf("%sResult = P\r\n\r\n", t->CurrentTestToString().c_str());
+  } else {
+    char buf[256];
+    ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
+    printf("%sResult = F (%s)\r\n\r\n", t->CurrentTestToString().c_str(), buf);
+  }
+  ERR_clear_error();
+  return true;
+}
+
+int main(int argc, char **argv) {
+  CRYPTO_library_init();
+
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s <test file>\n",
+            argv[0]);
+    return 1;
+  }
+
+  printf("# Generated by");
+  for (int i = 0; i < argc; i++) {
+    printf(" %s", argv[i]);
+  }
+  printf("\r\n\r\n");
+
+  return FileTestMainSilent(TestECDSA2SigVer, nullptr, argv[1]);
+}
diff --git a/fipsoracle/cavp_sha_monte_test.cc b/fipsoracle/cavp_sha_monte_test.cc
new file mode 100644
index 0000000..47b885e
--- /dev/null
+++ b/fipsoracle/cavp_sha_monte_test.cc
@@ -0,0 +1,101 @@
+/* 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_sha_monte_test processes a NIST CAVP SHA-Monte 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/crypto.h>
+#include <openssl/digest.h>
+
+#include "../crypto/test/file_test.h"
+#include "cavp_test_util.h"
+
+
+struct TestCtx {
+  std::string hash;
+};
+
+static bool TestSHAMonte(FileTest *t, void *arg) {
+  TestCtx *ctx = reinterpret_cast<TestCtx *>(arg);
+
+  const EVP_MD *md = EVP_get_digestbyname(ctx->hash.c_str());
+  if (md == nullptr) {
+    return false;
+  }
+  const size_t md_len = EVP_MD_size(md);
+
+  std::string out_len;
+  if (!t->GetInstruction(&out_len, "L") ||
+      md_len != strtoul(out_len.c_str(), nullptr, 0)) {
+    return false;
+  }
+
+  std::vector<uint8_t> seed;
+  if (!t->GetBytes(&seed, "Seed") ||
+      seed.size() != md_len) {
+    return false;
+  }
+
+  std::vector<uint8_t> out = seed;
+
+  printf("%s\r\n", t->CurrentTestToString().c_str());
+
+  for (int count = 0; count < 100; count++) {
+    std::vector<uint8_t> msg;
+    msg.insert(msg.end(), out.begin(), out.end());
+    msg.insert(msg.end(), out.begin(), out.end());
+    msg.insert(msg.end(), out.begin(), out.end());
+    for (int i = 0; i < 1000; i++) {
+      unsigned digest_len;
+      if (!EVP_Digest(msg.data(), msg.size(), out.data(), &digest_len, md,
+                      nullptr) ||
+          digest_len != out.size()) {
+        return false;
+      }
+
+      msg.erase(msg.begin(), msg.begin() + out.size());
+      msg.insert(msg.end(), out.begin(), out.end());
+    }
+    printf("COUNT = %d\r\n", count);
+    printf("MD = %s\r\n\r\n", EncodeHex(out.data(), out.size()).c_str());
+  }
+
+  return true;
+}
+
+static int usage(char *arg) {
+  fprintf(stderr, "usage: %s <hash> <test file>\n", arg);
+  return 1;
+}
+
+int main(int argc, char **argv) {
+  CRYPTO_library_init();
+
+  if (argc != 3) {
+    return usage(argv[0]);
+  }
+
+  TestCtx ctx = {std::string(argv[1])};
+
+  printf("# Generated by");
+  for (int i = 0; i < argc; i++) {
+    printf(" %s", argv[i]);
+  }
+  printf("\r\n\r\n");
+
+  return FileTestMainSilent(TestSHAMonte, &ctx, argv[2]);
+}
diff --git a/fipsoracle/cavp_sha_test.cc b/fipsoracle/cavp_sha_test.cc
new file mode 100644
index 0000000..ca60bb0
--- /dev/null
+++ b/fipsoracle/cavp_sha_test.cc
@@ -0,0 +1,96 @@
+/* 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_sha_test processes a NIST CAVP SHA 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/crypto.h>
+#include <openssl/digest.h>
+
+#include "../crypto/test/file_test.h"
+#include "cavp_test_util.h"
+
+
+struct TestCtx {
+  std::string hash;
+};
+
+static bool TestSHA(FileTest *t, void *arg) {
+  TestCtx *ctx = reinterpret_cast<TestCtx *>(arg);
+
+  const EVP_MD *md = EVP_get_digestbyname(ctx->hash.c_str());
+  if (md == nullptr) {
+    return false;
+  }
+  const size_t md_len = EVP_MD_size(md);
+
+  std::string out_len;
+  if (!t->GetInstruction(&out_len, "L") ||
+      md_len != strtoul(out_len.c_str(), nullptr, 0)) {
+    return false;
+  }
+
+  std::string msg_len_str;
+  std::vector<uint8_t> msg;
+  if (!t->GetAttribute(&msg_len_str, "Len") ||
+      !t->GetBytes(&msg, "Msg")) {
+    return false;
+  }
+
+  size_t msg_len = strtoul(msg_len_str.c_str(), nullptr, 0);
+  if (msg_len % 8 != 0 ||
+      msg_len / 8 > msg.size()) {
+    return false;
+  }
+  msg_len /= 8;
+
+  std::vector<uint8_t> out;
+  out.resize(md_len);
+  unsigned digest_len;
+  if (!EVP_Digest(msg.data(), msg_len, out.data(), &digest_len, md, nullptr) ||
+      digest_len != out.size()) {
+    return false;
+  }
+
+  printf("%s", t->CurrentTestToString().c_str());
+  printf("MD = %s\r\n\r\n", EncodeHex(out.data(), out.size()).c_str());
+
+  return true;
+}
+
+static int usage(char *arg) {
+  fprintf(stderr, "usage: %s <hash> <test file>\n", arg);
+  return 1;
+}
+
+int main(int argc, char **argv) {
+  CRYPTO_library_init();
+
+  if (argc != 3) {
+    return usage(argv[0]);
+  }
+
+  TestCtx ctx = {std::string(argv[1])};
+
+  printf("# Generated by");
+  for (int i = 0; i < argc; i++) {
+    printf(" %s", argv[i]);
+  }
+  printf("\r\n\r\n");
+
+  return FileTestMainSilent(TestSHA, &ctx, argv[2]);
+}
diff --git a/fipsoracle/cavp_test_util.cc b/fipsoracle/cavp_test_util.cc
new file mode 100644
index 0000000..c3d2cdf
--- /dev/null
+++ b/fipsoracle/cavp_test_util.cc
@@ -0,0 +1,219 @@
+/* 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. */
+
+#include "cavp_test_util.h"
+
+#include <openssl/bn.h>
+#include <openssl/digest.h>
+#include <openssl/ec.h>
+#include <openssl/nid.h>
+
+
+std::string EncodeHex(const uint8_t *in, size_t in_len) {
+  static const char kHexDigits[] = "0123456789abcdef";
+  std::string ret;
+  ret.reserve(in_len * 2);
+  for (size_t i = 0; i < in_len; i++) {
+    ret += kHexDigits[in[i] >> 4];
+    ret += kHexDigits[in[i] & 0xf];
+  }
+  return ret;
+}
+
+const EVP_CIPHER *GetCipher(const std::string &name) {
+  if (name == "des-cbc") {
+    return EVP_des_cbc();
+  } else if (name == "des-ecb") {
+    return EVP_des_ecb();
+  } else if (name == "des-ede") {
+    return EVP_des_ede();
+  } else if (name == "des-ede3") {
+    return EVP_des_ede3();
+  } else if (name == "des-ede-cbc") {
+    return EVP_des_ede_cbc();
+  } else if (name == "des-ede3-cbc") {
+    return EVP_des_ede3_cbc();
+  } else if (name == "rc4") {
+    return EVP_rc4();
+  } else if (name == "aes-128-ecb") {
+    return EVP_aes_128_ecb();
+  } else if (name == "aes-256-ecb") {
+    return EVP_aes_256_ecb();
+  } else if (name == "aes-128-cbc") {
+    return EVP_aes_128_cbc();
+  } else if (name == "aes-128-gcm") {
+    return EVP_aes_128_gcm();
+  } else if (name == "aes-128-ofb") {
+    return EVP_aes_128_ofb();
+  } else if (name == "aes-192-cbc") {
+    return EVP_aes_192_cbc();
+  } else if (name == "aes-192-ctr") {
+    return EVP_aes_192_ctr();
+  } else if (name == "aes-192-ecb") {
+    return EVP_aes_192_ecb();
+  } else if (name == "aes-256-cbc") {
+    return EVP_aes_256_cbc();
+  } else if (name == "aes-128-ctr") {
+    return EVP_aes_128_ctr();
+  } else if (name == "aes-256-ctr") {
+    return EVP_aes_256_ctr();
+  } else if (name == "aes-256-gcm") {
+    return EVP_aes_256_gcm();
+  } else if (name == "aes-256-ofb") {
+    return EVP_aes_256_ofb();
+  }
+  return nullptr;
+}
+
+bool CipherOperation(const EVP_CIPHER *cipher, std::vector<uint8_t> *out,
+                     bool encrypt, const std::vector<uint8_t> &key,
+                     const std::vector<uint8_t> &iv,
+                     const std::vector<uint8_t> &in) {
+  bssl::ScopedEVP_CIPHER_CTX ctx;
+  if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr,
+                         encrypt ? 1 : 0)) {
+    return false;
+  }
+  if (!iv.empty() && iv.size() != EVP_CIPHER_CTX_iv_length(ctx.get())) {
+    return false;
+  }
+
+  int result_len1 = 0, result_len2;
+  *out = std::vector<uint8_t>(in.size());
+  if (!EVP_CIPHER_CTX_set_key_length(ctx.get(), key.size()) ||
+      !EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data(),
+                         -1) ||
+      !EVP_CIPHER_CTX_set_padding(ctx.get(), 0) ||
+      !EVP_CipherUpdate(ctx.get(), out->data(), &result_len1, in.data(),
+                        in.size()) ||
+      !EVP_CipherFinal_ex(ctx.get(), out->data() + result_len1, &result_len2)) {
+    return false;
+  }
+  out->resize(result_len1 + result_len2);
+
+  return true;
+}
+
+bool AEADEncrypt(const EVP_AEAD *aead, std::vector<uint8_t> *ct,
+                 std::vector<uint8_t> *tag, size_t tag_len,
+                 const std::vector<uint8_t> &key,
+                 const std::vector<uint8_t> &pt,
+                 const std::vector<uint8_t> &aad, std::vector<uint8_t> *iv) {
+  bssl::ScopedEVP_AEAD_CTX ctx;
+  if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(),
+                                        tag->size(), evp_aead_seal)) {
+    return false;
+  }
+
+  std::vector<uint8_t> out;
+  out.resize(pt.size() + EVP_AEAD_max_overhead(aead));
+  size_t out_len;
+  if (!EVP_AEAD_CTX_seal(ctx.get(), out.data(), &out_len, out.size(),
+                         nullptr /* iv */, 0 /* iv_len */, pt.data(), pt.size(),
+                         aad.data(), aad.size())) {
+    return false;
+  }
+
+  static const size_t iv_len = EVP_AEAD_nonce_length(aead);
+  iv->assign(out.begin(), out.begin() + iv_len);
+  ct->assign(out.begin() + iv_len, out.end() - tag_len);
+  tag->assign(out.end() - tag_len, out.end());
+
+  return true;
+}
+
+bool AEADDecrypt(const EVP_AEAD *aead, std::vector<uint8_t> *pt,
+                 std::vector<uint8_t> *aad, size_t pt_len, size_t aad_len,
+                 const std::vector<uint8_t> &key,
+                 const std::vector<uint8_t> &ct,
+                 const std::vector<uint8_t> &tag, std::vector<uint8_t> &iv) {
+  bssl::ScopedEVP_AEAD_CTX ctx;
+  if (!EVP_AEAD_CTX_init_with_direction(ctx.get(), aead, key.data(), key.size(),
+                                        tag.size(), evp_aead_open)) {
+    return false;
+  }
+  std::vector<uint8_t> in = iv;
+  in.reserve(in.size() + ct.size() + tag.size());
+  in.insert(in.end(), ct.begin(), ct.end());
+  in.insert(in.end(), tag.begin(), tag.end());
+
+  pt->resize(pt_len);
+  aad->resize(aad_len);
+  size_t out_pt_len;
+  if (!EVP_AEAD_CTX_open(ctx.get(), pt->data(), &out_pt_len, pt->size(),
+                         nullptr /* iv */, 0 /* iv_len */, in.data(), in.size(),
+                         aad->data(), aad->size()) ||
+      out_pt_len != pt_len) {
+    return false;
+  }
+  return true;
+}
+
+static int HexToBIGNUM(bssl::UniquePtr<BIGNUM> *out, const char *in) {
+  BIGNUM *raw = NULL;
+  int ret = BN_hex2bn(&raw, in);
+  out->reset(raw);
+  return ret;
+}
+
+bssl::UniquePtr<BIGNUM> GetBIGNUM(FileTest *t, const char *attribute) {
+  std::string hex;
+  if (!t->GetAttribute(&hex, attribute)) {
+    return nullptr;
+  }
+
+  bssl::UniquePtr<BIGNUM> ret;
+  if (HexToBIGNUM(&ret, hex.c_str()) != static_cast<int>(hex.size())) {
+    t->PrintLine("Could not decode '%s'.", hex.c_str());
+    return nullptr;
+  }
+  return ret;
+}
+
+int GetECGroupNIDFromInstruction(FileTest *t) {
+  if (t->HasInstruction("P-224")) {
+    return NID_secp224r1;
+  }
+  if (t->HasInstruction("P-256")) {
+    return NID_X9_62_prime256v1;
+  }
+  if (t->HasInstruction("P-384")) {
+    return NID_secp384r1;
+  }
+  if (t->HasInstruction("P-521")) {
+    return NID_secp521r1;
+  }
+  t->PrintLine("No supported group specified.");
+  return NID_undef;
+}
+
+const EVP_MD *GetDigestFromInstruction(FileTest *t) {
+  if (t->HasInstruction("SHA-1")) {
+    return EVP_sha1();
+  }
+  if (t->HasInstruction("SHA-224")) {
+    return EVP_sha224();
+  }
+  if (t->HasInstruction("SHA-256")) {
+    return EVP_sha256();
+  }
+  if (t->HasInstruction("SHA-384")) {
+    return EVP_sha384();
+  }
+  if (t->HasInstruction("SHA-512")) {
+    return EVP_sha512();
+  }
+  t->PrintLine("No supported digest function specified.");
+  return nullptr;
+}
diff --git a/fipsoracle/cavp_test_util.h b/fipsoracle/cavp_test_util.h
new file mode 100644
index 0000000..44bb496
--- /dev/null
+++ b/fipsoracle/cavp_test_util.h
@@ -0,0 +1,56 @@
+/* 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. */
+
+#ifndef OPENSSL_HEADER_CRYPTO_FIPSMODULE_CAVP_TEST_UTIL_H
+#define OPENSSL_HEADER_CRYPTO_FIPSMODULE_CAVP_TEST_UTIL_H
+
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#include <openssl/aead.h>
+#include <openssl/cipher.h>
+
+#include "../crypto/test/file_test.h"
+
+
+std::string EncodeHex(const uint8_t *in, size_t in_len);
+
+const EVP_CIPHER *GetCipher(const std::string &name);
+
+bool CipherOperation(const EVP_CIPHER *cipher, std::vector<uint8_t> *out,
+                     bool encrypt, const std::vector<uint8_t> &key,
+                     const std::vector<uint8_t> &iv,
+                     const std::vector<uint8_t> &in);
+
+bool AEADEncrypt(const EVP_AEAD *aead, std::vector<uint8_t> *ct,
+                 std::vector<uint8_t> *tag, size_t tag_len,
+                 const std::vector<uint8_t> &key,
+                 const std::vector<uint8_t> &pt,
+                 const std::vector<uint8_t> &aad, std::vector<uint8_t> *iv);
+
+bool AEADDecrypt(const EVP_AEAD *aead, std::vector<uint8_t> *pt,
+                 std::vector<uint8_t> *aad, size_t pt_len, size_t aad_len,
+                 const std::vector<uint8_t> &key,
+                 const std::vector<uint8_t> &ct,
+                 const std::vector<uint8_t> &tag, std::vector<uint8_t> &iv);
+
+bssl::UniquePtr<BIGNUM> GetBIGNUM(FileTest *t, const char *attribute);
+
+int GetECGroupNIDFromInstruction(FileTest *t);
+
+const EVP_MD *GetDigestFromInstruction(FileTest *t);
+
+
+#endif  // OPENSSL_HEADER_CRYPTO_FIPSMODULE_CAVP_TEST_UTIL_H
diff --git a/fipsoracle/run_cavp.go b/fipsoracle/run_cavp.go
new file mode 100644
index 0000000..449f1c5
--- /dev/null
+++ b/fipsoracle/run_cavp.go
@@ -0,0 +1,285 @@
+// run_cavp.go processes CAVP input files and generates suitable response
+// files, optionally comparing the results against the provided FAX files.
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+var (
+	binaryDir = flag.String("bin-dir", "", "Directory containing fipsoracle binaries")
+	suiteDir  = flag.String("suite-dir", "", "Base directory containing the CAVP test suite")
+)
+
+// test describes a single request file.
+type test struct {
+	// inFile is the base of the filename without an extension, i.e.
+	// “ECBMCT128”.
+	inFile string
+	// args are the arguments (not including the input filename) to the
+	// oracle binary.
+	args []string
+	// noFAX, if true, indicates that the output cannot be compared against
+	// the FAX file. (E.g. because the primitive is non-deterministic.)
+	noFAX bool
+}
+
+// testSuite describes a series of tests that are handled by a single oracle
+// binary.
+type testSuite struct {
+	// directory is the name of the directory in the CAVP input, i.e. “AES”.
+	directory string
+	// binary is the name of the binary that can process these tests.
+	binary string
+	tests  []test
+}
+
+func (t *testSuite) getDirectory() string {
+	return filepath.Join(*suiteDir, t.directory)
+}
+
+var aesGCMTests = testSuite{
+	"AES_GCM",
+	"cavp_aes_gcm_test",
+	[]test{
+		{"gcmDecrypt128", []string{"dec", "aes-128-gcm"}, false},
+		{"gcmDecrypt256", []string{"dec", "aes-256-gcm"}, false},
+		{"gcmEncryptIntIV128", []string{"enc", "aes-128-gcm"}, true},
+		{"gcmEncryptIntIV256", []string{"enc", "aes-256-gcm"}, true},
+	},
+}
+
+var aesTests = testSuite{
+	"AES",
+	"cavp_aes_test",
+	[]test{
+		{"CBCGFSbox128", []string{"kat", "aes-128-cbc"}, false},
+		{"CBCGFSbox192", []string{"kat", "aes-192-cbc"}, false},
+		{"CBCGFSbox256", []string{"kat", "aes-256-cbc"}, false},
+		{"CBCKeySbox128", []string{"kat", "aes-128-cbc"}, false},
+		{"CBCKeySbox192", []string{"kat", "aes-192-cbc"}, false},
+		{"CBCKeySbox256", []string{"kat", "aes-256-cbc"}, false},
+		{"CBCMMT128", []string{"kat", "aes-128-cbc"}, false},
+		{"CBCMMT192", []string{"kat", "aes-192-cbc"}, false},
+		{"CBCMMT256", []string{"kat", "aes-256-cbc"}, false},
+		{"CBCVarKey128", []string{"kat", "aes-128-cbc"}, false},
+		{"CBCVarKey192", []string{"kat", "aes-192-cbc"}, false},
+		{"CBCVarKey256", []string{"kat", "aes-256-cbc"}, false},
+		{"CBCVarTxt128", []string{"kat", "aes-128-cbc"}, false},
+		{"CBCVarTxt192", []string{"kat", "aes-192-cbc"}, false},
+		{"CBCVarTxt256", []string{"kat", "aes-256-cbc"}, false},
+		{"ECBGFSbox128", []string{"kat", "aes-128-ecb"}, false},
+		{"ECBGFSbox192", []string{"kat", "aes-192-ecb"}, false},
+		{"ECBGFSbox256", []string{"kat", "aes-256-ecb"}, false},
+		{"ECBKeySbox128", []string{"kat", "aes-128-ecb"}, false},
+		{"ECBKeySbox192", []string{"kat", "aes-192-ecb"}, false},
+		{"ECBKeySbox256", []string{"kat", "aes-256-ecb"}, false},
+		{"ECBMMT128", []string{"kat", "aes-128-ecb"}, false},
+		{"ECBMMT192", []string{"kat", "aes-192-ecb"}, false},
+		{"ECBMMT256", []string{"kat", "aes-256-ecb"}, false},
+		{"ECBVarKey128", []string{"kat", "aes-128-ecb"}, false},
+		{"ECBVarKey192", []string{"kat", "aes-192-ecb"}, false},
+		{"ECBVarKey256", []string{"kat", "aes-256-ecb"}, false},
+		{"ECBVarTxt128", []string{"kat", "aes-128-ecb"}, false},
+		{"ECBVarTxt192", []string{"kat", "aes-192-ecb"}, false},
+		{"ECBVarTxt256", []string{"kat", "aes-256-ecb"}, false},
+		// AES Monte-Carlo tests
+		{"ECBMCT128", []string{"mct", "aes-128-ecb"}, false},
+		{"ECBMCT192", []string{"mct", "aes-192-ecb"}, false},
+		{"ECBMCT256", []string{"mct", "aes-256-ecb"}, false},
+		{"CBCMCT128", []string{"mct", "aes-128-cbc"}, false},
+		{"CBCMCT192", []string{"mct", "aes-192-cbc"}, false},
+		{"CBCMCT256", []string{"mct", "aes-256-cbc"}, false},
+	},
+}
+
+var ecdsa2PKVTests = testSuite{
+	"ECDSA2",
+	"cavp_ecdsa2_pkv_test",
+	[]test{{"PKV", nil, false}},
+}
+
+var ecdsa2SigVerTests = testSuite{
+	"ECDSA2",
+	"cavp_ecdsa2_sigver_test",
+	[]test{{"SigVer", nil, false}},
+}
+
+var shaTests = testSuite{
+	"SHA",
+	"cavp_sha_test",
+	[]test{
+		{"SHA1LongMsg", []string{"SHA1"}, false},
+		{"SHA1ShortMsg", []string{"SHA1"}, false},
+		{"SHA224LongMsg", []string{"SHA224"}, false},
+		{"SHA224ShortMsg", []string{"SHA224"}, false},
+		{"SHA256LongMsg", []string{"SHA256"}, false},
+		{"SHA256ShortMsg", []string{"SHA256"}, false},
+		{"SHA384LongMsg", []string{"SHA384"}, false},
+		{"SHA384ShortMsg", []string{"SHA384"}, false},
+		{"SHA512LongMsg", []string{"SHA512"}, false},
+		{"SHA512ShortMsg", []string{"SHA512"}, false},
+	},
+}
+
+var shaMonteTests = testSuite{
+	"SHA",
+	"cavp_sha_monte_test",
+	[]test{
+		{"SHA1Monte", []string{"SHA1"}, false},
+		{"SHA224Monte", []string{"SHA224"}, false},
+		{"SHA256Monte", []string{"SHA256"}, false},
+		{"SHA384Monte", []string{"SHA384"}, false},
+		{"SHA512Monte", []string{"SHA512"}, false},
+	},
+}
+
+var allTestSuites = []*testSuite{
+	&aesGCMTests,
+	&aesTests,
+	&ecdsa2PKVTests,
+	&ecdsa2SigVerTests,
+	&shaTests,
+	&shaMonteTests,
+}
+
+func main() {
+	flag.Parse()
+
+	if len(*binaryDir) == 0 {
+		fmt.Fprintf(os.Stderr, "Must give -bin-dir\n")
+		os.Exit(1)
+	}
+
+	for _, suite := range allTestSuites {
+		for _, test := range suite.tests {
+			if err := doTest(suite, test); err != nil {
+				fmt.Fprintf(os.Stderr, "%s\n", err)
+				os.Exit(2)
+			}
+
+			if !test.noFAX {
+				if err := compareFAX(suite, test); err != nil {
+					fmt.Fprintf(os.Stderr, "%s\n", err)
+					os.Exit(3)
+				}
+			}
+		}
+	}
+}
+
+func doTest(suite *testSuite, test test) error {
+	binary := filepath.Join(*binaryDir, suite.binary)
+
+	var args []string
+	args = append(args, test.args...)
+	args = append(args, filepath.Join(suite.getDirectory(), "req", test.inFile+".req"))
+
+	outPath := filepath.Join(suite.getDirectory(), "resp", test.inFile+".resp")
+	outFile, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
+	if err != nil {
+		return fmt.Errorf("cannot open output file for %q %q: %s", suite.getDirectory(), test.inFile, err)
+	}
+	defer outFile.Close()
+
+	cmd := exec.Command(binary, args...)
+	cmd.Stdout = outFile
+	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 nil
+}
+
+func canonicalizeLine(in string) string {
+	if strings.HasPrefix(in, "Result = P (") {
+		return "Result = P"
+	}
+	if strings.HasPrefix(in, "Result = F (") {
+		return "Result = F"
+	}
+	return in
+}
+
+func compareFAX(suite *testSuite, test test) error {
+	respPath := filepath.Join(suite.getDirectory(), "resp", test.inFile+".resp")
+	respFile, err := os.Open(respPath)
+	if err != nil {
+		return fmt.Errorf("cannot read output of %q %q: %s", suite.getDirectory(), test.inFile, err)
+	}
+	defer respFile.Close()
+
+	faxPath := filepath.Join(suite.getDirectory(), "fax", test.inFile+".fax")
+	faxFile, err := os.Open(faxPath)
+	if err != nil {
+		return fmt.Errorf("cannot open fax file for %q %q: %s", suite.getDirectory(), test.inFile, err)
+	}
+	defer faxFile.Close()
+
+	respScanner := bufio.NewScanner(respFile)
+	faxScanner := bufio.NewScanner(faxFile)
+
+	lineNo := 0
+	inHeader := true
+
+	for respScanner.Scan() {
+		lineNo++
+		respLine := respScanner.Text()
+		var faxLine string
+
+		if inHeader && (len(respLine) == 0 || respLine[0] == '#') {
+			continue
+		}
+
+		for {
+			haveFaxLine := false
+
+			if inHeader {
+				for faxScanner.Scan() {
+					faxLine = faxScanner.Text()
+					if len(faxLine) != 0 && faxLine[0] != '#' {
+						haveFaxLine = true
+						break
+					}
+				}
+
+				inHeader = false
+			} else {
+				if faxScanner.Scan() {
+					faxLine = faxScanner.Text()
+					haveFaxLine = true
+				}
+			}
+
+			if !haveFaxLine {
+				return fmt.Errorf("resp file is longer than fax for %q %q", suite.getDirectory(), test.inFile)
+			}
+
+			if strings.HasPrefix(faxLine, " (Reason: ") {
+				continue
+			}
+
+			break
+		}
+
+		if canonicalizeLine(faxLine) == canonicalizeLine(respLine) {
+			continue
+		}
+
+		return fmt.Errorf("resp and fax differ at line %d for %q %q: %q vs %q", lineNo, suite.getDirectory(), test.inFile, respLine, faxLine)
+	}
+
+	if faxScanner.Scan() {
+		return fmt.Errorf("fax file is longer than resp for %q %q", suite.getDirectory(), test.inFile)
+	}
+
+	return nil
+}