diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index ba57ec27273..4d40c1c001f 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -113,6 +113,8 @@ int wc_FreeNetRandom(void); MAX_REQUEST_LEN \return DRBG_CONT_FIPS_E wc_RNG_GenerateBlock: Hash_gen returned DRBG_CONT_FAILURE + \return ENTROPY_RT_E or ENTROPY_APT_E wc_InitRng: the SP 800-90B seed + health test rejected the entropy gathered to instantiate \return RNG_FAILURE_E wc_RNG_GenerateBlock: Default error. rng’s status originally not ok, or set to DRBG_FAILED \return BAD_MUTEX_E the lock that lets threads share this rng could not @@ -157,6 +159,8 @@ int wc_InitRng(WC_RNG* rng); \return 0 on success \return BAD_FUNC_ARG an input is null or sz exceeds MAX_REQUEST_LEN \return DRBG_CONT_FIPS_E Hash_gen returned DRBG_CONT_FAILURE + \return ENTROPY_RT_E or ENTROPY_APT_E the SP 800-90B seed health test + rejected the entropy gathered for a reseed \return RNG_FAILURE_E Default error. rng’s status originally not ok, or set to DRBG_FAILED \return BAD_MUTEX_E the rng's lock could not be taken @@ -544,7 +548,6 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz); \return BAD_FUNC_ARG If seed is NULL \return ENTROPY_RT_E || ENTROPY_APT_E Validation failed \return ENTROPY_APT_E The adaptive proportion test failed. - \return MEMORY_E Allocation failed. \param seed Seed to test \param seedSz Seed size @@ -1009,6 +1012,9 @@ int wc_RNG_DRBG_ScheduleReseed(WC_RNG* rng); \return DRBG_CONT_FIPS_E The continuous test failed; the DRBG is out of service. \return RNG_FAILURE_E The DRBG is out of service or reseeding failed. + \return ENTROPY_RT_E or ENTROPY_APT_E The SP 800-90B seed health test + rejected the gathered seed; the reseed did not happen and the instance + remains usable until its reseed interval is reached. \param rng The RNG object to reseed. \param nonce Optional additional input. @@ -1303,7 +1309,8 @@ int wc_RNG_DRBG_GetNextSeedRBGCStratum(const WC_RNG* rng); \return 0 Bytes were banked (bank may or may not yet be complete). \return ALREADY_E The bank is ready or being consumed. - \return NOT_READY_E The health test could not run; simply retry. + \return ENTROPY_RT_E or ENTROPY_APT_E The SP 800-90B seed health test + rejected the banked material, which is burned. \return BAD_FUNC_ARG rng is null or n is 0. \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). @@ -1334,7 +1341,8 @@ int wc_RNG_DRBG_NextSeedGenerate(WC_RNG* rng, word32 n); \return 0 Bytes were banked. \return ALREADY_E The bank is ready or being consumed. - \return NOT_READY_E The health test could not run; simply retry. + \return ENTROPY_RT_E or ENTROPY_APT_E The SP 800-90B seed health test + rejected the banked material, which is burned. \return BAD_FUNC_ARG rng or root is null, or n is 0. \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). \return SEQ_OVERFLOW_E root's stratum is at the representable maximum. @@ -1388,6 +1396,8 @@ int wc_RNG_DRBG_NextSeedCurrent(WC_RNG* rng, WC_ATOMIC_INT_ARG* n); \return NOT_READY_E No bank is ready. \return BAD_FUNC_ARG rng is null. \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + \return ENTROPY_RT_E or ENTROPY_APT_E A seed health test failed on + the reseed this performs; the instance is condemned. \param rng The RNG object to reseed. @@ -1417,6 +1427,8 @@ int wc_RNG_DRBG_NextSeedNow(WC_RNG* rng); \return NOT_READY_E No bank is ready. \return BAD_FUNC_ARG rng is null, or nonce is null with nonceSz nonzero. \return MISSING_RNG_E rng has no DRBG (RDRAND et al.). + \return ENTROPY_RT_E or ENTROPY_APT_E A seed health test failed on + the reseed this performs; the instance is condemned. \return DRBG_CONT_FIPS_E The continuous test failed; the DRBG is out of service. \return RNG_FAILURE_E The DRBG is out of service. diff --git a/doc/dox_comments/header_files/rng_bank.h b/doc/dox_comments/header_files/rng_bank.h index 18f4f999bba..bc2f1311d27 100644 --- a/doc/dox_comments/header_files/rng_bank.h +++ b/doc/dox_comments/header_files/rng_bank.h @@ -402,7 +402,8 @@ int wc_rng_bank_get_inst_id(struct wc_rng_bank_inst *rng_inst); \return 0 Bytes were banked. \return ALREADY_E The instance's bank is ready or being consumed. - \return NOT_READY_E The health test could not run; simply retry. + \return ENTROPY_RT_E or ENTROPY_APT_E The SP 800-90B seed health test + rejected the banked material, which is burned. \return BAD_FUNC_ARG bank is null, inst_offset is out of range, or n is 0. @@ -437,7 +438,8 @@ int wc_rng_bank_next_seed_generate(struct wc_rng_bank *bank, int inst_offset, \return 0 Bytes were banked. \return ALREADY_E The instance's bank is ready or being consumed. - \return NOT_READY_E The health test could not run; simply retry. + \return ENTROPY_RT_E or ENTROPY_APT_E The SP 800-90B seed health test + rejected the banked material, which is burned. \return BAD_FUNC_ARG bank or root is null, inst_offset is out of range, or n is 0. diff --git a/linuxkm/lkcapi_sha_glue.c b/linuxkm/lkcapi_sha_glue.c index 7fa98f6d32e..4acead3bf00 100644 --- a/linuxkm/lkcapi_sha_glue.c +++ b/linuxkm/lkcapi_sha_glue.c @@ -2747,10 +2747,6 @@ static void wc_linuxkm_vmgenid_poll_teardown( * draining nextSeeds as fast as it can. Per-turn classification of * wc_rng_bank_next_seed_generate() returns: * 0 gathered/published -- progress; - * NOT_READY_E transient (incl. a burned bank, which is refill-eligible - * now, and an environmental TestSeed miss with the aperture - * preserved) -- progress, so a forced burn can never induce - * a nap; * ALREADY_E ready or consuming -- no work on this instance; * BUSY_E instance-op gate held by a reinit -- no progress here, * but the gate holder is making it; @@ -3219,7 +3215,7 @@ static int wc_linuxkm_entropy_daemon(void *arg) ret = wc_rng_bank_next_seed_generate( bank, i, WC_LINUXKM_ENTROPY_DAEMON_GRANULE); - if ((ret == 0) || (ret == WC_NO_ERR_TRACE(NOT_READY_E))) { + if (ret == 0) { progress = 1; } else if ((ret == WC_NO_ERR_TRACE(ALREADY_E)) || @@ -3729,6 +3725,16 @@ WC_MAYBE_UNUSED static int linuxkm_InitRng_DefaultRef(WC_RNG* rng) { #define WC_LINUXKM_DRBG_SMALL_LIMIT 8 #endif +/* Reinstantiations attempted when a generate keeps failing. Each one gathers + * a fresh seed and health tests it, so this never re-judges rejected data. */ +#ifndef WC_LINUXKM_DRBG_REINIT_TRIES + #define WC_LINUXKM_DRBG_REINIT_TRIES 3 +#endif +/* Zero would silently turn off recovery rather than bounding it. */ +#if WC_LINUXKM_DRBG_REINIT_TRIES < 1 + #error WC_LINUXKM_DRBG_REINIT_TRIES must be at least 1 +#endif + #ifdef WC_RNG_HAVE_POOL wc_static_assert(WC_LINUXKM_DRBG_SMALL_LIMIT <= WC_LINUXKM_RNG_POOL_SIZE); #endif @@ -3956,13 +3962,18 @@ static int wc_linuxkm_drbg_generate(struct wc_rng_bank *ctx, if (ret == 0) continue; - if (unlikely(ret == WC_NO_ERR_TRACE(RNG_FAILURE_E))) { + /* A seed health-test alarm now arrives as its own SP 800-90B code; + * it is the same recoverable condition as RNG_FAILURE_E. */ + if (unlikely((ret == WC_NO_ERR_TRACE(RNG_FAILURE_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E)))) + { if (slen > 0) break; - if (retried) + if (retried >= WC_LINUXKM_DRBG_REINIT_TRIES) break; - retried = 1; + ++retried; if (! can_wait) break; @@ -4035,13 +4046,13 @@ static int wc_linuxkm_drbg_generate(struct wc_rng_bank *ctx, if (ret == 0) { pr_warn_ratelimited("WARNING: reinitialized DRBG #%d after " - "RNG_FAILURE_E from wc_RNG_GenerateBlock().\n", + "a seed health or RNG failure from wc_RNG_GenerateBlock().\n", wc_rng_bank_get_inst_id(drbg)); continue; } else { pr_err_ratelimited("ERROR: reinitialization of DRBG #%d after " - "RNG_FAILURE_E failed with ret %d.\n", + "a seed health or RNG failure failed with ret %d.\n", wc_rng_bank_get_inst_id(drbg), ret); break; } diff --git a/tests/api/test_random.c b/tests/api/test_random.c index be08fd1d6a0..bd21dd9b4a3 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -413,6 +413,94 @@ int test_wc_RNG_DRBG_Reseed(void) return EXPECT_RESULT(); } +#if defined(HAVE_HASHDRBG) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WC_RNG_SEED_APT_CUTOFF) && \ + !defined(WC_RNG_SEED_APT_WINDOW) && !defined(WC_RNG_SEED_RCT_CUTOFF) +/* Build one APT window whose reference value occurs m+1 times, keeping runs + * under the RCT cutoff so only the APT can fire. Returns -1 if m does not + * fit in the window. */ +static int test_random_apt_window(byte* s, word32 start, word32 win, byte ref, + word32 m) +{ + word32 i, placed = 0, run = 1; + byte filler = 0; + + for (i = 0; i < win; i++) { + if (++filler == ref) + filler++; + s[start + i] = filler; + } + s[start] = ref; + for (i = 1; (i < win) && (placed < m); i++) { + if (run >= 29) { + run = 0; + continue; + } + s[start + i] = ref; + placed++; + run++; + } + return (placed == m) ? 0 : -1; +} + +/* Build one window whose majority value is NOT its reference sample, so the + * 4.4.2 test stays quiet and only the all-values test can fire. */ +static int test_random_allvals_window(byte* s, word32 win, byte ref, byte val, + word32 m) +{ + word32 i, placed = 0, run = 0; + byte filler = 0; + + for (i = 0; i < win; i++) { + if (++filler == val) + filler++; + s[i] = filler; + } + s[0] = ref; + for (i = 1; (i < win) && (placed < m); i++) { + if (run >= 29) { + run = 0; + continue; + } + s[i] = val; + placed++; + run++; + } + return (placed == m) ? 0 : -1; +} + +/* Smallest match count that trips the APT at this window, discovered through + * the public API; 0 when the window cannot reach its cutoff. */ +static word32 test_random_apt_cutoff(byte* buf, word32 win, int allValues) +{ + word32 lo = 2, hi = win, best = 0; + + while (lo <= hi) { + word32 mid = lo + ((hi - lo) / 2); + int built; + + if (allValues) + built = test_random_allvals_window(buf, win, 0x01, 0x99, mid); + else + built = test_random_apt_window(buf, 0, win, 0x11, mid - 1); + + if ((built != 0) || + (wc_RNG_TestSeed(buf, win) != WC_NO_ERR_TRACE(ENTROPY_APT_E))) { + if (built != 0) + hi = mid - 1; + else + lo = mid + 1; + } + else { + best = mid; + hi = mid - 1; + } + } + return best; +} +#endif + int test_wc_RNG_TestSeed(void) { EXPECT_DECLS; @@ -449,6 +537,149 @@ int test_wc_RNG_TestSeed(void) seed[i] = i; ExpectIntEQ(wc_RNG_TestSeed(seed, sizeof(seed)), 0); #endif + +#if defined(HAVE_HASHDRBG) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WC_RNG_SEED_APT_CUTOFF) && \ + !defined(WC_RNG_SEED_APT_WINDOW) && !defined(WC_RNG_SEED_RCT_CUTOFF) + /* SP 800-90B 4.4.2 cutoffs are per window size, 1 + CRITBINOM(W, 2^-H, + * 1-alpha) with H = 1 and alpha = 2^-30: 101 at W = 132 (the FIPS reseed + * seed), 140 at W = 196 (instantiate) and 325 at W = 512. */ + { + byte* buf = (byte*)XMALLOC(1024, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + ExpectNotNull(buf); + if (buf != NULL) { + /* one below the cutoff passes, the cutoff itself fails */ + ExpectIntEQ(test_random_apt_window(buf, 0, 132, 0x11, 101 - 2), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 132), 0); + ExpectIntEQ(test_random_apt_window(buf, 0, 132, 0x11, 101 - 1), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 132), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + ExpectIntEQ(test_random_apt_window(buf, 0, 196, 0x22, 140 - 2), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 196), 0); + ExpectIntEQ(test_random_apt_window(buf, 0, 196, 0x22, 140 - 1), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 196), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + ExpectIntEQ(test_random_apt_window(buf, 0, 512, 0x33, 325 - 2), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 512), 0); + ExpectIntEQ(test_random_apt_window(buf, 0, 512, 0x33, 325 - 1), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 512), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + /* windows do not overlap: bias in the second window is caught */ + ExpectIntEQ(test_random_apt_window(buf, 0, 512, 0x44, 0), 0); + ExpectIntEQ(test_random_apt_window(buf, 512, 512, 0x55, 325 - 1), + 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 1024), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + /* the all-values test (SP 800-90B 4.3 Req 1c) catches bias toward + * a value that is not the window's first byte; its cutoff is + * 1 + CRITBINOM(W, 2^-H, 1-alpha/256), which is 105 at W = 132 */ + { + word32 j, placed = 0, run = 0; + byte filler = 0; + + for (j = 0; j < 132; j++) { + if ((placed < 105) && (run < 28) && (j > 0)) { + buf[j] = 0x99; + placed++; + run++; + } + else { + if (++filler == 0x99) + filler++; + buf[j] = filler; + run = 0; + } + } + buf[0] = 0x01; + ExpectIntEQ(placed, 105); + ExpectIntEQ(wc_RNG_TestSeed(buf, 132), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + buf[1] = 0x02; /* one below the cutoff */ + ExpectIntEQ(wc_RNG_TestSeed(buf, 132), 0); + } + + /* a seed that does not divide into whole windows slides its last + * window back to full length, so the trailing bytes are covered by + * a window whose cutoff is reachable (IG D.K Res 16) */ + ExpectIntEQ(test_random_apt_window(buf, 0, 512, 0x44, 0), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 513), 0); + ExpectIntEQ(test_random_apt_window(buf, 88, 512, 0x66, 325 - 1), 0); + ExpectIntEQ(wc_RNG_TestSeed(buf, 600), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + /* under 30 bytes no cutoff is reachable, so the APT is not applied + * and a short all-same seed is left to the RCT */ + XMEMSET(buf, 0x5a, 20); + ExpectIntEQ(wc_RNG_TestSeed(buf, 20), 0); + + /* Sweep the windows a seed can actually present. seedSz is at + * least SEED_BLOCK_SZ, and no cutoff is reachable below W = 30 + * (38 for the all-values table), so smaller windows are visited + * but only the reachable ones are asserted. That still covers far + * more of the 1024 constants than the few sizes one build uses. */ + { + word32 w; + word32 prev = 0, prevAll = 0; + word32 firstFire = 0, firstFireAll = 0; + int bad = 0, badAll = 0; + + for (w = 4; w <= 512; w++) { + word32 c = test_random_apt_cutoff(buf, w, 0); + word32 a2 = test_random_apt_cutoff(buf, w, 1); + + if (c != 0) { + if (firstFire == 0) + firstFire = w; + /* reachable, and a strict majority of the window */ + if ((c > w) || (c <= (w / 2))) + bad = (bad != 0) ? bad : (int)w; + /* C(W) rises by 0 or 1 as the window grows */ + if ((prev != 0) && ((c < prev) || ((c - prev) > 1))) + bad = (bad != 0) ? bad : (int)w; + prev = c; + } + if (a2 != 0) { + if (firstFireAll == 0) + firstFireAll = w; + if ((a2 > w) || (a2 <= (w / 2))) + badAll = (badAll != 0) ? badAll : (int)w; + if ((prevAll != 0) && + ((a2 < prevAll) || ((a2 - prevAll) > 1))) + badAll = (badAll != 0) ? badAll : (int)w; + /* alpha/256 is stricter, so it never sits lower */ + if ((c != 0) && (a2 < c)) + badAll = (badAll != 0) ? badAll : (int)w; + prevAll = a2; + } + } + /* the failing window, not just "something broke" */ + ExpectIntEQ(bad, 0); + ExpectIntEQ(badAll, 0); + /* Nothing fires below the window where alpha 2^-30 first + * makes a cutoff reachable. This is one-sided on purpose: + * the densest window these helpers can build still keeps + * runs under the RCT cutoff, so the smallest window they can + * actually trip sits above the table's own floor. */ + ExpectIntGE((int)firstFire, 30); + ExpectIntGE((int)firstFireAll, 38); + /* and the anchors the derivation is quoted against */ + ExpectIntEQ((int)test_random_apt_cutoff(buf, 132, 0), 101); + ExpectIntEQ((int)test_random_apt_cutoff(buf, 196, 0), 140); + ExpectIntEQ((int)test_random_apt_cutoff(buf, 512, 0), 325); + ExpectIntEQ((int)test_random_apt_cutoff(buf, 132, 1), 105); + ExpectIntEQ((int)test_random_apt_cutoff(buf, 196, 1), 146); + ExpectIntEQ((int)test_random_apt_cutoff(buf, 512, 1), 334); + } + } + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif return EXPECT_RESULT(); } @@ -1092,6 +1323,110 @@ int test_wc_RNG_SeedCb(void) return EXPECT_RESULT(); } +#if defined(WC_RNG_SEED_CB) && defined(HAVE_HASHDRBG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) && \ + !defined(WC_RNG_SEED_APT_CUTOFF) && !defined(WC_RNG_SEED_APT_WINDOW) && \ + !defined(WC_RNG_SEED_RCT_CUTOFF) +/* Stuck noise source: every seed byte is zero. */ +static int test_random_seedCb_stuck(OS_Seed* os, byte* seed, word32 sz) +{ + (void)os; + XMEMSET(seed, 0, sz); + return 0; +} + +/* Source stuck on one value, with a different byte every 28 so no run can + * reach the RCT cutoff: only the APT can object, at any seed size. */ +static int test_random_seedCb_biased(OS_Seed* os, byte* seed, word32 sz) +{ + word32 i; + byte filler = 0; + + (void)os; + for (i = 0; i < sz; i++) { + if ((i > 0) && ((i % 28) == 0)) { + if (++filler == 0x99) + filler++; + seed[i] = filler; + } + else { + seed[i] = 0x99; + } + } + return 0; +} + +/* Source that fails with something that is not a 90B verdict. */ +static int test_random_seedCb_broken(OS_Seed* os, byte* seed, word32 sz) +{ + (void)os; + (void)seed; + (void)sz; + return WC_NO_ERR_TRACE(BAD_FUNC_ARG); +} +#endif + +/* A stuck seed source at reseed reports the SP 800-90B RCT code. + * rng.pid = 0 never matches the process, so the next generate reseeds. */ +int test_wc_RNG_ReseedVerdict(void) +{ + EXPECT_DECLS; +#if defined(WC_RNG_SEED_CB) && defined(HAVE_HASHDRBG) && \ + !defined(CUSTOM_RAND_GENERATE_BLOCK) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) && \ + !defined(WC_RNG_SEED_APT_CUTOFF) && !defined(WC_RNG_SEED_APT_WINDOW) && \ + !defined(WC_RNG_SEED_RCT_CUTOFF) + WC_RNG rng; + byte out[32]; + + /* Do not inherit whatever source the previous test left installed: under + * HAVE_FIPS seedCb starts NULL and wc_InitRng() would fail. */ + DoExpectIntEQ(wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT), 0); + XMEMSET(&rng, 0, sizeof(WC_RNG)); + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Control: a forced reseed from a working source succeeds. */ + rng.pid = 0; + ExpectIntEQ(wc_RNG_GenerateBlock(&rng, out, sizeof(out)), 0); + + ExpectIntEQ(wc_SetSeed_Cb(test_random_seedCb_stuck), 0); + rng.pid = 0; + ExpectIntEQ(wc_RNG_GenerateBlock(&rng, out, sizeof(out)), + WC_NO_ERR_TRACE(ENTROPY_RT_E)); + /* The failed instance stays failed. */ + ExpectIntEQ(wc_RNG_GenerateBlock(&rng, out, sizeof(out)), + WC_NO_ERR_TRACE(RNG_FAILURE_E)); + + /* A biased source reports the APT verdict out of the generate path. */ + DoExpectIntEQ(wc_FreeRng(&rng), 0); + XMEMSET(&rng, 0, sizeof(WC_RNG)); + DoExpectIntEQ(wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_SetSeed_Cb(test_random_seedCb_biased), 0); + rng.pid = 0; + ExpectIntEQ(wc_RNG_GenerateBlock(&rng, out, sizeof(out)), + WC_NO_ERR_TRACE(ENTROPY_APT_E)); + + /* A source error that is not a 90B verdict still reports RNG_FAILURE_E. */ + DoExpectIntEQ(wc_FreeRng(&rng), 0); + XMEMSET(&rng, 0, sizeof(WC_RNG)); + DoExpectIntEQ(wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_SetSeed_Cb(test_random_seedCb_broken), 0); + rng.pid = 0; + ExpectIntEQ(wc_RNG_GenerateBlock(&rng, out, sizeof(out)), + WC_NO_ERR_TRACE(RNG_FAILURE_E)); + + /* Do-form: restore even after a failed check, or later tests inherit it. */ + DoExpectIntEQ(wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT), 0); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + /* CUSTOM_RAND_GENERATE_BLOCK: an external RNG function bypasses Hash_DRBG * generation entirely in wc_RNG_GenerateBlock() (and _InitRng() itself is * skipped, since it is guarded by diff --git a/tests/api/test_random.h b/tests/api/test_random.h index 1a6d43229ec..84d7a6a289c 100644 --- a/tests/api/test_random.h +++ b/tests/api/test_random.h @@ -40,6 +40,7 @@ int test_wc_RNG_HealthTest_SHA512(void); int test_wc_RNG_HealthTest_SHA256_Ext(void); int test_wc_RNG_HealthTest_SHA512_Ext(void); int test_wc_RNG_SeedCb(void); +int test_wc_RNG_ReseedVerdict(void); int test_wc_RNG_CustomRandBlock(void); int test_wc_RNG_DrbgDisable(void); int test_wc_DrbgDecisionCoverage(void); @@ -63,6 +64,7 @@ int test_wc_Entropy_Get(void); TEST_DECL_GROUP("random", test_wc_RNG_HealthTest_SHA256_Ext), \ TEST_DECL_GROUP("random", test_wc_RNG_HealthTest_SHA512_Ext), \ TEST_DECL_GROUP("random", test_wc_RNG_SeedCb), \ + TEST_DECL_GROUP("random", test_wc_RNG_ReseedVerdict), \ TEST_DECL_GROUP("random", test_wc_RNG_CustomRandBlock), \ TEST_DECL_GROUP("random", test_wc_RNG_DrbgDisable), \ TEST_DECL_GROUP("random", test_wc_DrbgDecisionCoverage), \ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 93e7802b977..c68394545f2 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -2593,18 +2593,11 @@ int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz) return wc_RNG_DRBG_Stir_Nonce(rng, seed, seedSz, NULL, 0); } -/* FIPS 140-3 IG 10.3.A / SP800-90B Health Tests for Seed Data - * - * These tests replace the older FIPS 140-2 Continuous Random Number Generator - * Test (CRNGT) with more mathematically robust statistical tests per - * ISO 19790 / SP800-90B requirements. - * - * When HAVE_ENTROPY_MEMUSE is defined, the wolfentropy.c jitter-based TRNG - * performs another set of these health tests, but those are on the noise not - * the conditioned output so we still need to retest here even in that case - * to evaluate the conditioned output for the same behavior. These tests ensure - * the seed data meets basic entropy requirements regardless of the source. - */ +/* SP800-90B 4.4 health tests run over conditioned seed material, which makes + * them the developer-defined additional tests 4.3 Req 1c allows: 4.2 puts the + * noise source's own tests in that source, not here. A seed shorter than the + * 512 byte window is one window of its own length, with the cutoff for that + * length. */ /* SP800-90B 4.4.1 - Repetition Count Test * Detects if the noise source becomes "stuck" producing repeated output. @@ -2612,10 +2605,17 @@ int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz) * C = 1 + ceil(-log2(alpha) / H) * For alpha = 2^-30 (false positive probability) and H = 1 (min entropy): * C = 1 + ceil(30 / 1) = 31 - */ + * + * H = 1 bit per byte is a deliberate floor, recorded in the Security Policy: + * the seed source is a build choice and the module is not told the assessed + * rate of the one in use, so it assumes the weakest. */ #ifndef WC_RNG_SEED_RCT_CUTOFF #define WC_RNG_SEED_RCT_CUTOFF 31 #endif +/* The run starts at 1, so a cutoff below 2 fails every seed. */ +#if WC_RNG_SEED_RCT_CUTOFF < 2 + #error WC_RNG_SEED_RCT_CUTOFF must be at least 2 +#endif /* SP800-90B 4.4.2 - Adaptive Proportion Test * Monitors if a particular sample value appears too frequently within a @@ -2629,9 +2629,121 @@ int wc_RNG_DRBG_Stir(WC_RNG* rng, const byte* seed, word32 seedSz) #ifndef WC_RNG_SEED_APT_WINDOW #define WC_RNG_SEED_APT_WINDOW 512 #endif +#if WC_RNG_SEED_APT_WINDOW < 1 + #error WC_RNG_SEED_APT_WINDOW must be at least 1 +#endif #ifndef WC_RNG_SEED_APT_CUTOFF + /* No caller-supplied cutoff: take one per window size from the table. */ + #define WC_RNG_SEED_APT_CUTOFF_PER_WINDOW + /* The published W = 512 value, kept for readers and for tests. */ #define WC_RNG_SEED_APT_CUTOFF 325 #endif +/* Only the tables cap the window; a caller-supplied cutoff has no such + * limit, so this bound belongs to the table build alone. */ +#if defined(WC_RNG_SEED_APT_CUTOFF_PER_WINDOW) && (WC_RNG_SEED_APT_WINDOW > 512) + #error WC_RNG_SEED_APT_WINDOW must be 1 to 512 unless WC_RNG_SEED_APT_CUTOFF is set +#endif +/* A caller-supplied cutoff above the window can never be reached at any seed + * size, so it is refused here. A cutoff the window allows but a given seed + * does not is not a build-time property: the window is min(seedSz, WINDOW) and + * seedSz is the caller's, so that case is left to the runtime skip below. */ +#ifndef WC_RNG_SEED_APT_CUTOFF_PER_WINDOW + #if WC_RNG_SEED_APT_CUTOFF < 2 + #error WC_RNG_SEED_APT_CUTOFF must be at least 2 + #endif + #if WC_RNG_SEED_APT_CUTOFF > WC_RNG_SEED_APT_WINDOW + #error WC_RNG_SEED_APT_CUTOFF exceeds the window and can never fire + #endif +#endif + +#ifdef WC_RNG_SEED_APT_CUTOFF_PER_WINDOW +/* C for every window size, not only 512: a seed shorter than the window is one + * window of its own length, and 325 can never be reached in 132 or 196 bytes. + * 1 + CRITBINOM(W, 2^-H, 1 - alpha), H = 1, alpha = 2^-30, W = 1..512, held as + * the exact distance above W / 2 so a byte covers every window. */ +static const byte aptCutoffDelta[512] = { + 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 15, 16, 16, + 17, 17, 18, 17, 18, 18, 19, 18, 19, 19, 20, 19, 20, 20, 21, 20, + 21, 21, 22, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 23, 24, 24, + 25, 24, 25, 25, 25, 25, 26, 25, 26, 26, 26, 26, 27, 26, 27, 27, + 28, 27, 28, 28, 28, 28, 29, 28, 29, 29, 29, 29, 30, 29, 30, 29, + 30, 30, 30, 30, 31, 30, 31, 31, 31, 31, 32, 31, 32, 32, 32, 32, + 33, 32, 33, 33, 33, 33, 33, 33, 34, 33, 34, 34, 34, 34, 35, 34, + 35, 34, 35, 35, 35, 35, 36, 35, 36, 36, 36, 36, 36, 36, 37, 36, + 37, 37, 37, 37, 37, 37, 38, 37, 38, 38, 38, 38, 38, 38, 39, 38, + 39, 39, 39, 39, 39, 39, 40, 39, 40, 39, 40, 40, 40, 40, 41, 40, + 41, 40, 41, 41, 41, 41, 41, 41, 42, 41, 42, 42, 42, 42, 42, 42, + 43, 42, 43, 42, 43, 43, 43, 43, 43, 43, 44, 43, 44, 43, 44, 44, + 44, 44, 45, 44, 45, 44, 45, 45, 45, 45, 45, 45, 46, 45, 46, 45, + 46, 46, 46, 46, 46, 46, 47, 46, 47, 46, 47, 47, 47, 47, 47, 47, + 48, 47, 48, 47, 48, 48, 48, 48, 48, 48, 49, 48, 49, 48, 49, 49, + 49, 49, 49, 49, 49, 49, 50, 49, 50, 49, 50, 50, 50, 50, 50, 50, + 51, 50, 51, 50, 51, 51, 51, 51, 51, 51, 52, 51, 52, 51, 52, 51, + 52, 52, 52, 52, 52, 52, 53, 52, 53, 52, 53, 53, 53, 53, 53, 53, + 53, 53, 54, 53, 54, 53, 54, 54, 54, 54, 54, 54, 54, 54, 55, 54, + 55, 54, 55, 55, 55, 55, 55, 55, 56, 55, 56, 55, 56, 55, 56, 56, + 56, 56, 56, 56, 57, 56, 57, 56, 57, 56, 57, 57, 57, 57, 57, 57, + 57, 57, 58, 57, 58, 57, 58, 58, 58, 58, 58, 58, 58, 58, 59, 58, + 59, 58, 59, 58, 59, 59, 59, 59, 59, 59, 60, 59, 60, 59, 60, 59, + 60, 60, 60, 60, 60, 60, 60, 60, 61, 60, 61, 60, 61, 60, 61, 61, + 61, 61, 61, 61, 62, 61, 62, 61, 62, 61, 62, 62, 62, 62, 62, 62, + 62, 62, 63, 62, 63, 62, 63, 62, 63, 63, 63, 63, 63, 63, 63, 63, + 64, 63, 64, 63, 64, 63, 64, 64, 64, 64, 64, 64, 64, 64, 65, 64, + 65, 64, 65, 64, 65, 65, 65, 65, 65, 65, 65, 65, 66, 65, 66, 65, + 66, 65, 66, 66, 66, 66, 66, 66, 66, 66, 67, 66, 67, 66, 67, 66, + 67, 67, 67, 67, 67, 67, 67, 67, 68, 67, 68, 67, 68, 67, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 69, 68, 69, 68, 69, 68, 69, 69 +}; + #define WC_RNG_SEED_APT_CUTOFF_FOR(w) \ + (((word32)(w) / 2) + (word32)aptCutoffDelta[(w) - 1]) + +/* Cutoff for the all-values test below, at alpha/256 so that scanning the + * whole alphabet keeps the same 2^-30 budget per window. Same W / 2 delta + * form as the table above. + * 1 + CRITBINOM(W, 2^-H, 1 - alpha/256), H = 1, alpha = 2^-30, W = 1..512. */ +static const byte aptAllCutoffDelta[512] = { + 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, + 18, 18, 19, 19, 20, 19, 20, 20, 21, 21, 22, 21, 22, 22, 23, 23, + 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, + 27, 27, 28, 28, 28, 28, 29, 28, 29, 29, 30, 29, 30, 30, 30, 30, + 31, 30, 31, 31, 32, 31, 32, 32, 32, 32, 33, 32, 33, 33, 33, 33, + 34, 34, 34, 34, 35, 34, 35, 35, 35, 35, 36, 35, 36, 36, 36, 36, + 37, 36, 37, 37, 37, 37, 38, 37, 38, 38, 38, 38, 39, 38, 39, 39, + 39, 39, 40, 39, 40, 39, 40, 40, 40, 40, 41, 40, 41, 41, 41, 41, + 42, 41, 42, 42, 42, 42, 42, 42, 43, 42, 43, 43, 43, 43, 44, 43, + 44, 44, 44, 44, 44, 44, 45, 44, 45, 45, 45, 45, 46, 45, 46, 45, + 46, 46, 46, 46, 47, 46, 47, 46, 47, 47, 47, 47, 48, 47, 48, 47, + 48, 48, 48, 48, 49, 48, 49, 48, 49, 49, 49, 49, 50, 49, 50, 49, + 50, 50, 50, 50, 51, 50, 51, 50, 51, 51, 51, 51, 52, 51, 52, 51, + 52, 52, 52, 52, 52, 52, 53, 52, 53, 53, 53, 53, 53, 53, 54, 53, + 54, 53, 54, 54, 54, 54, 54, 54, 55, 54, 55, 55, 55, 55, 55, 55, + 56, 55, 56, 55, 56, 56, 56, 56, 56, 56, 57, 56, 57, 56, 57, 57, + 57, 57, 58, 57, 58, 57, 58, 58, 58, 58, 58, 58, 59, 58, 59, 58, + 59, 59, 59, 59, 59, 59, 60, 59, 60, 59, 60, 60, 60, 60, 60, 60, + 61, 60, 61, 60, 61, 61, 61, 61, 61, 61, 62, 61, 62, 61, 62, 62, + 62, 62, 62, 62, 63, 62, 63, 62, 63, 63, 63, 63, 63, 63, 63, 63, + 64, 63, 64, 63, 64, 64, 64, 64, 64, 64, 65, 64, 65, 64, 65, 65, + 65, 65, 65, 65, 66, 65, 66, 65, 66, 65, 66, 66, 66, 66, 66, 66, + 67, 66, 67, 66, 67, 67, 67, 67, 67, 67, 68, 67, 68, 67, 68, 67, + 68, 68, 68, 68, 68, 68, 69, 68, 69, 68, 69, 69, 69, 69, 69, 69, + 69, 69, 70, 69, 70, 69, 70, 70, 70, 70, 70, 70, 70, 70, 71, 70, + 71, 70, 71, 71, 71, 71, 71, 71, 71, 71, 72, 71, 72, 71, 72, 72, + 72, 72, 72, 72, 72, 72, 73, 72, 73, 72, 73, 73, 73, 73, 73, 73, + 73, 73, 74, 73, 74, 73, 74, 74, 74, 74, 74, 74, 74, 74, 75, 74, + 75, 74, 75, 75, 75, 75, 75, 75, 75, 75, 76, 75, 76, 75, 76, 75, + 76, 76, 76, 76, 76, 76, 77, 76, 77, 76, 77, 76, 77, 77, 77, 77, + 77, 77, 77, 77, 78, 77, 78, 77, 78, 77, 78, 78, 78, 78, 78, 78 +}; + #define WC_RNG_SEED_APT_ALL_CUTOFF_FOR(w) \ + (((word32)(w) / 2) + (word32)aptAllCutoffDelta[(w) - 1]) +#else + /* A caller-supplied cutoff is a threshold, so it serves both tests; the + * all-values scan still runs only where its majority precondition holds. */ + #define WC_RNG_SEED_APT_CUTOFF_FOR(w) ((word32)WC_RNG_SEED_APT_CUTOFF) + #define WC_RNG_SEED_APT_ALL_CUTOFF_FOR(w) ((word32)WC_RNG_SEED_APT_CUTOFF) +#endif int wc_RNG_TestSeed(const byte* seed, word32 seedSz) { @@ -2669,64 +2781,80 @@ int wc_RNG_TestSeed(const byte* seed, word32 seedSz) } } - /* SP800-90B 4.4.2 - Adaptive Proportion Test (APT) - * Check that no single byte value appears too frequently within - * a sliding window. This detects bias in the entropy source. - * - * For seeds smaller than the window size, we test the entire seed. - * For larger seeds, we use a sliding window approach. - * - * Constant-time implementation: always process full seed and check - * all counts to prevent timing side-channels. - */ + /* SP800-90B 4.4.2 Adaptive Proportion Test: the first byte of each window + * is the reference value, and a window fails when matches reach the cutoff + * for that window size. Windows do not overlap except the last, which + * slides back to full length and so re-judges the bytes it covers; that + * only adds windows, so it can raise the false alarm rate but never mask a + * failure. A frequent value that is not the reference is left to the + * all-values test below. */ { - #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) - word16* byteCounts = NULL; - #else - word16 byteCounts[MAX_ENTROPY_BITS]; - #endif - word32 windowSize = min(seedSz, (word32)WC_RNG_SEED_APT_WINDOW); - word32 windowStart = 0; - word32 newIdx; - - #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) - byteCounts = (word16*)XMALLOC(MAX_ENTROPY_BITS * sizeof(word16), NULL, - DYNAMIC_TYPE_TMP_BUFFER); - if (byteCounts == NULL) - return MEMORY_E; - #endif - XMEMSET(byteCounts, 0, MAX_ENTROPY_BITS * sizeof(word16)); - - /* Indices are WC_OCTET-masked: byteCounts has 256 entries, but a - * byte cell can exceed 255 where CHAR_BIT != 8, so an unmasked seed - * value would index out of bounds. */ - for (i = 0; i < windowSize; i++) { - byteCounts[WC_OCTET(seed[i])]++; - } + word32 start; + word32 window = min(seedSz, (word32)WC_RNG_SEED_APT_WINDOW); + word32 cutoff = WC_RNG_SEED_APT_CUTOFF_FOR(window); + + /* A cutoff above the window can never be reached (IG D.K Res 16); that + * is every window under 30 bytes at alpha 2^-30, and such a seed gets + * no APT and rests on the RCT above. */ + if (cutoff <= window) { + /* Constant time: every window is scanned in full, no early exit. */ + for (start = 0; start < seedSz; start += window) { + word32 matches = 1; + byte refByte; + + /* A trailing piece shorter than the window slides back to a + * full window instead of forming a short one. */ + if ((seedSz - start) < window) + start = seedSz - window; + + refByte = seed[start]; + for (i = 1; i < window; i++) { + matches += (word32)(seed[start + i] == refByte); + } - /* Check first window - scan all 256 counts */ - for (i = 0; i < MAX_ENTROPY_BITS; i++) { - aptFailed |= (byteCounts[i] >= WC_RNG_SEED_APT_CUTOFF); + aptFailed |= (matches >= cutoff); + } } + } - /* Slide window through remaining seed data */ - while ((windowStart + windowSize) < seedSz) { - /* Remove byte leaving the window */ - byteCounts[WC_OCTET(seed[windowStart])]--; - windowStart++; + /* Additional developer-defined test (SP800-90B 4.3 Req 1c): 4.4.2 watches + * only the window's first byte, this watches whichever value is the + * window's majority, wherever it falls. Its cutoff uses alpha/256 for the + * alphabet, so it catches a near-majority (334 of 512) and not bias in + * general, and it needs a 38 byte window before it can fire at all. */ + { + word32 start; + word32 window = min(seedSz, (word32)WC_RNG_SEED_APT_WINDOW); + word32 cutoff = WC_RNG_SEED_APT_ALL_CUTOFF_FOR(window); + + if ((cutoff <= window) && (cutoff > (window / 2))) { + for (start = 0; start < seedSz; start += window) { + byte cand = 0; + word32 votes = 0; + word32 count = 0; + + if ((seedSz - start) < window) + start = seedSz - window; + + /* A count above half the window makes that value the window's + * majority, which this vote finds without a histogram. */ + for (i = 0; i < window; i++) { + word32 take = (word32)(votes == 0); + word32 same; + + cand = (byte)((take * seed[start + i]) + + ((1 - take) * cand)); + same = (word32)(seed[start + i] == cand); + votes = (same * (votes + 1)) + ((1 - same) * (votes - 1)); + } - /* Add byte entering the window */ - newIdx = windowStart + windowSize - 1; - byteCounts[WC_OCTET(seed[newIdx])]++; + for (i = 0; i < window; i++) { + count += (word32)(seed[start + i] == cand); + } - /* Accumulate failure flag for new byte's count */ - aptFailed |= (byteCounts[WC_OCTET(seed[newIdx])] >= - WC_RNG_SEED_APT_CUTOFF); + aptFailed |= (count >= cutoff); + } } - - #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) - XFREE(byteCounts, NULL, DYNAMIC_TYPE_TMP_BUFFER); - #endif } /* Set return code based on accumulated failure flags */ @@ -2835,6 +2963,19 @@ int wc_Sha512Drbg_IsDisabled(void) #endif /* HAVE_HASHDRBG */ /* End NIST DRBG Code */ +/* Same condition as both callers: DRBG_FAILURE is a Hash_DRBG internal and + * does not exist without it. */ +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) +static int ReseedSourceFailure(int ret) +{ + if ((ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E))) { + return ret; + } + return DRBG_FAILURE; +} +#endif + /* Semantics of "flags": * * Security attributes are fixed at instantiation and caller-declared -- the @@ -3243,7 +3384,8 @@ static WARN_UNUSED_RESULT int _InitRng(WC_RNG* rng, "ERROR: seedCb in _InitRng() failed with err = %d", ret); #endif - ret = DRBG_FAILURE; + /* mapped once, by the shared arm every seed path falls + * into below */ } } #else @@ -3264,7 +3406,9 @@ static WARN_UNUSED_RESULT int _InitRng(WC_RNG* rng, "ERROR: seed acquisition in _InitRng() failed with err %d", ret); #endif - ret = DRBG_FAILURE; + /* A verdict from the source survives here as it does on the + * reseed path, so instantiate and reseed classify it alike. */ + ret = ReseedSourceFailure(ret); rng->status = DRBG_FAILED; } @@ -3553,6 +3697,28 @@ int wc_InitRngNonce_ex2(WC_RNG* rng, const byte* nonce, word32 nonceSz, heap, devId, NULL, flags); } +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) +/* Map a failed generate or reseed to the return code and rng->status. + * A failed SP 800-90A health test returns DRBG_CONT_FIPS_E. */ +static WC_MAYBE_UNUSED int RngGenerateFailure(WC_RNG* rng, int ret) +{ + if (ret == WC_NO_ERR_TRACE(DRBG_CONT_FAILURE)) { + rng->status = DRBG_CONT_FAILED; + return DRBG_CONT_FIPS_E; + } + + rng->status = DRBG_FAILED; + + /* SP 800-90B RCT and APT failures keep their own code. */ + if ((ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || + (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E))) { + return ret; + } + + return RNG_FAILURE_E; +} +#endif + #if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID) #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) @@ -3577,8 +3743,7 @@ static WARN_UNUSED_RESULT WC_MAYBE_UNUSED int rng_pid_change_check(WC_RNG* rng) #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) ret = PollAndReSeed(rng, NULL, 0); if (ret != DRBG_SUCCESS) { - rng->status = DRBG_FAILED; - ret = RNG_FAILURE_E; + ret = RngGenerateFailure(rng, ret); } #endif @@ -4757,6 +4922,8 @@ int wc_RNG_DRBG_StirRBGC(WC_RNG* rng, WC_RNG* root, #if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) +/* A failed seed source reports DRBG_FAILURE, except an SP 800-90B RCT or APT + * verdict, which keeps its own code so the caller can tell the two apart. */ static WARN_UNUSED_RESULT int PollAndReSeed(WC_RNG* rng, const byte* additional, word32 additionalSz) { @@ -4912,9 +5079,11 @@ int wc_RNG_DRBG_Reseed_Now(WC_RNG* rng, const byte* nonce, word32 nonceSz) else { wc_drbg_reseed_ctr_t ctr = WC_RESEED_INTERVAL; (void)wc_RNG_DRBG_GetReseedCtr(rng, &ctr); + /* A rejected seed never entered the state, so the instantiation is + * still valid and only loses the reseed (SP 800-90A 9.2 returns the + * status; 11.4.1 treats unavailable entropy as a normal-operation + * error). Out of runway is different: that one condemns. */ if (ctr >= WC_RESEED_INTERVAL) { - /* The instance is out of generate runway -- condemn now, matching - * wc_RNG_GenerateBlock()'s behavior for mandatory reseeds. */ rng->status = DRBG_FAILED; } if (ret > 0) { @@ -4936,9 +5105,9 @@ int wc_RNG_DRBG_Reseed_Now(WC_RNG* rng, const byte* nonce, word32 nonceSz) /* Banked-next-seed services. _NextSeedGenerate() banks up to n more * bytes from the module's seed source (clamped to the space remaining; * ALREADY_E when the bank is ready or being consumed), health-testing - * and publishing the bank when it completes (NOT_READY_E when the health - * test could not run and the call should simply be retried); a - * scheduling daemon may call it without owning the instance. + * and publishing the bank when it completes, burning it on a failed + * health test (ENTROPY_RT_E / ENTROPY_APT_E); a scheduling daemon may + * call it without owning the instance. * _NextSeedCurrent() reports the raw aperture value (racy snapshot). * _NextSeedNow() claims a ready bank and performs a source-free * credited reseed with it -- safe in atomic context -- or returns @@ -5268,17 +5437,6 @@ static WARN_UNUSED_RESULT int wc_RNG_DRBG_NextSeedGenerate_local( #endif return 0; } - else if (ret == WC_NO_ERR_TRACE(MEMORY_E)) { - /* wc_RNG_TestSeed() did nothing with the data -- not - * dispositive. Release complete-but-unpublished for a - * later retry; a purge-discard's BUSY_E percolates (the - * retry cause is then the purge, not the test). */ - ret = NextSeedProducerRelease(lenp, seed, nextSeedSz, - (WC_ATOMIC_INT_ARG)nextSeedSz); - if (ret != 0) - return ret; - return NOT_READY_E; - } else if ((ret == WC_NO_ERR_TRACE(ENTROPY_RT_E)) || (ret == WC_NO_ERR_TRACE(ENTROPY_APT_E))) { @@ -5861,11 +6019,13 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) ((rng->RBGCStratum > 0) && (banked_stratum == 0))) { ret = wc_RNG_DRBG_NextSeedNow_local(rng); - if ((ret == WC_NO_ERR_TRACE(DRBG_CONT_FIPS_E)) || - (ret == WC_NO_ERR_TRACE(RNG_FAILURE_E))) - { + /* Key the bail on the instance state, not on a list of codes: + * this leg can now also see ENTROPY_RT_E / ENTROPY_APT_E, and + * falling through would let a later generate overwrite ret and + * report success for a call that already condemned the DRBG. */ + if (rng->status != DRBG_OK) { RngAutoLockExit(rng); - return ret; + return (ret != 0) ? ret : RNG_FAILURE_E; } } } @@ -5890,10 +6050,11 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) #ifdef WC_RNG_HAVE_LOCK if (WOLFSSL_ATOMIC_LOAD(rng->lock) & WC_RNG_LOCK_ENTROPY_INVALIDATED) { - if (PollAndReSeed(rng, NULL, 0) != DRBG_SUCCESS) { - rng->status = DRBG_FAILED; + int reseed_ret = PollAndReSeed(rng, NULL, 0); + if (reseed_ret != DRBG_SUCCESS) { + reseed_ret = RngGenerateFailure(rng, reseed_ret); RngAutoLockExit(rng); - return RNG_FAILURE_E; + return reseed_ret; } } #endif @@ -5960,12 +6121,15 @@ int wc_RNG_GenerateBlock(WC_RNG* rng, byte* output, word32 sz) rng->status = DRBG_CONT_FAILED; } else { - ret = RNG_FAILURE_E; - /* Note, Hash_DRBG_Generate() always leaves the DRBG in a - * self-consistent state, success or failure, and can fail for retryable - * causes (e.g. failed memory allocation), so we only update rng->status - * above. - */ + /* A mandatory reseed above can leave a seed health verdict in ret, and + * that arm has already set rng->status, so keep the SP 800-90B code + * rather than flattening it. Everything else is unchanged: + * Hash_DRBG_Generate() stays self-consistent and can fail for + * retryable causes such as an allocation, so it is not condemned. */ + if ((ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) && + (ret != WC_NO_ERR_TRACE(ENTROPY_APT_E))) { + ret = RNG_FAILURE_E; + } } RngAutoLockExit(rng); #else