Skip to content
Closed
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
1 change: 1 addition & 0 deletions include/wolfprovider/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 4 additions & 0 deletions src/wp_dh_kmgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
101 changes: 89 additions & 12 deletions src/wp_internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions src/wp_mlx_kem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/wp_wolfprov.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
10 changes: 10 additions & 0 deletions test/standalone/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading