diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index 151bc707..c9754f46 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -28,6 +28,24 @@ #include #include +/* wc_RNG_DRBG_Reseed is WOLFSSL_API in non-FIPS >= 5.7.2 and in FIPS v6+. + * Across FIPS v5.x commercial bundles its linkage is inconsistent: some v5.2.4 + * bundles export it and others keep it WOLFSSL_LOCAL (it depends on both the + * wolfSSL wrapper version and the FIPS cert), so it cannot be predicted from + * the version macros. Use the native in-place reseed only where the symbol is + * reliably exported and fall back to DRBG re-instantiation otherwise (which + * links on every build). Define WP_NO_DRBG_RESEED to force the fallback for a + * bundle that keeps the symbol WOLFSSL_LOCAL despite its version. */ +#if defined(WP_NO_DRBG_RESEED) + /* caller forced the re-instantiation fallback */ +#elif !defined(HAVE_FIPS) + #if LIBWOLFSSL_VERSION_HEX >= 0x05007002 + #define WP_HAVE_DRBG_RESEED + #endif +#elif defined(HAVE_FIPS_VERSION_MAJOR) && HAVE_FIPS_VERSION_MAJOR >= 6 + #define WP_HAVE_DRBG_RESEED +#endif + #define WP_HAVE_DIGEST #if !defined(NO_MD5) #define WP_HAVE_MD5 diff --git a/scripts/utils-wolfssl.sh b/scripts/utils-wolfssl.sh index ea789733..e259cdff 100644 --- a/scripts/utils-wolfssl.sh +++ b/scripts/utils-wolfssl.sh @@ -179,7 +179,15 @@ install_wolfssl() { # Determine configure option from tag local fips_configure_arg="" case "$fips_tag" in + v5.2.4|linuxv5.2.4) + # Distinct module (SP math, PATCH 4) — not the v5/cert4718 base + fips_configure_arg="v5.2.4" + ;; + v5.2.3|linuxv5.2.3) + fips_configure_arg="v5.2.3" + ;; v5.2.*|v5.3.*|v5.4.*|v5.5.*|linuxv5.*) + # v5.2.1/cert4718 and other v5.x map to the base v5 module fips_configure_arg="v5" ;; v6.*|linuxv6.*) diff --git a/src/wp_drbg.c b/src/wp_drbg.c index b68a5352..bbdbdc5c 100644 --- a/src/wp_drbg.c +++ b/src/wp_drbg.c @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -61,6 +62,10 @@ typedef struct wp_DrbgCtx { OSSL_FUNC_rand_get_seed_fn* parentGetSeed; /** Parent's clear_seed function. */ OSSL_FUNC_rand_clear_seed_fn* parentClearSeed; +#ifndef WP_HAVE_DRBG_RESEED + /** Set when a failed reseed re-instantiation left ctx->rng de-instantiated. */ + int rngError; +#endif } wp_DrbgCtx; @@ -143,6 +148,8 @@ static void wp_drbg_free(wp_DrbgCtx* ctx) } } +static int wp_drbg_uninstantiate(wp_DrbgCtx* ctx); + /** * Instantiate a new DRBG. * @@ -171,6 +178,11 @@ static int wp_drbg_instantiate(wp_DrbgCtx* ctx, unsigned int strength, ok = 0; } + /* Free any existing DRBG before re-allocating to avoid a leak. */ + if (ok && ctx->rng != NULL) { + wp_drbg_uninstantiate(ctx); + } + if (ok && ctx->parentGetSeed != NULL) { /* Get entropy from parent DRBG (no file I/O needed) */ WOLFPROV_MSG_DEBUG(WP_LOG_COMP_RNG, @@ -189,22 +201,29 @@ static int wp_drbg_instantiate(wp_DrbgCtx* ctx, unsigned int strength, } if (ok) { - /* Initialize wolfCrypt RNG with parent-provided seed */ - ctx->rng = OPENSSL_zalloc(sizeof(*ctx->rng)); + /* Use wc_rng_new (>= 5.0) so alloc and free use the same allocator, + * matching the root path and wc_rng_free() in teardown. */ + #if LIBWOLFSSL_VERSION_HEX >= 0x05000000 + ctx->rng = wc_rng_new(seed, (word32)seedLen, NULL); if (ctx->rng == NULL) { ok = 0; } - } - - if (ok) { - int rc = wc_InitRngNonce(ctx->rng, seed, (word32)seedLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, - "wc_InitRngNonce", rc); - OPENSSL_free(ctx->rng); - ctx->rng = NULL; + #else + ctx->rng = OPENSSL_zalloc(sizeof(*ctx->rng)); + if (ctx->rng == NULL) { ok = 0; } + if (ok) { + int rc = wc_InitRngNonce(ctx->rng, seed, (word32)seedLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, + "wc_InitRngNonce", rc); + OPENSSL_clear_free(ctx->rng, sizeof(*ctx->rng)); + ctx->rng = NULL; + ok = 0; + } + } + #endif } /* Clear the seed from parent */ @@ -242,6 +261,13 @@ static int wp_drbg_instantiate(wp_DrbgCtx* ctx, unsigned int strength, #endif } +#ifndef WP_HAVE_DRBG_RESEED + if (ok) { + /* Clear any prior reseed error state. */ + ctx->rngError = 0; + } +#endif + WOLFPROV_LEAVE(WP_LOG_COMP_RNG, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -262,6 +288,9 @@ static int wp_drbg_uninstantiate(wp_DrbgCtx* ctx) OPENSSL_clear_free(ctx->rng, sizeof(*ctx->rng)); #endif ctx->rng = NULL; +#ifndef WP_HAVE_DRBG_RESEED + ctx->rngError = 0; +#endif WOLFPROV_LEAVE(WP_LOG_COMP_RNG, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), 1); return 1; } @@ -292,6 +321,16 @@ static int wp_drbg_generate(wp_DrbgCtx* ctx, unsigned char* out, if (strength > WP_DRBG_STRENGTH) { ok = 0; } + if (ok && ctx->rng == NULL) { + WOLFPROV_MSG_DEBUG(WP_LOG_COMP_RNG, "DRBG not instantiated"); + ok = 0; + } +#ifndef WP_HAVE_DRBG_RESEED + if (ok && ctx->rngError) { + WOLFPROV_MSG_DEBUG(WP_LOG_COMP_RNG, "DRBG in error state"); + ok = 0; + } +#endif #if 0 if (ok && (addInLen > 0)) { rc = wc_RNG_DRBG_Reseed(ctx->rng, addIn, addInLen); @@ -322,6 +361,10 @@ static int wp_drbg_generate(wp_DrbgCtx* ctx, unsigned char* out, /** * Reseed DRBG. * + * When WP_HAVE_DRBG_RESEED is undefined (FIPS modules without an exported + * wc_RNG_DRBG_Reseed), this re-instantiates rather than reseeding: @p entropy / + * @p addIn are used as the nonce, not as DRBG entropy_input. + * * @param [in, out] ctx DRBG context object. * @param [in] predResist Prediction resistance required. * @param [in] entropy Entropy data to reseed with. @@ -338,52 +381,130 @@ static int wp_drbg_reseed(wp_DrbgCtx* ctx, int predResist, { int ok = 1; int rc; - unsigned char *seed = NULL; - size_t seedLen = 0; WOLFPROV_ENTER(WP_LOG_COMP_RNG, "wp_drbg_reseed"); - /* If no entropy provided, get fresh entropy from the OS source. */ - if (entropy == NULL || entropyLen == 0) { - seedLen = 48; - seed = OPENSSL_malloc(seedLen); - if (seed == NULL) { - ok = 0; - } - if (ok) { - OS_Seed osSeed; - rc = wc_GenerateSeed(&osSeed, seed, (word32)seedLen); - if (rc != 0) { + /* Reseed requires an instantiated DRBG. */ + if (ctx->rng == NULL) { + ok = 0; + } + + /* wolfCrypt RNG APIs take word32 lengths; reject oversized inputs. */ + if (ok && entropy != NULL && entropyLen > 0xFFFFFFFFU) { + ok = 0; + } + if (ok && addIn != NULL && addInLen > 0xFFFFFFFFU) { + ok = 0; + } + +#ifdef WP_HAVE_DRBG_RESEED + { + unsigned char* seed = NULL; + size_t seedLen = 0; + + /* An in-place reseed needs explicit entropy. If the caller supplied + * none, draw fresh entropy from the cached /dev/urandom fd (SEED-SRC), + * which stays open across a seccomp sandbox, rather than + * wc_GenerateSeed() which opens /dev/urandom directly and would be + * blocked under the sandbox. */ + if (ok && (entropy == NULL || entropyLen == 0)) { + seedLen = 48; + seed = OPENSSL_malloc(seedLen); + if (seed == NULL) { ok = 0; } - else { + if (ok) { + #if defined(WP_HAVE_SEED_SRC) && defined(WP_HAVE_RANDOM) + if (wp_urandom_read(seed, seedLen) != (int)seedLen) { + ok = 0; + } + #else + OS_Seed osSeed; + if (wc_GenerateSeed(&osSeed, seed, (word32)seedLen) != 0) { + ok = 0; + } + #endif + } + if (ok) { entropy = seed; entropyLen = seedLen; } } - } - if (ok && entropy != NULL && entropyLen > 0) { - rc = wc_RNG_DRBG_Reseed(ctx->rng, entropy, (word32)entropyLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, - "wc_RNG_DRBG_Reseed", rc); - ok = 0; + /* In-place SP 800-90A reseed via wolfCrypt's public DRBG API. */ + if (ok && entropy != NULL && entropyLen > 0) { + rc = wc_RNG_DRBG_Reseed(ctx->rng, entropy, (word32)entropyLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, + "wc_RNG_DRBG_Reseed", rc); + ok = 0; + } } - } - if (ok && (addInLen > 0) && (addIn != NULL)) { - rc = wc_RNG_DRBG_Reseed(ctx->rng, addIn, (word32)addInLen); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, - "wc_RNG_DRBG_Reseed", rc); - ok = 0; + if (ok && (addInLen > 0) && (addIn != NULL)) { + rc = wc_RNG_DRBG_Reseed(ctx->rng, addIn, (word32)addInLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, + "wc_RNG_DRBG_Reseed", rc); + ok = 0; + } + } + + if (seed != NULL) { + OPENSSL_clear_free(seed, seedLen); } } +#else + /* FIPS modules without an exported wc_RNG_DRBG_Reseed (e.g. cert4718): + * re-instantiate in place via wc_FreeRng() + wc_InitRngNonce() on the same + * WC_RNG struct. wc_InitRngNonce self-seeds fresh entropy_input; with + * SEED-SRC that entropy comes from the cached /dev/urandom fd via the seed + * callback (so it stays sandbox-safe), otherwise from wc_GenerateSeed(). + * Caller entropy/addIn are folded in only as the nonce (which may be empty). + * A failed re-init leaves the DRBG de-instantiated and sets rngError. */ + if (ok) { + unsigned char* nonce = NULL; + word32 nonceLen = 0; + word32 eLen = (entropy != NULL) ? (word32)entropyLen : 0; + word32 aLen = (addIn != NULL) ? (word32)addInLen : 0; - /* Securely clear and free locally allocated seed buffer. */ - if (seed != NULL) { - OPENSSL_clear_free(seed, seedLen); + /* Build nonce = entropy || addIn (either may be absent). */ + if (aLen > (0xFFFFFFFFU - eLen)) { + ok = 0; + } + if (ok && (eLen + aLen) > 0) { + nonceLen = eLen + aLen; + nonce = OPENSSL_malloc(nonceLen); + if (nonce == NULL) { + ok = 0; + } + else { + if (eLen > 0) { + XMEMCPY(nonce, entropy, eLen); + } + if (aLen > 0) { + XMEMCPY(nonce + eLen, addIn, aLen); + } + } + } + if (ok) { + wc_FreeRng(ctx->rng); + rc = wc_InitRngNonce(ctx->rng, nonce, nonceLen); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, + "wc_InitRngNonce", rc); + ctx->rngError = 1; + ok = 0; + } + else { + /* Recovered: clear any prior reseed error. */ + ctx->rngError = 0; + } + } + if (nonce != NULL) { + OPENSSL_clear_free(nonce, nonceLen); + } } +#endif /* WP_HAVE_DRBG_RESEED */ (void)predResist; @@ -512,15 +633,25 @@ static int wp_drbg_get_ctx_params(wp_DrbgCtx* ctx, OSSL_PARAM params[]) WOLFPROV_ENTER(WP_LOG_COMP_RNG, "wp_drbg_get_ctx_params"); - (void)ctx; - p = OSSL_PARAM_locate(params, OSSL_RAND_PARAM_MAX_REQUEST); if ((p != NULL) && (!OSSL_PARAM_set_size_t(p, WP_DRBG_MAX_REQUESTS))) { ok = 0; } if (ok) { + int state = EVP_RAND_STATE_READY; + + if (ctx->rng == NULL) { + state = EVP_RAND_STATE_UNINITIALISED; + } + #ifndef WP_HAVE_DRBG_RESEED + /* Failed reseed re-instantiation left the DRBG de-instantiated. */ + else if (ctx->rngError) { + state = EVP_RAND_STATE_ERROR; + } + #endif + p = OSSL_PARAM_locate(params, OSSL_RAND_PARAM_STATE); - if ((p != NULL) && (!OSSL_PARAM_set_int(p, EVP_RAND_STATE_READY))) { + if ((p != NULL) && (!OSSL_PARAM_set_int(p, state))) { ok = 0; } } @@ -622,6 +753,12 @@ static size_t wp_drbg_get_seed(wp_DrbgCtx* ctx, unsigned char** pSeed, "DRBG not instantiated"); goto end; } +#ifndef WP_HAVE_DRBG_RESEED + if (ctx->rngError) { + WOLFPROV_MSG_DEBUG(WP_LOG_COMP_RNG, "DRBG in error state"); + goto end; + } +#endif buffer = OPENSSL_secure_malloc(minLen); if (buffer == NULL) { diff --git a/test/test_seccomp_sandbox.c b/test/test_seccomp_sandbox.c index 82d65b94..8b3f3e42 100644 --- a/test/test_seccomp_sandbox.c +++ b/test/test_seccomp_sandbox.c @@ -269,6 +269,27 @@ static int child_test_rand_under_sandbox(OSSL_LIB_CTX *libCtx) err = 1; } + /* Explicitly reseed the public DRBG under the sandbox. With NULL entropy + * this drives wp_drbg_reseed()'s fresh-entropy path, which must use the + * cached /dev/urandom fd (SEED-SRC) and must not open() /dev/urandom. */ + if (err == 0) { + EVP_RAND_CTX *rctx = RAND_get0_public(libCtx); + if (rctx == NULL) { + PRINT_ERR_MSG("RAND_get0_public failed under sandbox"); + err = 1; + } + else if (EVP_RAND_reseed(rctx, 0, NULL, 0, NULL, 0) != 1) { + PRINT_ERR_MSG("EVP_RAND_reseed failed under sandbox"); + err = 1; + } + } + + /* Confirm the DRBG is still usable after the sandboxed reseed. */ + if (err == 0 && RAND_bytes(buf, sizeof(buf)) != 1) { + PRINT_ERR_MSG("RAND_bytes after reseed failed under sandbox"); + err = 1; + } + OSSL_LIB_CTX_set0_default(origCtx); return err; }