Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to define a callback for FIPS test failures before aborting the process #2162

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ if(BUILD_TESTING)
set(INTEGRATION_TEST_EXEC integration_test.so)
set(DYNAMIC_LOADING_TEST_EXEC dynamic_loading_test.so)
set(RWLOCK_STATIC_INIT_TEST_EXEC rwlock_static_init.so)
set(FIPS_CALLBACK_TEST_EXEC fips_callback_test.so)
else()
set(CRYPTO_TEST_EXEC crypto_test)
set(RANDOM_TEST_EXEC urandom_test)
Expand All @@ -1009,6 +1010,7 @@ if(BUILD_TESTING)
set(INTEGRATION_TEST_EXEC integration_test)
set(DYNAMIC_LOADING_TEST_EXEC dynamic_loading_test)
set(RWLOCK_STATIC_INIT_TEST_EXEC rwlock_static_init)
set(FIPS_CALLBACK_TEST_EXEC fips_callback_test)
endif()

add_subdirectory(util/fipstools/acvp/modulewrapper)
Expand Down
3 changes: 3 additions & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,9 @@ if(BUILD_TESTING)
)
set_test_location(${RANDOM_TEST_EXEC})

# fips_callback_test tests that setting the callback overrides the default abort behavior
add_test_executable(${FIPS_CALLBACK_TEST_EXEC} fips_callback_test.cc)

add_dependencies(${RANDOM_TEST_EXEC} boringssl_prefix_symbols)
target_link_libraries(${RANDOM_TEST_EXEC} test_support_lib boringssl_gtest crypto)
target_include_directories(${RANDOM_TEST_EXEC} BEFORE PRIVATE ${PROJECT_BINARY_DIR}/symbol_prefix_include)
Expand Down
135 changes: 135 additions & 0 deletions crypto/fips_callback_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#if defined(BORINGSSL_FIPS)
#include <gtest/gtest.h>
#include <openssl/crypto.h>
#include <openssl/curve25519.h>
#include <openssl/dh.h>
#include <openssl/ec_key.h>
#include <openssl/evp.h>
#include <openssl/nid.h>
#include <openssl/rsa.h>

#include "internal.h"

extern "C" {
OPENSSL_EXPORT void AWS_LC_fips_failure_callback(const char* message);
}

void AWS_LC_fips_failure_callback(const char* message) {
ASSERT_EQ(1, FIPS_mode());

// TODO update the self test to report the actual test that failed
const std::map<std::string, std::string> kat_failure_messages = {
{"HMAC-SHA-256", "Integrity test failed"},
{"AES-CBC-encrypt", "Self-tests failed"},
{"AES-CBC-decrypt", "Self-tests failed"},
{"AES-GCM-encrypt", "Self-tests failed"},
{"AES-GCM-decrypt", "Self-tests failed"},
{"DRBG", "Self-tests failed"},
{"DRBG-reseed", "Self-tests failed"},
{"SHA-1", "Self-tests failed"},
{"SHA-256", "Integrity test failed"},
{"SHA-512", "Self-tests failed"},
{"TLS-KDF", "Self-tests failed"},
{"RSA-sign", "RSA self-tests failed"},
{"RSA-verify", "RSA self-tests failed"},
{"ECDSA-sign", "ECC self-tests failed"},
{"ECDSA-verify", "ECC self-tests failed"},
{"Z-computation", "ECC self-tests failed"},
{"FFDH", "FFDH self-tests failed"},
{"RSA_PWCT", "RSA PCT failed"},
{"ECDSA_PWCT", "EC PCT failed"}
};

char* broken_kat = getenv("FIPS_CALLBACK_TEST_EXPECTED_FAILURE");
if (broken_kat != nullptr) {
auto expected_message = kat_failure_messages.find(broken_kat);
if (expected_message != kat_failure_messages.end()) {
ASSERT_STREQ(expected_message->second.c_str(), message);
} else {
FAIL() << "Failed to find expected message for FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE=" << broken_kat;
}

} else {
FAIL() << "AWS_LC_fips_failure_callback called when no KAT was expected to be broken";
}
fprintf(stderr, "AWS-LC FIPS callback passed\n");
}

TEST(FIPSCallback, PowerOnSelfTests) {
// Some KATs are lazy and run on first use
bssl::UniquePtr<RSA> rsa(RSA_new());
EXPECT_TRUE(RSA_generate_key_fips(rsa.get(), 2048, nullptr));

bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
EXPECT_TRUE(EC_KEY_generate_key_fips(key.get()));

bssl::UniquePtr<DH> dh(DH_new());
ASSERT_TRUE(DH_generate_parameters_ex(dh.get(), 64, DH_GENERATOR_5, nullptr));
EXPECT_TRUE(DH_generate_key(dh.get()));

bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_KEM, nullptr));
ASSERT_TRUE(ctx);
ASSERT_TRUE(EVP_PKEY_CTX_kem_set_params(ctx.get(), NID_MLKEM512));
ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get()));
EVP_PKEY *raw = nullptr;
ASSERT_TRUE(EVP_PKEY_keygen(ctx.get(), &raw));
OPENSSL_free(raw);

uint8_t public_key[ED25519_PUBLIC_KEY_LEN];
uint8_t private_key[ED25519_PRIVATE_KEY_LEN];
ED25519_keypair(public_key, private_key);

uint8_t message[2];
uint8_t context[2];
uint8_t signature[ED25519_SIGNATURE_LEN];
ED25519ph_sign(signature, message, sizeof(message), private_key, context, sizeof(context));

char* broken_kat = getenv("FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE");
if (broken_kat != nullptr) {
// A kat was supposed to be broken and the self tests should have failed by now
FAIL() << "FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE=" << broken_kat
<< " and should have failed the self tests and aborted before this" <<
"function can run";
} else {
// break-kat.go has not run and corrupted this test yet, everything should work
ASSERT_TRUE(BORINGSSL_self_test());
}
}

TEST(FIPSCallback, RSARuntimeTest) {
// At this point the library has loaded, if a self test was broken
// the process would have aborted already
ASSERT_EQ(1, FIPS_mode());
ASSERT_EQ(1, BORINGSSL_self_test());

char*broken_runtime_test = getenv("BORINGSSL_FIPS_BREAK_TEST");
bssl::UniquePtr<RSA> rsa(RSA_new());
// This should either pass or abort
EXPECT_TRUE(RSA_generate_key_fips(rsa.get(), 2048, nullptr));
if (broken_runtime_test != nullptr && strcmp(broken_runtime_test, "RSA_PWCT" ) == 0) {
FAIL() << "FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE=RSA_PWCT and should have"
" failed the self tests and aborted before here";
}
}

TEST(FIPSCallback, ECDSARuntimeTest) {
// At this point the library has loaded, if a self test was broken
// the process would have aborted already
ASSERT_EQ(1, FIPS_mode());
ASSERT_EQ(1, BORINGSSL_self_test());

char*broken_runtime_test = getenv("BORINGSSL_FIPS_BREAK_TEST");
// This should either pass or abort
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
EXPECT_TRUE(EC_KEY_generate_key_fips(key.get()));
if (broken_runtime_test != nullptr && (strcmp(broken_runtime_test, "ECDSA_PWCT" ) == 0 ||
strcmp(broken_runtime_test, "CRNG" ) == 0)) {
FAIL() << "FIPS_CALLBACK_TEST_POWER_ON_TEST_FAILURE=ECDSA_PWCT and should have"
" failed the self tests and aborted before here";
}
}

#endif
40 changes: 18 additions & 22 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,10 @@ static void assert_within(const void *start, const void *symbol,
if (start_val <= symbol_val && symbol_val < end_val) {
return;
}

fprintf(
stderr,
"FIPS module doesn't span expected symbol (%s). Expected %p <= %p < %p\n",
symbol_name, start, symbol, end);
BORINGSSL_FIPS_abort();
char buffer[256];
snprintf(buffer, sizeof(buffer), "FIPS module doesn't span expected symbol (%s). Expected %p <= %p < %p\n",
symbol_name, start, symbol, end);
AWS_LC_FIPS_failure(buffer);
}

static void assert_not_within(const void *start, const void *symbol,
Expand All @@ -211,12 +209,10 @@ static void assert_not_within(const void *start, const void *symbol,
if (start_val >= symbol_val || symbol_val > end_val) {
return;
}

fprintf(
stderr,
"FIPS module spans unexpected symbol (%s), expected %p < %p || %p > %p\n",
symbol_name, symbol, start, symbol, end);
BORINGSSL_FIPS_abort();
char buffer[256];
snprintf(buffer, sizeof(buffer), "FIPS module spans unexpected symbol (%s), expected %p < %p || %p > %p\n",
symbol_name, symbol, start, symbol, end);
AWS_LC_FIPS_failure(buffer);
}

// TODO: Re-enable once all data has been moved out of .text segments CryptoAlg-2360
Expand Down Expand Up @@ -263,27 +259,21 @@ static void BORINGSSL_bcm_power_on_self_test(void) {

#if defined(FIPS_ENTROPY_SOURCE_JITTER_CPU)
if (jent_entropy_init()) {
fprintf(stderr, "CPU Jitter entropy RNG initialization failed.\n");
goto err;
AWS_LC_FIPS_failure("CPU Jitter entropy RNG initialization failed");
}
#endif

#if !defined(OPENSSL_ASAN)
// Integrity tests cannot run under ASAN because it involves reading the full
// .text section, which triggers the global-buffer overflow detection.
if (!BORINGSSL_integrity_test()) {
goto err;
AWS_LC_FIPS_failure("Integrity test failed");
}
#endif // OPENSSL_ASAN

if (!boringssl_self_test_startup()) {
goto err;
AWS_LC_FIPS_failure("Self-tests failed");
}

return;

err:
BORINGSSL_FIPS_abort();
}

#if !defined(OPENSSL_ASAN)
Expand Down Expand Up @@ -386,7 +376,13 @@ int BORINGSSL_integrity_test(void) {
}
#endif // OPENSSL_ASAN

void BORINGSSL_FIPS_abort(void) {
WEAK_SYMBOL_FUNC(void, AWS_LC_fips_failure_callback, (const char* message))
#include <unistd.h>
void AWS_LC_FIPS_failure(const char* message) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me if I'm wrong, but this will break our next FIPS validation. If we allow someone outside the boundary to register a callback our lab will not be able to prove that we stop processing events when a PCT or self-tests fails. When we detect a failure we need to abort immediately as defined in ISO 19790 section 7.3.3, b), 7.3.3 c), and 7.5. This needs to be wrapped in a #ifdef so we don't expose it when we are building as a 140-3 level 1 module.

if (AWS_LC_fips_failure_callback != NULL) {
AWS_LC_fips_failure_callback(message);
}
fprintf(stderr, "AWS-LC FIPS failure caused by %s\n", message);
for (;;) {
abort();
exit(1);
Expand Down
2 changes: 1 addition & 1 deletion crypto/fipsmodule/curve25519/curve25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ static void ed25519_keypair_pct(uint8_t public_key[ED25519_PUBLIC_KEY_LEN],
uint8_t out_sig[ED25519_SIGNATURE_LEN];
if (ED25519_sign_no_self_test(out_sig, msg, 16, private_key) != 1 ||
ED25519_verify_no_self_test(msg, 16, out_sig, public_key) != 1) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("ED25519 PCT failed");
}
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion crypto/fipsmodule/ec/ec_key.c
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ int EC_KEY_generate_key_fips(EC_KEY *eckey) {
eckey->priv_key = NULL;

#if defined(AWSLC_FIPS)
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("EC PCT failed");
#else
return 0;
#endif
Expand Down
2 changes: 1 addition & 1 deletion crypto/fipsmodule/ml_kem/ml_kem_ref/kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ int crypto_kem_keypair_derand(ml_kem_params *params,
#if defined(AWSLC_FIPS)
// Abort in case of PCT failure.
if (keygen_pct(params, pk, sk)) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("ML-KEM PCT failed");
}
#endif
return 0;
Expand Down
2 changes: 1 addition & 1 deletion crypto/fipsmodule/rsa/rsa_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ static int RSA_generate_key_ex_maybe_fips(RSA *rsa, int bits,
if(check_fips && !RSA_check_fips(tmp)) {
RSA_free(tmp);
#if defined(AWSLC_FIPS)
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("RSA PCT failed");
#endif
return ret;
}
Expand Down
12 changes: 6 additions & 6 deletions crypto/fipsmodule/self_check/self_check.c
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,7 @@ static int boringssl_self_test_hasheddsa(void) {

static void run_self_test_rsa(void) {
if (!boringssl_self_test_rsa()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("RSA self-tests failed");
}
}

Expand All @@ -1624,7 +1624,7 @@ void boringssl_ensure_rsa_self_test(void) {

static void run_self_test_ecc(void) {
if (!boringssl_self_test_ecc()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("ECC self-tests failed");
}
}

Expand All @@ -1636,7 +1636,7 @@ void boringssl_ensure_ecc_self_test(void) {

static void run_self_test_ffdh(void) {
if (!boringssl_self_test_ffdh()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("FFDH self-tests failed");
}
}

Expand All @@ -1648,7 +1648,7 @@ void boringssl_ensure_ffdh_self_test(void) {

static void run_self_test_ml_kem(void) {
if (!boringssl_self_test_ml_kem()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("ML-KEM self-tests failed");
}
}

Expand All @@ -1660,7 +1660,7 @@ void boringssl_ensure_ml_kem_self_test(void) {

static void run_self_test_eddsa(void) {
if (!boringssl_self_test_eddsa()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("EdDSA self-tests failed");
}
}

Expand All @@ -1672,7 +1672,7 @@ void boringssl_ensure_eddsa_self_test(void) {

static void run_self_test_hasheddsa(void) {
if (!boringssl_self_test_hasheddsa()) {
BORINGSSL_FIPS_abort();
AWS_LC_FIPS_failure("Hashed EdDSA self-tests failed");
}
}

Expand Down
21 changes: 16 additions & 5 deletions crypto/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1267,13 +1267,13 @@ static inline uint64_t CRYPTO_subc_u64(uint64_t x, uint64_t y, uint64_t borrow,

#if defined(BORINGSSL_FIPS)

// BORINGSSL_FIPS_abort is called when a FIPS power-on or continuous test
// fails. It prevents any further cryptographic operations by the current
// process.
// AWS_LC_FIPS_failure is called when a FIPS power-on or continuous test
// fails. It optionally calls a debug function to record the error, then
// prevents further cryptographic use.
#if defined(_MSC_VER)
__declspec(noreturn) void BORINGSSL_FIPS_abort(void);
__declspec(noreturn) void AWS_LC_FIPS_error(const char* message);
#else
void BORINGSSL_FIPS_abort(void) __attribute__((noreturn));
void AWS_LC_FIPS_failure(const char* message) __attribute__((noreturn));
#endif

// boringssl_self_test_startup runs all startup self tests and returns one on
Expand Down Expand Up @@ -1409,6 +1409,17 @@ OPENSSL_EXPORT int OPENSSL_vasprintf_internal(char **str, const char *format,
#define GUARD_PTR(ptr) __AWS_LC_ENSURE((ptr) != NULL, OPENSSL_PUT_ERROR(CRYPTO, ERR_R_PASSED_NULL_PARAMETER); \
return AWS_LC_ERROR)


// Windows doesn't really support weak symbols as of May 2019, and Clang on
// Windows will emit strong symbols instead. See
// https://bugs.llvm.org/show_bug.cgi?id=37598
#if defined(__ELF__) && defined(__GNUC__)
#define WEAK_SYMBOL_FUNC(rettype, name, args) \
rettype name args __attribute__((weak));
#else
#define WEAK_SYMBOL_FUNC(rettype, name, args) static rettype(*name) args = NULL;
#endif

#if defined(__cplusplus)
} // extern C
#endif
Expand Down
10 changes: 0 additions & 10 deletions crypto/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,6 @@ static void __asan_poison_memory_region(const void *addr, size_t size) {}
static void __asan_unpoison_memory_region(const void *addr, size_t size) {}
#endif

// Windows doesn't really support weak symbols as of May 2019, and Clang on
// Windows will emit strong symbols instead. See
// https://bugs.llvm.org/show_bug.cgi?id=37598
#if defined(__ELF__) && defined(__GNUC__)
#define WEAK_SYMBOL_FUNC(rettype, name, args) \
rettype name args __attribute__((weak));
#else
#define WEAK_SYMBOL_FUNC(rettype, name, args) static rettype(*name) args = NULL;
#endif

#define AWSLC_FILE ""
#define AWSLC_LINE 0

Expand Down
Loading
Loading