diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index e188e2e70f..00d64a0a0f 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -23,7 +23,7 @@ jobs: check_filenames: true check_hidden: true # Add comma separated list of words that occur multiple times that should be ignored (sorted alphabetically, case sensitive) - ignore_words_list: adin,aNULL,brunch,carryIn,chainG,ciph,cLen,cliKs,dout,haveA,inCreated,inOut,inout,larg,LEAPYEAR,Merget,optionA,parm,parms,repid,rIn,userA,ser,siz,te,Te,HSI, + ignore_words_list: adin,aNULL,brunch,carryIn,chainG,ciph,cLen,cliKs,dout,haveA,inCreated,inOut,inout,larg,LEAPYEAR,Merget,optionA,parm,parms,repid,rIn,userA,ser,siz,te,Te,HSI,failT, # The exclude_file contains lines of code that should be ignored. This is useful for individual lines which have non-words that can safely be ignored. exclude_file: '.codespellexcludelines' # To skip files entirely from being processed, add it to the following list: diff --git a/.github/workflows/hostap-vm.yml b/.github/workflows/hostap-vm.yml index 47e053baaf..5d118eb7a0 100644 --- a/.github/workflows/hostap-vm.yml +++ b/.github/workflows/hostap-vm.yml @@ -2,10 +2,14 @@ name: hostap and wpa-supplicant Tests # START OF COMMON SECTION on: - push: - branches: [ 'master', 'main', 'release/**' ] - pull_request: - branches: [ '*' ] + workflow_dispatch: # Allows people to run it manually if they want but + # disables it from running automatically when broken +# To restore this to an auto test delete the above workflow_dispatch line and +# comments and uncomment the below lines for push and pull_request +# push: +# branches: [ 'master', 'main', 'release/**' ] +# pull_request: +# branches: [ '*' ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/doc/dox_comments/header_files/random.h b/doc/dox_comments/header_files/random.h index 9594872a53..ead9011d46 100644 --- a/doc/dox_comments/header_files/random.h +++ b/doc/dox_comments/header_files/random.h @@ -476,7 +476,7 @@ int wc_RNG_DRBG_Reseed(WC_RNG* rng, const byte* seed, word32 seedSz); \return 0 If valid \return BAD_FUNC_ARG If seed is NULL - \return RNG_FAILURE_E Validation failed + \return ENTROPY_RT_E || ENTROPY_APT_E Validation failed \param seed Seed to test \param seedSz Seed size diff --git a/tests/api/test_random.c b/tests/api/test_random.c index 3b586559f2..df53b1062b 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -345,7 +345,12 @@ int test_wc_RNG_TestSeed(void) /* Bad seed as it repeats. */ XMEMSET(seed, 0xa5, sizeof(seed)); /* Return value is DRBG_CONT_FAILURE which is not public. */ + /* Moving forward with the RCT test check LT instead of GT */ +#if !defined(HAVE_FIPS) || ( defined(HAVE_FIPS) && FIPS_VERSION3_GE(7,0,0) ) + ExpectIntLT(wc_RNG_TestSeed(seed, sizeof(seed)), 0); +#else ExpectIntGT(wc_RNG_TestSeed(seed, sizeof(seed)), 0); +#endif /* Good seed. */ for (i = 0; i < (byte)sizeof(seed); i++) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 4a50de6cbb..0ac9380875 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -746,22 +746,138 @@ static int Hash_DRBG_Uninstantiate(DRBG_internal* drbg) } +/* 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 + * already performs these health tests, so we can skip them here to avoid + * duplicate testing overhead. However, when customers use custom seed + * callbacks (e.g., for hardware TRNGs with ESV certs), these tests ensure + * the seed data meets entropy requirements when we can not be 100% positive + * that the entropy source is implementing these tests since we don't control + * it's implementation. + */ + +/* SP800-90B 4.4.1 - Repetition Count Test + * Detects if the noise source becomes "stuck" producing repeated output. + * + * C = 1 + ceil(-log2(alpha) / H) + * For alpha = 2^-30 (false positive probability) and H = 1 (min entropy): + * C = 1 + ceil(30 / 1) = 31 + */ +#ifndef WC_RNG_SEED_RCT_CUTOFF + #define WC_RNG_SEED_RCT_CUTOFF 31 +#endif + +/* SP800-90B 4.4.2 - Adaptive Proportion Test + * Monitors if a particular sample value appears too frequently within a + * window of samples, indicating loss of entropy. + * + * Window size W = 512 for non-binary alphabet (byte values 0-255) + * C = 1 + CRITBINOM(W, 2^(-H), 1-alpha) + * For alpha = 2^-30 and H = 1, W = 512: + * C = 1 + CRITBINOM(512, 0.5, 1-2^-30) = 325 + */ +#ifndef WC_RNG_SEED_APT_WINDOW + #define WC_RNG_SEED_APT_WINDOW 512 +#endif +#ifndef WC_RNG_SEED_APT_CUTOFF + #define WC_RNG_SEED_APT_CUTOFF 325 +#endif + int wc_RNG_TestSeed(const byte* seed, word32 seedSz) { int ret = 0; - /* Check the seed for duplicate words. */ - word32 seedIdx = 0; - word32 scratchSz = min(SEED_BLOCK_SZ, seedSz - SEED_BLOCK_SZ); + /* It would be desirable to skip a second round of RCT/ADP testing when + * HAVE_ENTROPY_MEMUSE is enabled and already doing these tests on the + * entropy, however it is run on the "unconditioned noise" so once it + * arrives here in TestSeed it's now been conditioned and should in fact + * be checked again before being consumed by the DRBG */ + word32 i; + int rctFailed = 0; + int aptFailed = 0; - while (seedIdx < seedSz - SEED_BLOCK_SZ) { - if (ConstantCompare(seed + seedIdx, - seed + seedIdx + scratchSz, - (int)scratchSz) == 0) { - ret = DRBG_CONT_FAILURE; + if (seed == NULL || seedSz == 0) { + return BAD_FUNC_ARG; + } + + /* SP800-90B 4.4.1 - Repetition Count Test (RCT) + * Check for consecutive identical bytes that would indicate a stuck + * entropy source. Fail if we see WC_RNG_SEED_RCT_CUTOFF or more + * consecutive identical values. + * + * Constant-time implementation: always process full seed, accumulate + * failure status without early exit to prevent timing side-channels. + */ + { + int repCount = 1; + byte prevByte = seed[0]; + + for (i = 1; i < seedSz; i++) { + /* Constant-time: always evaluate both branches effects */ + int match = (seed[i] == prevByte); + /* If match, increment count, if not, reset to 1 */ + repCount = (match * (repCount + 1)) + (!match * 1); + /* Update prevByte only when not matching (new value) */ + prevByte = (byte) ((match * prevByte) + (!match * seed[i])); + /* Accumulate failure flag - once set, stays set */ + rctFailed |= (repCount >= WC_RNG_SEED_RCT_CUTOFF); + } + } + + /* 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. + */ + { + word16 byteCounts[MAX_ENTROPY_BITS]; + word32 windowSize = min(seedSz, (word32)WC_RNG_SEED_APT_WINDOW); + word32 windowStart = 0; + word32 newIdx; + + XMEMSET(byteCounts, 0, sizeof(byteCounts)); + + /* Initialize counts for first window */ + for (i = 0; i < windowSize; i++) { + byteCounts[seed[i]]++; + } + + /* Check first window - scan all 256 counts */ + for (i = 0; i < MAX_ENTROPY_BITS; i++) { + aptFailed |= (byteCounts[i] >= WC_RNG_SEED_APT_CUTOFF); + } + + /* Slide window through remaining seed data */ + while ((windowStart + windowSize) < seedSz) { + /* Remove byte leaving the window */ + byteCounts[seed[windowStart]]--; + windowStart++; + + /* Add byte entering the window */ + newIdx = windowStart + windowSize - 1; + byteCounts[seed[newIdx]]++; + + /* Accumulate failure flag for new byte's count */ + aptFailed |= (byteCounts[seed[newIdx]] >= WC_RNG_SEED_APT_CUTOFF); } - seedIdx += SEED_BLOCK_SZ; - scratchSz = min(SEED_BLOCK_SZ, (seedSz - seedIdx)); + } + + /* Set return code based on accumulated failure flags */ + if (rctFailed) { + ret = ENTROPY_RT_E; + } + else if (aptFailed) { + ret = ENTROPY_APT_E; } return ret; diff --git a/wolfcrypt/test/README.md b/wolfcrypt/test/README.md index 1675566442..6ca6de89d3 100644 --- a/wolfcrypt/test/README.md +++ b/wolfcrypt/test/README.md @@ -58,3 +58,185 @@ For building wolfCrypt test project in Visual Studio open the `test.sln`. For ne If you see an error about `rc.exe` then you'll need to update the "Target Platform Version". You can do this by right-clicking on the test project -> General -> "Target Platform Version" and changing to 8.1 (needs to match the wolfssl library project). This solution includes the wolfSSL library project at `wolfssl.vcxproj` and will compile the library, then the test project. + +-------- + +Jan 2026 - Reviewing the older FIPS compliant CRNGT test specified in FIPS 140-2 +ss 4.9.2 vs the newer replacement tests RCT/ADP that are allowed to replace the +CRNGT under the new FIPS 140-3 / ISO 19790 standard. + +================================================================================ +DRBG Continuous Health Test Statistical Analysis & Diagnostic Report +================================================================================ + +OVERVIEW +-------- +This document describes the statistical false positive behavior of the DRBG +continuous health test in wc_RNG_TestSeed() and provides diagnostic tools to +distinguish between: + 1. Statistical false positives (expected behavior) + 2. Entropy source depletion (under heavy concurrent load) + 3. Actual stuck entropy source (hardware failure) + + +BACKGROUND: THE ISSUE +--------------------- +The DRBG was experiencing high volumes of (DRBG_CONT_FIPS_E) on wc_InitRng() +calls. + +Example error: + ERROR: wc_InitRng failed at iteration 330788 with code -209 + +This raises the question: Is this a bug in wc_RNG_TestSeed() or expected +statistical behavior? + + +STATISTICAL ANALYSIS +-------------------- + +The wc_RNG_TestSeed() Function Behavior: + - Compares ALL consecutive SEED_BLOCK_SZ chunks in the seed buffer + - With FIPS mode (typical configuration): + SEED_SZ = 256 * 4 / 8 = 128 bytes (1024-bits) + SEED_BLOCK_SZ = 4 bytes (default) (32-bits) + seedSz passed to test = 132 bytes (SEED_SZ + SEED_BLOCK_SZ) + Number of comparisons = ~32 consecutive block pairs + +False Positive Probability Calculation: + - Probability one 4-byte block equals another random 4-byte block: 1/2^32 + - With 32 comparisons per seed: 32/2^32 ≈ 1 in 134 million per wc_InitRng() + +Test Configuration (Default): + - 40 threads × 100M iterations = 4 BILLION total wc_InitRng() calls + - Expected false positives: 4,000,000,000 × (32/2^32) ≈ 30 failures + +Conclusion: + Seeing failures around 1 in 30-140 million is EXPECTED STATISTICAL BEHAVIOR. + Under heavy concurrent load (40 threads), entropy source + depletion can also cause legitimate failures. + + +TESTING IT +-------------------- + +Non-FIPS: + + ./configure CFLAGS="-DWC_RNG_SEED_DEBUG -DREALLY_LONG_DRBG_CONTINUOUS_TEST" + make + ./wolfcrypt/test/testwolfcrypt + +FIPS: + + ./configure --enable-fips= CFLAGS="-DWC_RNG_SEED_DEBUG -DREALLY_LONG_DRBG_CONTINUOUS_TEST" + make + ./fips-hash.sh + make + ./wolfcrypt/test/testwolfcrypt + + +OUTPUTS EXPECTED +-------------------- + +Non-FIPS: + + Math: Multi-Precision: Wolf(SP) word-size=64 bits=4096 sp_int.c + ------------------------------------------------------------------------------ + wolfSSL version 5.8.4 + ------------------------------------------------------------------------------ + macro test passed! + error test passed! + MEMORY test passed! + base64 test passed! + asn test passed! + MD5 test passed! + SHA test passed! + SHA-224 test passed! + SHA-256 test passed! + SHA-384 test passed! + SHA-512 test passed! + SHA-512/224 test passed! + SHA-512/256 test passed! + SHA-3 test passed! + RNG Entropy Source: getrandom() syscall + =============================================== + DRBG Continuous Test Validation Suite + =============================================== + FIPS Build: NO + + --- Test 1: Basic RNG Functionality --- + Generated 32 random bytes successfully + [PASS] Basic RNG Functionality + + --- Test 2: Multiple RNG Instances --- + Successfully operated 100 RNG instances concurrently + [PASS] Multiple RNG Instances + + --- Test 3: FIPS Status Check --- + SKIPPED: FIPS not enabled + [PASS] FIPS Status Check + + --- Test 4: RNG ReInit Test (multi-threaded) --- + Configuration: 40 threads × 100000000 iterations = 4000000000 total + Test Profile: Default (Aggressive multi-threaded) + Expected statistical false positive rate: ~29.80 failures + Duplicate block at offset 4: + Block 1: E6 E9 D1 7B + Block 2: E6 E9 D1 7B + Full seed buffer (52 bytes): + DA 93 B7 88 E6 E9 D1 7B E6 E9 D1 7B A5 4C C9 E9 + 13 EE D8 4C B3 C1 71 DE 32 37 17 F2 E7 A4 29 7D + 9B 02 B0 0C EC 8D AC F5 DA B1 71 05 84 C0 61 75 + 59 6D 87 B5 + ERROR: wc_InitRng failed at iteration 778551 with code -209 + ERROR: wc_RNG_GenerateBlock failed at iteration 778551 with code -199 +... + (18 other failures truncated here for brevity) +... + Duplicate block at offset 16: + Block 1: C1 19 37 B1 + Block 2: C1 19 37 B1 + Full seed buffer (52 bytes): + 62 66 5B D2 F5 54 47 9B 59 DD 0A 55 4B 52 8C 39 + C1 19 37 B1 C1 19 37 B1 3F 62 CB 2E FE 56 65 4D + 4F 0C A7 7D 1C 09 48 51 30 1B CA 00 56 9F 29 A7 + E3 93 EF 8E + ERROR: wc_InitRng failed at iteration 90467867 with code -209 + ERROR: wc_RNG_GenerateBlock failed at iteration 90467867 with code -199 + Thread 0 Succeeded +... + 38 other thread results truncated here for brevity (all threads succeeded + even though they experienced 1 or 2 failures in several of the threads) +... + Thread 39 Succeeded + Reinitialized RNG 4000000000 times across 40 threads + Experienced 0 thread failures and 40 thread successes + 20/4000000000 API calls failed <--- This is the bread and the butter of the + test, we unfortunately expect to see + ~29.80 failures, prior to the newer FIPS + 140-3 RCT and ADP tests the CRNGT was + required. Now the CRNGT is replaceable + by the more mathematically robust + RCT/ADP. + [PASS] RNG Reinitialization + + + +TESTING RESULTS with the CRNGT test: +-------------------- + +Old implementation non-FIPS: + Run 1 - 6 failures in 4 billion runs (100M per thread, 40 threads) + Run 2 - 11 failures in 4 billion (100M per thread, 40 threads) + Run 3 - 13 failures in 4 billion (100M per thread, 40 threads) + +Old implementation with FIPS: +(keeping in mind just a single failure means catastrophic +failure for the entire module until power cycled): + Run 1 - 3990118689 failures in 4 billion API calls (yikes) + +TESTING RESULTS with the RCT/ADP tests in place of the CRNGT test: + +New implementation non-FIPS: 4 billion successes +New implementation FIPS: 4 billion successes + + diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 54f8d72213..2b4e3117ac 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -863,6 +863,21 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_siv_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_eax_test(void); #endif /* WOLFSSL_AES_EAX */ +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + #ifdef WOLFSSL_PTHREADS + #include + #include + #endif + /* Prototypes */ + void print_result(const char* test_name, int passed, int *all_passed); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_basic_rng(void); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_multiple_rngs(void); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_stress_rng(unsigned long iterations); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_reinit_rng(void); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_fips_status(void); + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t drbg_continuous_main(void); +#endif + /* General big buffer size for many tests. */ #define FOURK_BUF 4096 @@ -1006,9 +1021,15 @@ typedef struct func_args { /* Kernel modules implement and install their own FIPS callback with similar * functionality. */ +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + int only_run_cb_once = 1; +#endif #if defined(HAVE_FIPS) && !defined(WOLFSSL_KERNEL_MODE) static void myFipsCb(int ok, int err, const char* hash) { +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + if (only_run_cb_once == 1) { +#endif printf("in my Fips callback, ok = %d, err = %d\n", ok, err); printf("message = %s\n", wc_GetErrorString(err)); printf("hash = %s\n", hash); @@ -1017,6 +1038,14 @@ static void myFipsCb(int ok, int err, const char* hash) printf("In core integrity hash check failure, copy above hash\n"); printf("into verifyCore[] in fips_test.c and rebuild\n"); } +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + only_run_cb_once = 0; + } else { + (void) ok; + (void) err; + (void) hash; + } +#endif } #endif /* HAVE_FIPS && !WOLFSSL_KERNEL_MODE */ @@ -19867,6 +19896,10 @@ static wc_test_ret_t random_rng_test(void) } #endif +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + ret = drbg_continuous_main(); +#endif + return ret; } @@ -63645,6 +63678,491 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_siv_test(void) } #endif +#ifdef REALLY_LONG_DRBG_CONTINUOUS_TEST + /* Test configuration */ + #define STRESS_TEST_ITERATIONS 4000000000UL /* 100 million iterations */ + #define PROGRESS_INTERVAL 10000000UL /* Report every 10M */ + #define BUFFER_SIZE 32 + + /* Color codes for output */ + #define COLOR_GREEN "\033[0;32m" + #define COLOR_RED "\033[0;31m" + #define COLOR_YELLOW "\033[0;33m" + #define COLOR_RESET "\033[0m" + + void print_result(const char* test_name, int passed, int *all_passed) + { + printf("[%s] %s\n", + (passed == 0) ? COLOR_GREEN "PASS" COLOR_RESET : + COLOR_RED "FAIL" COLOR_RESET, + test_name); + if (passed != 0) { + printf("Test result was %d\n", passed); + *all_passed = 0; + } + } + + /* Test 1: Basic RNG functionality */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_basic_rng(void) + { + WC_RNG rng; + byte buffer[BUFFER_SIZE]; + int ret = 0; + + printf("--- Test 1: Basic RNG Functionality ---\n"); + + ret = wc_InitRng(&rng); + if (ret != 0) { + printf("ERROR: wc_InitRng failed with code %d: %s\n", + ret, wc_GetErrorString(ret)); + return ret; + } + + /* Generate some random bytes */ + ret = wc_RNG_GenerateBlock(&rng, buffer, BUFFER_SIZE); + if (ret != 0) { + printf("ERROR: wc_RNG_GenerateBlock failed with code %d: %s\n", + ret, wc_GetErrorString(ret)); + wc_FreeRng(&rng); + return ret; + } + + /* Check that buffer is not all zeros */ + int all_zeros = 1; + for (int i = 0; i < BUFFER_SIZE; i++) { + if (buffer[i] != 0) { + all_zeros = 0; + break; + } + } + + if (all_zeros) { + printf("ERROR: RNG generated all zeros\n"); + wc_FreeRng(&rng); + return DRBG_CONT_FIPS_E; + } + + printf("Generated %d random bytes successfully\n", BUFFER_SIZE); + ret = wc_FreeRng(&rng); + + return ret; + } + + /* Test 2: Multiple RNG instances */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_multiple_rngs(void) + { + #define NUM_RNGS 100 + WC_RNG rngs[NUM_RNGS]; + byte buffers[NUM_RNGS][BUFFER_SIZE]; + int ret = 0; + int i, j; + int all_same = 1; + + printf("\n--- Test 2: Multiple RNG Instances ---\n"); + + /* Initialize all RNGs */ + for (i = 0; i < NUM_RNGS; i++) { + ret = wc_InitRng(&rngs[i]); + if (ret != 0) { + printf("ERROR: wc_InitRng[%d] failed with code %d\n", i, ret); + /* Clean up any initialized RNGs */ + for (j = 0; j < i; j++) { + wc_FreeRng(&rngs[j]); + } + return ret; + } + } + + /* Generate random data from all RNGs */ + for (i = 0; i < NUM_RNGS; i++) { + ret = wc_RNG_GenerateBlock(&rngs[i], buffers[i], BUFFER_SIZE); + if (ret != 0) { + printf("ERROR: wc_RNG_GenerateBlock[%d] failed with code %d\n", + i, ret); + for (j = 0; j < NUM_RNGS; j++) { + wc_FreeRng(&rngs[j]); + } + return ret; + } + } + + /* Verify outputs are different (not all the same) */ + for (i = 1; i < NUM_RNGS; i++) { + if (memcmp(buffers[0], buffers[i], BUFFER_SIZE) != 0) { + all_same = 0; + break; + } + } + + if (all_same) { + printf("WARNING: All RNG outputs are identical (unexpected)\n"); + } + + /* Clean up */ + for (i = 0; i < NUM_RNGS; i++) { + wc_FreeRng(&rngs[i]); + } + + if (ret == 0) { + printf("Successfully operated %d RNG instances concurrently\n", + NUM_RNGS); + } else { + printf("Experienced failure %d\n", ret); + } + + return ret; + } + + /* Test 3: Stress test - run many iterations to detect false positives */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_stress_rng( + unsigned long iterations) + { + WC_RNG rng; + byte buffer[BUFFER_SIZE]; + int ret = 0; + unsigned long i; + unsigned long errors = 0; + + printf("\n--- Test 5: Stress Test (%lu iterations) ---\n", iterations); + printf("Verifies no false positive continuous test failures occur.\n"); + + ret = wc_InitRng(&rng); + if (ret != 0) { + printf("ERROR: wc_InitRng failed with code %d\n", ret); + return ret; + } + + for (i = 0; i < iterations; i++) { + ret = wc_RNG_GenerateBlock(&rng, buffer, BUFFER_SIZE); + + if (ret == DRBG_CONT_FIPS_E) { + printf("\n" COLOR_RED "ERROR: DRBG_CONT_FIPS_E at iteration %lu" + COLOR_RESET "\n", i); + printf("False positive found in the continuous test!\n"); + errors++; + break; + } + else if (ret != 0) { + printf("\n" COLOR_RED "ERROR: %d ret code at iteration %lu: %s" + COLOR_RESET "\n", ret, i, wc_GetErrorString(ret)); + errors++; + break; + } + + /* Progress reporting */ + if ((i + 1) % PROGRESS_INTERVAL == 0) { + printf(" Progress: %lu iterations (%.1f%%)...\r", + i + 1, (double)(i + 1) / iterations * 100.0); + fflush(stdout); + } + } + + printf("\n"); + + wc_FreeRng(&rng); + + if (errors == 0) { + printf(COLOR_GREEN "Completed %lu iterations, no false positives!" + COLOR_RESET "\n", iterations); + return 0; + } else { + printf(COLOR_RED "Test failed with %lu errors" COLOR_RESET "\n", + errors); + return ret; + } + } + + /* Test 4: Reinitialize RNG multiple times */ +#ifndef WOLFSSL_PTHREADS + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_reinit_rng(void) + { + WC_RNG rng; + byte buffer[BUFFER_SIZE]; + int ret = 0; + unsigned long i; + unsigned long REINIT_COUNT = STRESS_TEST_ITERATIONS; + + printf("\n--- Test 4: RNG Reinitialization Test ---\n"); + + for (i = 0; i < REINIT_COUNT; i++) { + ret = wc_InitRng(&rng); + if (ret != 0) { +#if defined(HAVE_FIPS) && defined(VERBOSE_STRESS_TEST) +/* SUPER noisy default on when not FIPS and off when FIPS */ + printf("ERROR: wc_InitRng failed at iteration %lu with code " + "%d\n", i, ret); +#endif + return ret; + } + + ret = wc_RNG_GenerateBlock(&rng, buffer, BUFFER_SIZE); + if (ret != 0) { +#if defined(HAVE_FIPS) && defined(VERBOSE_STRESS_TEST) +/* SUPER noisy default on when not FIPS and off when FIPS */ + printf("ERROR: wc_RNG_GenerateBlock failed at iteration %lu " + "with code %d\n", i, ret); +#endif + wc_FreeRng(&rng); + return ret; + } + + wc_FreeRng(&rng); + } + + printf("Successfully reinitialized RNG %lu times\n", REINIT_COUNT); + + return 0; + } +#else + /* RNG ReInit Test Configuration + * + * You can override these at compile time with -DNUM_THREADS=X + * or use one of the predefined test profiles below: + * + * Profile A (Default - Aggressive): 40 threads × 100M = 4B iterations + * - Expected ~30 statistical false positives + * - High entropy source stress + * - Total entropy demand: ~528 GB + * + * Profile B (Moderate): 4 threads × 1M = 4M iterations + * - Expected ~0.03 statistical false positives (unlikely to see any) + * - Moderate entropy source stress + * - Compile with: -DRNG_TEST_PROFILE=2 + * + * Profile C (Single-threaded): 1 thread × 100M = 100M iterations + * - Expected ~0.75 statistical false positives + * - Tests for false positives without threading stress + * - Compile with: -DRNG_TEST_PROFILE=3 + */ + + #if defined(RNG_TEST_PROFILE) && (RNG_TEST_PROFILE == 2) + /* Profile B: Moderate test - reduced entropy stress */ + #ifndef NUM_THREADS + #define NUM_THREADS 4 + #endif + #ifndef ITERATIONS_PER_THREAD + /* Only testing 400 million, expect 1 or 0 failures */ + #define ITERATIONS_PER_THREAD 1000000 + #endif + #elif defined(RNG_TEST_PROFILE) && (RNG_TEST_PROFILE == 3) + /* Profile C: Single-threaded stress - no thread contention */ + #ifndef NUM_THREADS + #define NUM_THREADS 1 + #endif + #ifndef ITERATIONS_PER_THREAD + /* Test 4 billion with no thread contention and less likely to + * deplete entropy pool, expect 0 failures */ + #define ITERATIONS_PER_THREAD 4000000000 + #endif + #else + /* Profile A (Default): Aggressive multi-threaded test */ + #ifndef NUM_THREADS + #define NUM_THREADS 40 + #endif + #ifndef ITERATIONS_PER_THREAD + /* Test 4 billion with high probability of entropy depletion. + * expect many failures (30+ threads failing) */ + #define ITERATIONS_PER_THREAD 100000000 + #endif + #endif + + struct worker_args { + int id; + unsigned long iterations; + int result; + unsigned long succCnt; + }; + + static THREAD_RETURN WOLFSSL_THREAD reinit_worker(void *args) + { + struct worker_args* wa = (struct worker_args*)args; + WC_RNG rng; + byte buffer[BUFFER_SIZE]; + int ret = 0; + unsigned long i; + + for (i = 0; i < wa->iterations; i++) { + ret = wc_InitRng(&rng); + if (ret != 0) { +#if defined(HAVE_FIPS) && defined(VERBOSE_STRESS_TEST) +/* SUPER noisy default on when not FIPS and off when FIPS */ + printf("ERROR: wc_InitRng failed at iteration %lu with code " + "%d\n", i, ret); +#endif + wa->result = ret; + wa->succCnt -= 1; + } + + ret = wc_RNG_GenerateBlock(&rng, buffer, BUFFER_SIZE); + if (ret != 0) { +#if defined(HAVE_FIPS) && defined(VERBOSE_STRESS_TEST) +/* SUPER noisy default on when not FIPS and off when FIPS */ + printf("ERROR: wc_RNG_GenerateBlock failed at iteration %lu " + "with code %d\n", i, ret); +#endif + wc_FreeRng(&rng); + wa->result = ret; + } + + (void) wc_FreeRng(&rng); + } + wa->result = 0; + WOLFSSL_RETURN_FROM_THREAD(0); + } + + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_reinit_rng(void) + { + THREAD_TYPE threads[NUM_THREADS]; + struct worker_args args[NUM_THREADS]; + int i, ret = 0; + int succT = NUM_THREADS, failT = 0; + unsigned long totalCnt, totalSuccCnt = 0; + unsigned long total_iterations = (unsigned long)NUM_THREADS * + ITERATIONS_PER_THREAD; + + totalCnt = (unsigned long) ITERATIONS_PER_THREAD * + (unsigned long) NUM_THREADS; + printf("\n--- Test 4: RNG ReInit Test (multi-threaded) ---\n"); + printf("Configuration: %d threads × %lu iterations = %lu total\n", + NUM_THREADS, (unsigned long)ITERATIONS_PER_THREAD, + total_iterations); + #if defined(RNG_TEST_PROFILE) + printf("Test Profile: %d ", RNG_TEST_PROFILE); + #if RNG_TEST_PROFILE == 2 + printf("(Moderate - reduced entropy stress)\n"); + #elif RNG_TEST_PROFILE == 3 + printf("(Single-threaded stress test)\n"); + #else + printf("(Custom)\n"); + #endif + #else + printf("Test Profile: Default (Aggressive multi-threaded)\n"); + #endif + printf("Expected statistical false positive rate: ~%.2f failures\n", + (double)total_iterations * 32.0 / 4294967296.0); + + for (i = 0; i < NUM_THREADS; i++) { + args[i].id = i; + args[i].iterations = ITERATIONS_PER_THREAD; + args[i].succCnt = ITERATIONS_PER_THREAD; + ret = wolfSSL_NewThread(&threads[i], &reinit_worker, &args[i]); + if (ret != 0) { + printf("ERROR: Failed to create thread %d\n", i); + goto drbg_cont_end; + } + } + + for (i = 0; i < NUM_THREADS; i++) { + wolfSSL_JoinThread(threads[i]); + } + + for (i = 0; i < NUM_THREADS; i++) { + if (args[i].result == 0) { + printf("Thread %d Succeeded\n", i); + } else { + succT -= 1; + failT += 1; + printf("Thread %d failed\n", i); + } + totalSuccCnt += args[i].succCnt; + } + +drbg_cont_end: + printf("Reinitialized RNG %lu times across %d threads\n", + total_iterations, NUM_THREADS); + printf("Experienced %d thread failures and %d thread successes\n", + failT, succT); + if (totalCnt == totalSuccCnt) { + printf("All %lu API calls succeeded\n", totalSuccCnt); + } else { + printf("%lu/%lu API calls failed\n", totalCnt - totalSuccCnt, + totalCnt); + } + return failT; + } +#endif /* !WOLFSSL_PTHREADS */ + + /* Test 5: FIPS status check */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t test_fips_status(void) + { + #ifdef HAVE_FIPS + int status; + + printf("\n--- Test 3: FIPS Status Check ---\n"); + + status = wolfCrypt_GetStatus_fips(); + + printf("FIPS Module Status: %d\n", status); + + if (status != 0) { + printf(COLOR_YELLOW "WARNING: FIPS module not OK state (status=%d)" + COLOR_RESET "\n", status); + return -1; + } else { + printf(COLOR_GREEN "FIPS module in OK state" COLOR_RESET "\n"); + return 0; + } + #else + printf("\n--- Test 3: FIPS Status Check ---\n"); + printf(COLOR_YELLOW "SKIPPED: FIPS not enabled" COLOR_RESET "\n"); + return 0; /* Not a failure, just skipped */ + #endif + } + + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t drbg_continuous_main(void) + { + int all_passed = 1; + unsigned long stress_iterations = STRESS_TEST_ITERATIONS; + + printf("===============================================\n"); + printf("DRBG Continuous Test Validation Suite\n"); + printf("===============================================\n"); + + #ifdef HAVE_FIPS + printf("FIPS Build: YES\n"); + printf("FIPS Version: %s\n", wolfCrypt_GetVersion_fips()); + #else + printf("FIPS Build: NO\n"); + #endif + printf("\n"); + + /* Run tests */ + /* Test 1 */ + print_result("Basic RNG Functionality", test_basic_rng(), &all_passed); + + /* Test 2 */ + print_result("Multiple RNG Instances", test_multiple_rngs(), + &all_passed); + + /* Test 3 */ + print_result("FIPS Status Check", test_fips_status(), &all_passed); + + /* Test 4 */ + print_result("RNG Reinitialization", test_reinit_rng(), &all_passed); + + /* Stress test (takes longest) */ + /* Test 5 */ + print_result("Stress Test (No False Positives)", + test_stress_rng(stress_iterations), &all_passed); + + /* Test 3 - ReRun after the heavy stress tests */ + print_result("FIPS Status Check", test_fips_status(), &all_passed); + + /* Summary */ + printf("\n===============================================\n"); + if (all_passed) { + printf(COLOR_GREEN "ALL TESTS PASSED" COLOR_RESET "\n"); + printf("The DRBG continuous test fix is working correctly.\n"); + } else { + printf(COLOR_RED "SOME TESTS FAILED" COLOR_RESET "\n"); + printf("Please review the errors above.\n"); + } + printf("===============================================\n"); + + return all_passed ? 0 : 1; + } +#endif /* REALLY_LONG_DRBG_CONTINUOUS_TEST */ + #undef ERROR_OUT static WC_MAYBE_UNUSED const int fiducial4 = WC_TEST_RET_LN; diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 6381c2f7a7..ef76407cb9 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -179,6 +179,9 @@ struct OS_Seed { /* wolfentropy.h will define for HAVE_ENTROPY_MEMUSE */ #ifdef HAVE_ENTROPY_MEMUSE #include +#else + /* Maximum entropy bits that can be produced. */ + #define MAX_ENTROPY_BITS 256 #endif /* ENTROPY_SCALE_FACTOR is unprefixed for backward compat. */