From a12e3e3b3db6be934ae2087ad4c1642748915eee Mon Sep 17 00:00:00 2001 From: Andrew Hopkins Date: Fri, 21 Apr 2023 06:28:57 -0700 Subject: [PATCH] Add RSA and EC FIPS pairwise failure tests with the new callback (#954) * Add RSA and EC FIPS pairwise failure tests with the new callback * PR feedback: add additional snaity tests to PowerOnTests and cleanup other runtime test --- crypto/fips_callback_test.cc | 103 ++++++++++++++++++++-------- tests/ci/run_fips_callback_tests.sh | 4 +- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/crypto/fips_callback_test.cc b/crypto/fips_callback_test.cc index 811642ee6f..aa89232817 100644 --- a/crypto/fips_callback_test.cc +++ b/crypto/fips_callback_test.cc @@ -7,8 +7,10 @@ #if defined(__ELF__) && defined(__GNUC__) #include +#include #include #include +#include #include #include #include @@ -38,62 +40,62 @@ static bool message_in_errors(const string& expected_message) { struct test_config { string expected_failure_message; int initial_failure_count; - int expected_failure_count; }; // If hmac sha-256 is broken the integrity check can not be trusted to check // itself and fails earlier +set integrity_test_names = {"SHA-256", "HMAC-SHA-256"}; const test_config integrity_test_config = { "BORINGSSL_integrity_test", 1, - 2 -}; -vector integrity_tests = {"SHA-256", "HMAC-SHA-256"}; - -// The fast tests run automatically at startup and will report a failure to -// the test callback immediately, and then again when BORINGSSL_self_test is called -const test_config fast_test_config = { - "boringssl_self_test_startup", - 1, - 2 }; // The lazy tests are not run at power up, only when called directly with // BORINGSSL_self_test, therefore the callback should have been called once +set lazy_test_names = {"ECDSA-sign", "ECDSA-verify", "FFDH", "RSA-sign", "RSA-verify", "Z-computation"}; const test_config lazy_test_config = { "BORINGSSL_self_test", 0, - 1 }; -vector lazy_tests = {"ECDSA-sign", "ECDSA-verify", "FFDH", "RSA-sign", "RSA-verify", "Z-computation"}; +// The fast tests run automatically at startup and will report a failure to +// the test callback immediately, and then again when BORINGSSL_self_test is called +const test_config fast_test_config = { + "boringssl_self_test_startup", + 1, +}; + +static test_config get_self_test_failure_config(char* broken_kat) { + if(integrity_test_names.find(broken_kat) != integrity_test_names.end()) { + return integrity_test_config; + } else if (lazy_test_names.find(broken_kat) != lazy_test_names.end()) { + return lazy_test_config; + } else { + return fast_test_config; + } +} TEST(FIPSCallback, PowerOnTests) { + ASSERT_EQ(1, FIPS_mode()); // At this point the library has loaded, if a self test was broken // AWS_LC_FIPS_Callback would have already been called. If this test // wasn't broken the call count should be zero char* broken_kat = getenv("FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE"); if (broken_kat != nullptr) { - struct test_config config; - if(find(integrity_tests.begin(), integrity_tests.end(), broken_kat) != integrity_tests.end()) { - config = integrity_test_config; - } else if (find(lazy_tests.begin(), lazy_tests.end(), broken_kat) != lazy_tests.end()) { - config = lazy_test_config; - } else { - config = fast_test_config; - } + test_config config = get_self_test_failure_config(broken_kat); + // Fast tests will have already run and if they were broken our callback would + // have already been called ASSERT_EQ(config.initial_failure_count, failure_count); - - // Trigger lazy tests to run - ASSERT_EQ(0, BORINGSSL_self_test()); - ASSERT_EQ(config.expected_failure_count, failure_count); + // BORINGSSL_self_test will re-run the fast tests and trigger the lazy tests. + ASSERT_FALSE(BORINGSSL_self_test()); + ASSERT_EQ(config.initial_failure_count + 1, failure_count); ASSERT_TRUE(message_in_errors(config.expected_failure_message)); } else { // break-kat.go has not run and corrupted this test yet, everything should work - ASSERT_EQ(1, BORINGSSL_self_test()); + ASSERT_TRUE(BORINGSSL_self_test()); ASSERT_EQ(0, failure_count); - ASSERT_EQ(1, FIPS_mode()); } + ASSERT_EQ(1, FIPS_mode()); } TEST(FIPSCallback, DRBGRuntime) { @@ -106,15 +108,56 @@ TEST(FIPSCallback, DRBGRuntime) { uint8_t buf[10]; if (broken_runtime_test != nullptr && strcmp(broken_runtime_test, "CRNG" ) == 0) { ASSERT_FALSE(RAND_bytes(buf, sizeof(buf))); - // TODO: make AWS_LC_FIPS_error update a new global state so FIPS_mode returns 0 - ASSERT_EQ(1, FIPS_mode()); ASSERT_EQ(1, failure_count); } else { // BORINGSSL_FIPS_BREAK_TEST has not been set and everything should work ASSERT_TRUE(RAND_bytes(buf, sizeof(buf))); - ASSERT_EQ(1, FIPS_mode()); ASSERT_EQ(0, failure_count); } + ASSERT_EQ(1, FIPS_mode()); +} + +TEST(FIPSCallback, RSARuntimeTest) { + // At this point the library has loaded, if a self test was broken + // AWS_LC_FIPS_Callback would have already been called. If this test + // wasn't broken the call count should be zero + char*broken_runtime_test = getenv("BORINGSSL_FIPS_BREAK_TEST"); + bssl::UniquePtr rsa(RSA_new()); + ASSERT_EQ(0, failure_count); + ASSERT_EQ(1, FIPS_mode()); + if (broken_runtime_test != nullptr && (strcmp(broken_runtime_test, "RSA_PWCT" ) == 0 || + strcmp(broken_runtime_test, "CRNG" ) == 0)) { + ASSERT_FALSE(RSA_generate_key_fips(rsa.get(), 2048, nullptr)); + // RSA key generation can call the DRBG multiple times before failing we + // don't know how many times, but it should fail at least once. + ASSERT_NE(0, failure_count); + } else { + // BORINGSSL_FIPS_BREAK_TEST has not been set and everything should work + ASSERT_TRUE(RSA_generate_key_fips(rsa.get(), 2048, nullptr)); + } + ASSERT_EQ(1, FIPS_mode()); +} + +TEST(FIPSCallback, ECDSARuntimeTest) { + // At this point the library has loaded, if a self test was broken + // AWS_LC_FIPS_Callback would have already been called. If this test + // wasn't broken the call count should be zero + char*broken_runtime_test = getenv("BORINGSSL_FIPS_BREAK_TEST"); + bssl::UniquePtr key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + ASSERT_TRUE(key); + ASSERT_EQ(0, failure_count); + ASSERT_EQ(1, FIPS_mode()); + if (broken_runtime_test != nullptr && (strcmp(broken_runtime_test, "ECDSA_PWCT" ) == 0 || + strcmp(broken_runtime_test, "CRNG" ) == 0)) { + ASSERT_FALSE(EC_KEY_generate_key_fips(key.get())); + // EC key generation can call the DRBG multiple times before failing, we + // don't know how many times, but it should fail at least once. + ASSERT_NE(0, failure_count); + } else { + // BORINGSSL_FIPS_BREAK_TEST has not been set and everything should work + ASSERT_TRUE(EC_KEY_generate_key_fips(key.get())); + } + ASSERT_EQ(1, FIPS_mode()); } #endif diff --git a/tests/ci/run_fips_callback_tests.sh b/tests/ci/run_fips_callback_tests.sh index df0966a40f..53bbc14a2f 100755 --- a/tests/ci/run_fips_callback_tests.sh +++ b/tests/ci/run_fips_callback_tests.sh @@ -29,7 +29,7 @@ for kat in $KATS; do unset FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE done -runtime_tests=("CRNG") +runtime_tests=("RSA_PWCT" "ECDSA_PWCT" "CRNG") for runtime_test in "${runtime_tests[@]}"; do # Tell our test what test is expected to fail export FIPS_CALLBACK_TEST_RUNTIME_TEST_FAILURE="$runtime_test" @@ -38,4 +38,6 @@ for runtime_test in "${runtime_tests[@]}"; do # These tests will have side affects in the future (modifying the global FIPS state) and must be run in separate process $original_test --gtest_filter=FIPSCallback.PowerOnTests $original_test --gtest_filter=FIPSCallback.DRBGRuntime + $original_test --gtest_filter=FIPSCallback.RSARuntimeTest + $original_test --gtest_filter=FIPSCallback.ECDSARuntimeTest done