-
Notifications
You must be signed in to change notification settings - Fork 34
wp_drbg: gate wc_RNG_DRBG_Reseed on FIPS version #408
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
| #include <openssl/evp.h> | ||
| #include <openssl/hmac.h> | ||
|
|
||
| #include <wolfprovider/settings.h> | ||
| #include <wolfprovider/alg_funcs.h> | ||
| #include <wolfprovider/internal.h> | ||
|
|
||
|
|
@@ -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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] wp_drbg_get_seed does not check the new rngError state, operating on a de-instantiated RNG after a failed fallback… · State Management This PR introduces a new failure state in the non-WP_HAVE_DRBG_RESEED fallback of wp_drbg_reseed: if wc_InitRngNonce fails after wc_FreeRng(ctx->rng), ctx->rng is left non-NULL but de-instantiated (internal DRBG freed) and ctx->rngError = 1 is set. The PR correctly teaches wp_drbg_generate (line 328-333) and wp_drbg_get_ctx_params (line 629-634) to honor ctx->rngError, but wp_drbg_get_seed was NOT updated. It still gates only on if (ctx->rng == NULL) (line 734), so on a parent DRBG whose reseed re-instantiation failed, a child's GET_SEED request (OSSL_FUNC_RAND_GET_SEED) falls through to wc_RNG_GenerateBlock(ctx->rng, buffer, ...) (line 750) on a de-instantiated WC_RNG. Before this PR the reseed path used wc_RNG_DRBG_Reseed, which never de-instantiates, so ctx->rng != NULL always implied a usable RNG and the single NULL check was sufficient. wolfCrypt's wc_RNG_GenerateBlock guards a de-instantiated RNG (returns an error rather than dereferencing freed memory), so this fails safe with no crash, use-after-free, or weak-randomness leak — hence Low/Info — but the error-state handling is inconsistent with the other two consumers. Severity views: review-security rated Low (CWE-665, defense-in-depth); bugs rated Info (narrow trigger). Kept the stricter Low. Fix: Mirror the guard used in wp_drbg_generate: immediately after the existing ctx->rng == NULL test in wp_drbg_get_seed, add an #ifndef WP_HAVE_DRBG_RESEED block: if (ctx->rngError) { WOLFPROV_MSG_DEBUG(WP_LOG_COMP_RNG, "DRBG in error state"); goto end; } so a failed-reseed parent reports failure to children consistently rather than relying on wolfCrypt's internal status guard.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, fix made |
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 [Medium] FIPS fallback reseed re-instantiates from OS entropy, bypassing the parent-seed/seccomp design and treating caller… · Security In the non-WP_HAVE_DRBG_RESEED fallback (FIPS modules without an exported wc_RNG_DRBG_Reseed, e.g. cert4718), reseed is implemented as wc_FreeRng(ctx->rng) + wc_InitRngNonce(ctx->rng, nonce, nonceLen), passing the caller-supplied entropy/addIn as the SP 800-90A NONCE rather than as entropy_input. This diverges from the native wc_RNG_DRBG_Reseed path in two ways: (1) wc_InitRngNonce always pulls fresh entropy from the OS seed source (wc_GenerateSeed -> getrandom()/dev/urandom) regardless of caller-supplied entropy, whereas the native path reseeds from caller entropy with no OS access; (2) it is a re-instantiation, not a true reseed, so the DRBG reseed counter/internal state semantics differ. The DRBG is not weakened (it is freshly seeded), and the divergence is documented in the function header. The notable risk worth confirming: wp_drbg_new documents that child DRBGs deliberately route entropy through the parent DRBG (parentGetSeed) so they avoid direct /dev/urandom access for seccomp-sandbox compatibility (see test/test_seccomp_sandbox.c). The in-place wc_InitRngNonce bypasses parentGetSeed entirely, so a reseed under a seccomp sandbox (reachable via RAND_seed/RAND_add, exercised by test_rand.c) could hit /dev/urandom and be blocked/killed on exactly the cert4718 FIPS builds this PR targets. Severity views: review rated Medium/SUGGEST (sandbox interaction worth confirming); review-security rated Info (documented, fails safe). Kept the stricter Medium. Fix: Confirm (ideally with a sandbox test on a fallback/cert4718 build that triggers a reseed) that the fallback wc_InitRngNonce path does not break the seccomp-sandbox guarantee the parent-seed design provides. If it can, route the fallback re-instantiation seed through ctx->parentGetSeed when available, mirroring wp_drbg_instantiate, to preserve the no-direct-/dev/urandom invariant.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a test for this. |
||
| /* 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [Low] wc_FreeRng() return code ignored in fallback reseed · Code Convention wc_FreeRng(ctx->rng) in the fallback branch discards its return value. The wolfSSL C coding standard calls for checking every return code. This matches the existing style in this file (wp_drbg_free/wp_drbg_uninstantiate also ignore wc_FreeRng's return in the <5.0 path), so it is consistent rather than a regression — noted only for completeness. Fix: Optional: capture and debug-log the wc_FreeRng return code for consistency with the coding standard, or leave as-is to match the file's existing convention.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leaving as is for consistency |
||
| 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) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 [Medium] New error-state and explicit-entropy reseed paths have no test coverage · Test Coverage
The PR adds new state logic only exercised on FIPS-fallback builds: ctx->rngError being set on a failed re-instantiation, wp_drbg_generate refusing to run in the error state, and wp_drbg_get_ctx_params reporting EVP_RAND_STATE_ERROR/EVP_RAND_STATE_UNINITIALISED. The existing test/test_rand.c only calls RAND_seed/RAND_add/RAND_bytes on a healthy DRBG and never asserts on the reported OSSL_RAND_PARAM_STATE or drives a reseed failure, so none of the new error-handling branches are validated. make test passing on cert4718 only confirms the happy-path fallback works.
Fix: Add a test that (a) reads OSSL_RAND_PARAM_STATE via EVP_RAND_CTX_get_params and asserts EVP_RAND_STATE_READY after instantiate, and (b) forces a reseed failure to confirm the state transitions to EVP_RAND_STATE_ERROR and that subsequent generate calls fail, plus recovery via uninstantiate+instantiate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is basically legacy path at this point, seems like test bloat rather than high ROI testing to me.