Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/hostap-vm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion doc/dox_comments/header_files/random.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions tests/api/test_random.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand Down
136 changes: 126 additions & 10 deletions wolfcrypt/src/random.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
182 changes: 182 additions & 0 deletions wolfcrypt/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-root>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=<flavor> 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


Loading