diff --git a/include/wolfprovider/internal.h b/include/wolfprovider/internal.h
index 0b10515c..78570e72 100644
--- a/include/wolfprovider/internal.h
+++ b/include/wolfprovider/internal.h
@@ -202,6 +202,7 @@ void wp_provctx_unlock_rng(WOLFPROV_CTX* provCtx);
#define WP_CAST_ALGO_COUNT 7
int wp_init_cast_mutexes(void);
+int wp_run_pending_casts(void);
int wp_init_cast(int algo);
diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c
index be3bc696..7442932c 100644
--- a/src/wp_dh_kmgmt.c
+++ b/src/wp_dh_kmgmt.c
@@ -393,6 +393,10 @@ static wp_Dh* wp_dh_new(WOLFPROV_CTX *provCtx)
{
wp_Dh* dh = NULL;
+ /* Every wolfCrypt DH call is made on a key created here, and wolfCrypt
+ * FIPS gates them on the DH primitive-Z CAST: serialize it here. */
+ WP_CHECK_FIPS_ALGO_PTR(WP_CAST_ALGO_DH);
+
if (wolfssl_prov_is_running()) {
dh = (wp_Dh*)OPENSSL_zalloc(sizeof(*dh));
}
diff --git a/src/wp_internal.c b/src/wp_internal.c
index d4bb7e3c..33462b1d 100644
--- a/src/wp_internal.c
+++ b/src/wp_internal.c
@@ -121,6 +121,83 @@ int wp_init_cast_mutexes(void)
return CRYPTO_THREAD_run_once(&castAlgosOnce, wolfprov_init_cast_mutex);
}
+static CRYPTO_ONCE castPendingOnce = CRYPTO_ONCE_STATIC_INIT;
+/** Number of CASTs that failed when the pending CASTs were run. */
+static int castPendingFailed = 0;
+
+/**
+ * Run a CAST self-test unless it has already passed.
+ *
+ * wc_RunCast_fips() re-runs a passed CAST, putting it back in progress; a
+ * thread using the algorithm meanwhile fails, and on FIPS v5 that puts the
+ * whole module in degraded mode.
+ *
+ * @param [in] type CAST identifier (FIPS_CAST_*).
+ * @return 0 on success.
+ * @return Other value on failure.
+ */
+static int wp_run_cast(int type)
+{
+ if (wc_GetCastStatus_fips(type) == FIPS_CAST_STATE_SUCCESS) {
+ return 0;
+ }
+ return wc_RunCast_fips(type);
+}
+
+/**
+ * Run every CAST self-test that has not run yet, except the DH and ECC
+ * primitive-Z ones.
+ *
+ * Run once, through wp_run_pending_casts().
+ */
+static void wolfprov_run_pending_casts(void)
+{
+ int i;
+ int state;
+
+ for (i = 0; i < FIPS_CAST_COUNT; i++) {
+ /* Left to run lazily (the slowest CASTs, #351), serialized by
+ * wp_init_cast() on every path that reaches them. */
+ if ((i == FIPS_CAST_DH_PRIMITIVE_Z) ||
+ (i == FIPS_CAST_ECC_PRIMITIVE_Z)) {
+ continue;
+ }
+ state = wc_GetCastStatus_fips(i);
+ if (state == FIPS_CAST_STATE_INIT) {
+ if (wc_RunCast_fips(i) != 0) {
+ castPendingFailed++;
+ }
+ }
+ else if (state == FIPS_CAST_STATE_FAILURE) {
+ castPendingFailed++;
+ }
+ }
+}
+
+/**
+ * Run, once per process, the CAST self-tests that have not run yet.
+ *
+ * wolfCrypt runs a CAST on first use of its algorithm. Two threads making
+ * that first use at once race on the CAST state, and on FIPS v5 the loser
+ * puts the module in degraded mode, so all crypto of the process fails.
+ * wp_init_cast() serializes the CASTs it runs, but not the ones wolfCrypt
+ * runs inside other algorithms (TLS 1.2, TLS 1.3 and SSH KDF, and
+ * dependencies across algorithm groups). Call before the provider is usable
+ * so that only the DH and ECC primitive-Z CASTs are left to run lazily, each
+ * through wp_init_cast() before any wolfCrypt call gated on it. CASTs that
+ * passed are not re-run.
+ *
+ * @return 1 when no CAST failed.
+ * @return 0 on failure.
+ */
+int wp_run_pending_casts(void)
+{
+ if (!CRYPTO_THREAD_run_once(&castPendingOnce, wolfprov_run_pending_casts)) {
+ return 0;
+ }
+ return castPendingFailed == 0;
+}
+
/**
* Initialize a CAST self-test for a specific algorithm.
*
@@ -153,54 +230,54 @@ int wp_init_cast(int algo)
switch (algo) {
#ifdef WP_HAVE_AES
case WP_CAST_ALGO_AES:
- if (wc_RunCast_fips(FIPS_CAST_AES_CBC) != 0 ||
- wc_RunCast_fips(FIPS_CAST_AES_GCM) != 0) {
+ if (wp_run_cast(FIPS_CAST_AES_CBC) != 0 ||
+ wp_run_cast(FIPS_CAST_AES_GCM) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_HMAC
case WP_CAST_ALGO_HMAC:
- if (wc_RunCast_fips(FIPS_CAST_HMAC_SHA1) != 0 ||
- wc_RunCast_fips(FIPS_CAST_HMAC_SHA2_256) != 0 ||
- wc_RunCast_fips(FIPS_CAST_HMAC_SHA2_512) != 0 ||
- wc_RunCast_fips(FIPS_CAST_HMAC_SHA3_256) != 0) {
+ if (wp_run_cast(FIPS_CAST_HMAC_SHA1) != 0 ||
+ wp_run_cast(FIPS_CAST_HMAC_SHA2_256) != 0 ||
+ wp_run_cast(FIPS_CAST_HMAC_SHA2_512) != 0 ||
+ wp_run_cast(FIPS_CAST_HMAC_SHA3_256) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_RSA
case WP_CAST_ALGO_RSA:
- if (wc_RunCast_fips(FIPS_CAST_RSA_SIGN_PKCS1v15) != 0) {
+ if (wp_run_cast(FIPS_CAST_RSA_SIGN_PKCS1v15) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_ECDSA
case WP_CAST_ALGO_ECDSA:
- if (wc_RunCast_fips(FIPS_CAST_ECDSA) != 0) {
+ if (wp_run_cast(FIPS_CAST_ECDSA) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_ECDH
case WP_CAST_ALGO_ECDH:
- if (wc_RunCast_fips(FIPS_CAST_ECC_CDH) != 0 ||
- wc_RunCast_fips(FIPS_CAST_ECC_PRIMITIVE_Z) != 0) {
+ if (wp_run_cast(FIPS_CAST_ECC_CDH) != 0 ||
+ wp_run_cast(FIPS_CAST_ECC_PRIMITIVE_Z) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_DH
case WP_CAST_ALGO_DH:
- if (wc_RunCast_fips(FIPS_CAST_DH_PRIMITIVE_Z) != 0) {
+ if (wp_run_cast(FIPS_CAST_DH_PRIMITIVE_Z) != 0) {
ok = 0;
}
break;
#endif
#ifdef WP_HAVE_RANDOM
case WP_CAST_ALGO_DRBG:
- if (wc_RunCast_fips(FIPS_CAST_DRBG) != 0) {
+ if (wp_run_cast(FIPS_CAST_DRBG) != 0) {
ok = 0;
}
break;
diff --git a/src/wp_mlx_kem.c b/src/wp_mlx_kem.c
index 09d15140..fd50c99c 100644
--- a/src/wp_mlx_kem.c
+++ b/src/wp_mlx_kem.c
@@ -233,6 +233,13 @@ static int wp_mlx_kem_classical_encap(wp_MlxCtx* ctx, unsigned char* cbuf,
ok = 0;
}
}
+#if defined(HAVE_FIPS) && !defined(WP_SINGLE_THREADED)
+ /* wc_ecc_shared_secret fires the ECC primitive-Z CAST; serialize it
+ * here. */
+ if (ok && wp_init_cast(WP_CAST_ALGO_ECDH) != 1) {
+ ok = 0;
+ }
+#endif
if (ok) {
PRIVATE_KEY_UNLOCK();
rc = wc_ecc_shared_secret(&eph,
@@ -382,6 +389,13 @@ static int wp_mlx_kem_classical_decap(wp_MlxCtx* ctx, const unsigned char* cbuf,
ok = 0;
}
}
+#if defined(HAVE_FIPS) && !defined(WP_SINGLE_THREADED)
+ /* wc_ecc_shared_secret fires the ECC primitive-Z CAST; serialize it
+ * here. */
+ if (ok && wp_init_cast(WP_CAST_ALGO_ECDH) != 1) {
+ ok = 0;
+ }
+#endif
if (ok) {
PRIVATE_KEY_UNLOCK();
rc = wc_ecc_shared_secret(priv, &peer, sbuf, &ssLen);
diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c
index 69446838..9c621431 100644
--- a/src/wp_wolfprov.c
+++ b/src/wp_wolfprov.c
@@ -1906,6 +1906,14 @@ int wolfssl_provider_init(const OSSL_CORE_HANDLE* handle,
"Failed to initialize FIPS CAST mutexes");
ok = 0;
}
+ /* Threads making the first use of an algorithm at once race on its
+ * CAST, so run the CASTs not run yet now, before the provider is
+ * usable (DH and ECC primitive-Z stay lazy, behind wp_init_cast()).
+ * A failed CAST still fails its algorithms when used. */
+ else if (!wp_run_pending_casts()) {
+ WOLFPROV_ERROR_MSG(WP_LOG_COMP_PROVIDER,
+ "FIPS CAST failed at provider init");
+ }
#endif
}
diff --git a/test/standalone/include.am b/test/standalone/include.am
index a8b9349e..be8494e6 100644
--- a/test/standalone/include.am
+++ b/test/standalone/include.am
@@ -56,6 +56,16 @@ test_decode_oid_precheck_test_SOURCES = \
test/standalone/tests/decode_oid_precheck/test_decode_oid_precheck.c
test_decode_oid_precheck_test_LDADD = $(STANDALONE_COMMON_LDADD)
+# FIPS CAST at provider init regression test - part of 'make check'. Runs in
+# fresh processes so the CASTs are cold (the shared unit.test warms them).
+check_PROGRAMS += test/fips_cast_init.test
+noinst_PROGRAMS += test/fips_cast_init.test
+DISTCLEANFILES += test/.libs/fips_cast_init.test
+test_fips_cast_init_test_CPPFLAGS = $(STANDALONE_COMMON_CPPFLAGS)
+test_fips_cast_init_test_SOURCES = \
+ test/standalone/tests/fips_cast_init/test_fips_cast_init.c
+test_fips_cast_init_test_LDADD = $(STANDALONE_COMMON_LDADD)
+
test_pqc_interop_test_CPPFLAGS = $(STANDALONE_COMMON_CPPFLAGS)
test_pqc_interop_test_SOURCES = test/standalone/tests/pqc_interop/test_pqc_interop.c
test_pqc_interop_test_LDADD = $(STANDALONE_COMMON_LDADD)
diff --git a/test/standalone/tests/fips_cast_init/test_fips_cast_init.c b/test/standalone/tests/fips_cast_init/test_fips_cast_init.c
new file mode 100644
index 00000000..67bba300
--- /dev/null
+++ b/test/standalone/tests/fips_cast_init/test_fips_cast_init.c
@@ -0,0 +1,390 @@
+/* test_fips_cast_init.c
+ *
+ * Copyright (C) 2006-2026 wolfSSL Inc.
+ *
+ * This file is part of wolfProvider.
+ *
+ * wolfProvider is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfProvider is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfProvider. If not, see .
+ */
+
+/*
+ * Regression test for running the FIPS CASTs at provider init.
+ *
+ * wolfCrypt FIPS runs an algorithm's CAST on its first use. Threads making
+ * that first use at once race on the CAST state and the losers fail. The
+ * provider now runs every CAST not run yet when it is loaded, except the DH
+ * and ECC primitive-Z ones, which stay lazy behind wp_init_cast().
+ *
+ * Standalone process because the CASTs must be cold, and the shared unit.test
+ * process warms them. Each race round runs in a fresh child process, as a
+ * CAST can only be raced once per process.
+ * Non-FIPS: there are no CASTs, so this skips.
+ */
+
+#include
+#include
+#include
+
+#ifdef WOLFPROV_USER_SETTINGS
+#include
+#endif
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "test_common.h"
+
+#if defined(HAVE_FIPS) && !defined(WP_SINGLE_THREADED) && !defined(_WIN32)
+#include
+#include
+#include
+#include
+
+/* Threads making their first use of an algorithm at once. */
+#define RACE_THREADS 8
+/* Fresh processes per operation. */
+#define RACE_ROUNDS 8
+
+/* First uses raced. Each fires CASTs that wolfCrypt runs inside the call. */
+enum {
+ RACE_TLS1_PRF,
+ RACE_TLS13_KDF,
+ RACE_SSHKDF,
+ RACE_HMAC,
+ RACE_OP_COUNT
+};
+
+static const char* raceOpName[RACE_OP_COUNT] = {
+ "TLS1-PRF", "TLS13-KDF", "SSHKDF", "HMAC"
+};
+
+static OSSL_LIB_CTX* wpLibCtx = NULL;
+static OSSL_PROVIDER* wpProv = NULL;
+
+static int load_provider(void)
+{
+ wpLibCtx = OSSL_LIB_CTX_new();
+ if (wpLibCtx == NULL) {
+ TEST_ERROR("Failed to create library context");
+ return TEST_FAILURE;
+ }
+ /* Match unit.test: find libwolfprov in .libs. */
+ OSSL_PROVIDER_set_default_search_path(wpLibCtx, ".libs");
+ wpProv = OSSL_PROVIDER_load(wpLibCtx, "libwolfprov");
+ if (wpProv == NULL) {
+ TEST_ERROR("Failed to load wolfProvider");
+ OSSL_LIB_CTX_free(wpLibCtx);
+ wpLibCtx = NULL;
+ return TEST_FAILURE;
+ }
+ return TEST_SUCCESS;
+}
+
+static void unload_provider(void)
+{
+ if (wpProv != NULL) {
+ OSSL_PROVIDER_unload(wpProv);
+ wpProv = NULL;
+ }
+ if (wpLibCtx != NULL) {
+ OSSL_LIB_CTX_free(wpLibCtx);
+ wpLibCtx = NULL;
+ }
+}
+
+static const char* cast_state_str(int s)
+{
+ switch (s) {
+ case FIPS_CAST_STATE_INIT: return "INIT";
+ case FIPS_CAST_STATE_PROCESSING: return "PROCESSING";
+ case FIPS_CAST_STATE_SUCCESS: return "SUCCESS";
+ case FIPS_CAST_STATE_FAILURE: return "FAILURE";
+ default: return "?";
+ }
+}
+
+/*
+ * TEST 1: once the provider is loaded, no CAST is left to run except the DH
+ * and ECC primitive-Z ones, which must still be cold. A CAST the module was
+ * built without is left PROCESSING by wolfCrypt when run, so only INIT and
+ * FAILURE are errors.
+ */
+static int test_casts_run_at_init(void)
+{
+ int err = TEST_SUCCESS;
+ int i;
+ int state;
+
+ TEST_INFO("Test 1: CASTs have run once the provider is loaded");
+
+ for (i = 0; i < FIPS_CAST_COUNT; i++) {
+ state = wc_GetCastStatus_fips(i);
+ if ((i == FIPS_CAST_DH_PRIMITIVE_Z) ||
+ (i == FIPS_CAST_ECC_PRIMITIVE_Z)) {
+ if (state != FIPS_CAST_STATE_INIT) {
+ TEST_ERROR(" CAST %d is %s, expected INIT (lazy)", i,
+ cast_state_str(state));
+ err = TEST_FAILURE;
+ }
+ }
+ else if ((state == FIPS_CAST_STATE_INIT) ||
+ (state == FIPS_CAST_STATE_FAILURE)) {
+ TEST_ERROR(" CAST %d is %s after provider load", i,
+ cast_state_str(state));
+ err = TEST_FAILURE;
+ }
+ }
+ return err;
+}
+
+static int race_kdf(EVP_KDF* kdf, int op)
+{
+ int ok;
+ EVP_KDF_CTX* kctx;
+ OSSL_PARAM params[6];
+ OSSL_PARAM* p = params;
+ unsigned char key[32];
+ unsigned char seed[32];
+ unsigned char out[32];
+ int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY;
+
+ memset(key, 0x0b, sizeof(key));
+ memset(seed, 0x0c, sizeof(seed));
+
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+ (char*)"SHA256", 0);
+ if (op == RACE_TLS1_PRF) {
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, key,
+ sizeof(key));
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, seed,
+ sizeof(seed));
+ }
+ else if (op == RACE_TLS13_KDF) {
+ *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, key,
+ sizeof(key));
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, seed,
+ sizeof(seed));
+ }
+ else {
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, key,
+ sizeof(key));
+ *p++ = OSSL_PARAM_construct_octet_string(
+ OSSL_KDF_PARAM_SSHKDF_XCGHASH, seed, sizeof(seed));
+ *p++ = OSSL_PARAM_construct_octet_string(
+ OSSL_KDF_PARAM_SSHKDF_SESSION_ID, seed, sizeof(seed));
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_SSHKDF_TYPE,
+ (char*)"A", 0);
+ }
+ *p = OSSL_PARAM_construct_end();
+
+ kctx = EVP_KDF_CTX_new(kdf);
+ ok = (kctx != NULL) &&
+ (EVP_KDF_derive(kctx, out, sizeof(out), params) == 1);
+ EVP_KDF_CTX_free(kctx);
+ return ok;
+}
+
+static int race_hmac(EVP_MAC* mac)
+{
+ int ok;
+ EVP_MAC_CTX* mctx;
+ OSSL_PARAM params[2];
+ unsigned char key[32];
+ unsigned char out[32];
+ size_t outLen = 0;
+
+ memset(key, 0x0b, sizeof(key));
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ (char*)"SHA256", 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ mctx = EVP_MAC_CTX_new(mac);
+ ok = (mctx != NULL) &&
+ (EVP_MAC_init(mctx, key, sizeof(key), params) == 1) &&
+ (EVP_MAC_update(mctx, key, sizeof(key)) == 1) &&
+ (EVP_MAC_final(mctx, out, &outLen, sizeof(out)) == 1);
+ EVP_MAC_CTX_free(mctx);
+ return ok;
+}
+
+typedef struct race_ctx {
+ pthread_barrier_t barrier;
+ int op;
+ EVP_KDF* kdf;
+ EVP_MAC* mac;
+} race_ctx;
+
+static void* race_thread(void* arg)
+{
+ race_ctx* rc = (race_ctx*)arg;
+ int ok;
+
+ pthread_barrier_wait(&rc->barrier);
+ if (rc->op == RACE_HMAC) {
+ ok = race_hmac(rc->mac);
+ }
+ else {
+ ok = race_kdf(rc->kdf, rc->op);
+ }
+ return ok ? NULL : (void*)1;
+}
+
+/* Child process: load the provider and race first use of op. Returns the
+ * number of threads that failed, or RACE_THREADS + 1 on setup failure. */
+static int race_child(int op)
+{
+ race_ctx rc;
+ pthread_t threads[RACE_THREADS];
+ void* res;
+ int i;
+ int failed = 0;
+
+ if (load_provider() != TEST_SUCCESS) {
+ return RACE_THREADS + 1;
+ }
+ rc.op = op;
+ rc.kdf = NULL;
+ rc.mac = NULL;
+ /* Fetch up front: only the first use of the algorithm is raced. */
+ if (op == RACE_HMAC) {
+ rc.mac = EVP_MAC_fetch(wpLibCtx, "HMAC", NULL);
+ }
+ else {
+ rc.kdf = EVP_KDF_fetch(wpLibCtx, raceOpName[op], NULL);
+ }
+ if ((rc.mac == NULL) && (rc.kdf == NULL)) {
+ unload_provider();
+ return RACE_THREADS + 1;
+ }
+
+ pthread_barrier_init(&rc.barrier, NULL, RACE_THREADS);
+ for (i = 0; i < RACE_THREADS; i++) {
+ if (pthread_create(&threads[i], NULL, race_thread, &rc) != 0) {
+ /* The barrier can never be reached: give up on this process. */
+ _exit(RACE_THREADS + 1);
+ }
+ }
+ for (i = 0; i < RACE_THREADS; i++) {
+ pthread_join(threads[i], &res);
+ if (res != NULL) {
+ failed++;
+ }
+ }
+ pthread_barrier_destroy(&rc.barrier);
+
+ EVP_MAC_free(rc.mac);
+ EVP_KDF_free(rc.kdf);
+ unload_provider();
+ return failed;
+}
+
+/*
+ * TEST 2: threads making the first use of a KDF or HMAC at once all succeed.
+ * Each round is a fresh process so the CASTs start cold.
+ */
+static int test_first_use_race(void)
+{
+ int err = TEST_SUCCESS;
+ int op;
+ int round;
+ int status;
+ int failed;
+ int badRounds;
+ pid_t pid;
+
+ TEST_INFO("Test 2: %d threads making first use at once, %d processes "
+ "per algorithm", RACE_THREADS, RACE_ROUNDS);
+
+ for (op = 0; op < RACE_OP_COUNT; op++) {
+ badRounds = 0;
+ for (round = 0; round < RACE_ROUNDS; round++) {
+ fflush(NULL);
+ pid = fork();
+ if (pid < 0) {
+ TEST_ERROR(" fork failed");
+ return TEST_FAILURE;
+ }
+ if (pid == 0) {
+ _exit(race_child(op));
+ }
+ if ((waitpid(pid, &status, 0) != pid) || !WIFEXITED(status)) {
+ TEST_ERROR(" %s: child did not exit normally",
+ raceOpName[op]);
+ return TEST_FAILURE;
+ }
+ failed = WEXITSTATUS(status);
+ if (failed > RACE_THREADS) {
+ TEST_ERROR(" %s: child setup failed", raceOpName[op]);
+ return TEST_FAILURE;
+ }
+ if (failed != 0) {
+ badRounds++;
+ }
+ }
+ TEST_INFO(" %s: %d of %d processes had a failed thread",
+ raceOpName[op], badRounds, RACE_ROUNDS);
+ if (badRounds != 0) {
+ err = TEST_FAILURE;
+ }
+ }
+ return err;
+}
+
+#endif /* HAVE_FIPS && !WP_SINGLE_THREADED && !_WIN32 */
+
+int main(void)
+{
+ TEST_INFO("========================================");
+ TEST_INFO("FIPS CAST at provider init test");
+ TEST_INFO("========================================");
+
+#if !defined(HAVE_FIPS) || defined(WP_SINGLE_THREADED) || defined(_WIN32)
+ TEST_INFO("SKIPPED - not a multi-threaded POSIX FIPS build");
+ return TEST_SUCCESS;
+#else
+ {
+ int rc = TEST_SUCCESS;
+
+ /* Race first: the children must start from cold CASTs, so this process
+ * loads no wolfCrypt state before forking. */
+ if (test_first_use_race() != TEST_SUCCESS) {
+ rc = TEST_FAILURE;
+ }
+ if (load_provider() != TEST_SUCCESS) {
+ return TEST_FAILURE;
+ }
+ if (test_casts_run_at_init() != TEST_SUCCESS) {
+ rc = TEST_FAILURE;
+ }
+ unload_provider();
+
+ TEST_INFO("========================================");
+ if (rc == TEST_SUCCESS) {
+ TEST_INFO("All FIPS CAST at provider init tests PASSED");
+ }
+ else {
+ TEST_ERROR("FIPS CAST at provider init tests FAILED");
+ }
+ TEST_INFO("========================================");
+
+ return rc;
+ }
+#endif
+}