From 05f28c3b1246246372c316b16db82bbe9d14275c Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 23 Jun 2026 09:01:16 -0700 Subject: [PATCH] wp_drbg: gate reseed on FIPS export with re-instantiation fallback Use native wc_RNG_DRBG_Reseed only where reliably exported (non-FIPS >=5.7.2, FIPS v6+); else re-instantiate via wc_FreeRng()+wc_InitRngNonce(). Parent-path instantiate uses the FIPS-validated wc_InitRngNonce. Also fix install_wolfssl cwd imbalance on FIPS-bundle builds and add a reseed-with-entropy test. --- include/wolfprovider/settings.h | 14 +++ scripts/utils-wolfssl.sh | 16 ++- src/wp_drbg.c | 198 ++++++++++++++++++++++++++------ test/test_rand_seed.c | 19 +++ test/test_seccomp_sandbox.c | 20 ++++ 5 files changed, 229 insertions(+), 38 deletions(-) diff --git a/include/wolfprovider/settings.h b/include/wolfprovider/settings.h index 151bc707..f2026dc4 100644 --- a/include/wolfprovider/settings.h +++ b/include/wolfprovider/settings.h @@ -28,6 +28,20 @@ #include #include +/* wc_RNG_DRBG_Reseed is only reliably exported in non-FIPS >= 5.7.2 and FIPS + * v6+. FIPS v5.x bundles are inconsistent (some keep it WOLFSSL_LOCAL), so use + * the native reseed only where it's exported, else fall back to DRBG + * re-instantiation. WP_NO_DRBG_RESEED forces the fallback. */ +#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..fb82abf9 100644 --- a/scripts/utils-wolfssl.sh +++ b/scripts/utils-wolfssl.sh @@ -179,7 +179,16 @@ 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) + # Distinct module (PATCH 3) — not the v5/cert4718 base + 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.*) @@ -320,7 +329,12 @@ install_wolfssl() { do_cleanup exit 1 fi - cd .. + # Only the clone path descended into XXX-fips-test; bundles build in + # wolfssl-source directly. Must mirror the descent guard, else the + # bundle path pops one level too far. + if [ -z "$WOLFSSL_FIPS_BUNDLE" ]; then + cd .. + fi printf "Done.\n" fi fi diff --git a/src/wp_drbg.c b/src/wp_drbg.c index b68a5352..ba516772 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,7 +201,9 @@ static int wp_drbg_instantiate(wp_DrbgCtx* ctx, unsigned int strength, } if (ok) { - /* Initialize wolfCrypt RNG with parent-provided seed */ + /* Route DRBG instantiation through the FIPS-validated + * wc_InitRngNonce entry (not wc_rng_new), so it works on every + * pinned FIPS bundle vintage. */ ctx->rng = OPENSSL_zalloc(sizeof(*ctx->rng)); if (ctx->rng == NULL) { ok = 0; @@ -201,7 +215,7 @@ static int wp_drbg_instantiate(wp_DrbgCtx* ctx, unsigned int strength, if (rc != 0) { WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_COMP_RNG, "wc_InitRngNonce", rc); - OPENSSL_free(ctx->rng); + OPENSSL_clear_free(ctx->rng, sizeof(*ctx->rng)); ctx->rng = NULL; ok = 0; } @@ -242,6 +256,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 +283,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 +316,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); @@ -318,10 +352,12 @@ static int wp_drbg_generate(wp_DrbgCtx* ctx, unsigned char* out, return ok; } -/* No usage of EVP_RAND_reseed seen in OpenSSL library. */ /** * Reseed DRBG. * + * Without WP_HAVE_DRBG_RESEED, re-instantiates instead of reseeding: @p entropy + * and @p addIn become the nonce, not 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 +374,124 @@ 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; + + /* No caller entropy: with SEED-SRC, draw from the cached /dev/urandom + * fd (survives a seccomp sandbox); else wc_GenerateSeed(). */ + 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 + /* No exported wc_RNG_DRBG_Reseed (e.g. cert4718): re-instantiate in place + * via wc_FreeRng() + wc_InitRngNonce(), which self-seeds fresh entropy + * (sandbox-safe when built with SEED-SRC). Caller entropy/addIn become + * only the nonce. A failed re-init 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 +620,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 +740,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_rand_seed.c b/test/test_rand_seed.c index 6f5aa190..e2c62cdc 100644 --- a/test/test_rand_seed.c +++ b/test/test_rand_seed.c @@ -680,6 +680,25 @@ static int test_drbg_reseed_helper(OSSL_LIB_CTX *libCtx, const char *propq) } PRINT_MSG("Pre/post-reseed outputs differ as expected"); + /* Reseed with explicit entropy + addIn. */ + { + unsigned char ent[32], add[16]; + memset(ent, 0xA5, sizeof(ent)); + memset(add, 0x5A, sizeof(add)); + if (EVP_RAND_reseed(drbg_ctx, 0, ent, sizeof(ent), + add, sizeof(add)) != 1) { + PRINT_ERR_MSG("Reseed with entropy/addIn failed"); + err = 1; + goto cleanup; + } + if (EVP_RAND_generate(drbg_ctx, buf1, sizeof(buf1), 256, 0, NULL, 0) + != 1 || memcmp(buf1, buf2, sizeof(buf1)) == 0) { + PRINT_ERR_MSG("Generate after entropy reseed failed/unchanged"); + err = 1; + goto cleanup; + } + } + /* Uninstantiate and verify zeroization (exercises fix #170). */ if (EVP_RAND_uninstantiate(drbg_ctx) != 1) { PRINT_ERR_MSG("EVP_RAND_uninstantiate failed"); diff --git a/test/test_seccomp_sandbox.c b/test/test_seccomp_sandbox.c index 82d65b94..9e43a64c 100644 --- a/test/test_seccomp_sandbox.c +++ b/test/test_seccomp_sandbox.c @@ -269,6 +269,26 @@ static int child_test_rand_under_sandbox(OSSL_LIB_CTX *libCtx) err = 1; } + /* Reseed under the sandbox with NULL entropy: drives the fresh-entropy + * path, which must use the cached SEED-SRC fd, 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; }