From a261c17b141bfc79d155e3898fee1f6217d4811b Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 28 Oct 2026 18:43:54 -0700 Subject: [PATCH] rng_noisesrc: move the generic SP800-90B noise source out of random.c --- .github/workflows/ti-c2000-compile.yml | 32 +- CMakeLists.txt | 9 + IDE/C2000/README.md | 60 ++- IDE/C2000/compile.sh | 29 +- cmake/functions.cmake | 7 + cmake/options.h.in | 2 + configure.ac | 20 + doc/dox_comments/header_files/rng_noisesrc.h | 173 ++++++ src/include.am | 5 + wolfcrypt/src/port/ti/ti-c2000-entropy.c | 18 +- wolfcrypt/src/random.c | 484 +---------------- wolfcrypt/src/rng_noisesrc.c | 522 +++++++++++++++++++ wolfcrypt/test/test.c | 253 ++++++--- wolfssl/wolfcrypt/include.am | 1 + wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h | 29 +- wolfssl/wolfcrypt/random.h | 98 ---- wolfssl/wolfcrypt/rng_noisesrc.h | 159 ++++++ wolfssl/wolfcrypt/settings.h | 10 + 18 files changed, 1192 insertions(+), 719 deletions(-) create mode 100644 doc/dox_comments/header_files/rng_noisesrc.h create mode 100644 wolfcrypt/src/rng_noisesrc.c create mode 100644 wolfssl/wolfcrypt/rng_noisesrc.h diff --git a/.github/workflows/ti-c2000-compile.yml b/.github/workflows/ti-c2000-compile.yml index d6a6ecb2e2a..e5f6099ea13 100644 --- a/.github/workflows/ti-c2000-compile.yml +++ b/.github/workflows/ti-c2000-compile.yml @@ -1,17 +1,19 @@ name: "CHAR_BIT != 8 (wide byte) guards" # Guards for the CHAR_BIT != 8 (WOLFSSL_WIDE_BYTE) support added for the TI -# C2000 C28x, where a char is a 16-bit cell. Three independent jobs: +# C2000 C28x, where a char is a 16-bit cell. Independent jobs: # # ti_c2000_compile cl2000 compile-only over the C28x wolfCrypt subset. # Needs the TI CGT, so it self-skips when that is not # configured (see the job's env block). # sha3_split64_host x86-64 build + wolfCrypt KAT run with WC_SHA3_SPLIT64 # forced. Always runs. +# noisesrc_host x86-64 build + make check of the generic noise source +# the C2000 entropy port sits on. Always runs. # wide_byte_be_compile x86-64 syntax-only check of WOLFSSL_WIDE_BYTE combined # with BIG_ENDIAN_ORDER. Always runs. # -# Only the first job involves TI tooling or the C28x at all; the other two are +# Only the first job involves TI tooling or the C28x at all; the others are # plain host jobs covering code paths this port introduced. On-target # run-tests live on a hardware-in-the-loop runner (there is no public C28x # simulator). @@ -26,6 +28,7 @@ on: - 'wolfcrypt/src/**' - 'wolfssl/wolfcrypt/**' - 'IDE/C2000/**' + - 'configure.ac' - '.github/workflows/ti-c2000-compile.yml' pull_request: types: [opened, synchronize, reopened, ready_for_review] @@ -34,6 +37,7 @@ on: - 'wolfcrypt/src/**' - 'wolfssl/wolfcrypt/**' - 'IDE/C2000/**' + - 'configure.ac' - '.github/workflows/ti-c2000-compile.yml' concurrency: @@ -145,6 +149,30 @@ jobs: make ./wolfcrypt/test/testwolfcrypt + # WHAT: x86-64 build + make check with --enable-noisesrc, which + # runs noisesrc_test: synthetic sources driving the RCT, APT, startup + # rerun, sampler-error and self-test paths, plus SHA3-256 KATs. + # NOT: nothing TI-specific and no hardware or TI CGT needed, so like the + # job above this always runs. The DCC sampler itself is not built. + # WHY: the wc_NoiseSrc_* layer is the C2000 port's whole health-test and + # conditioning path but is pure C, so its failure handling can be + # exercised on any host instead of only on hardware CI cannot reach. + noisesrc_host: + name: Noise source host test + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} + runs-on: ubuntu-22.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + name: Checkout wolfSSL + + - name: Build + make check with --enable-noisesrc + run: | + ./autogen.sh + ./configure --enable-noisesrc --enable-all + make + make check + # WHAT: gcc -fsyntax-only -Werror over the octet paths with both # WOLFSSL_WIDE_BYTE and BIG_ENDIAN_ORDER defined. A pure compile # smoke test: it only catches "that arm does not compile". diff --git a/CMakeLists.txt b/CMakeLists.txt index f3092326b72..0fab76f4797 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1851,6 +1851,15 @@ if(WOLFSSL_CSHAKE) override_cache(WOLFSSL_SHAKE256 "yes") endif() +# Generic SP800-90B noise source (wc_NoiseSrc_*); conditions with SHA3-256 +add_option("WOLFSSL_NOISESRC" + "Enable the generic SP800-90B noise source layer (default: disabled)" + "no" "yes;no") +if(WOLFSSL_NOISESRC) + override_cache(WOLFSSL_SHA3 "yes") + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_NOISE_SRC") +endif() + # SHA512 add_option("WOLFSSL_SHA512" "Enable wolfSSL SHA-512 support (default: enabled)" diff --git a/IDE/C2000/README.md b/IDE/C2000/README.md index ec81d951966..e27a44b2023 100644 --- a/IDE/C2000/README.md +++ b/IDE/C2000/README.md @@ -247,7 +247,7 @@ turns that into an entropy source: a DCC counts PLLRAWCLK edges inside a window of INTOSC cycles, and the **LSB of that count** is one noise bit, carrying the relative phase drift of two physically distinct oscillators. Raw noise is oversampled far past its measured min-entropy, health-tested per -SP800-90B 4.4, conditioned with SHA-256, and handed to the SP800-90A +SP800-90B 4.4, conditioned with SHA3-256, and handed to the SP800-90A Hash-DRBG. The work is split in two. The port file owns only the hardware -- DCC setup, @@ -323,9 +323,11 @@ The hardware-selection group is what to reach for if the board already uses a DC ### The generic `wc_NoiseSrc_*` layer None of the SP800-90B machinery is C2000-specific, so it lives in -`wolfcrypt/src/random.c` behind `WOLFSSL_NOISE_SRC` (declarations in -`wolfssl/wolfcrypt/random.h`) and this port configures it. A port that has a -raw noise source but no TRNG supplies one callback: +`wolfcrypt/src/rng_noisesrc.c` behind `WOLFSSL_NOISE_SRC` (declarations in +`wolfssl/wolfcrypt/rng_noisesrc.h`), outside the FIPS module boundary, and this +port configures it. It follows wolfEntropy's SP800-90B health-test and +failure semantics and conditions with SHA3-256, so it turns `WOLFSSL_SHA3` +on. A port that has a raw noise source but no TRNG supplies one callback: ```c int my_sample(void* ctx, int srcIdx, byte* octet); @@ -333,58 +335,54 @@ int my_sample(void* ctx, int srcIdx, byte* octet); fills a `wc_NoiseSrc` with the callback, a domain-separation tag, a caller-owned work buffer, the entropy budget (`hmin`, `margin`) and the -health-test cutoffs, and gets back: +health-test cutoffs -- any of those left zero selects wolfEntropy's defaults -- +and gets back: | Call | Does | |---|---| | `wc_NoiseSrc_Init` | Validates the config, derives the gather size, runs the SP800-90B 4.3 startup test | -| `wc_NoiseSrc_GenerateSeed` | Gathers, health-tests, SHA-256 conditions, wipes the output on any failure | +| `wc_NoiseSrc_GenerateSeed` | Gathers, health-tests, SHA3-256 conditions, wipes the output on any failure | | `wc_NoiseSrc_GetRaw` | Unconditioned noise for characterization only | | `wc_NoiseSrc_SelfTest` | Liveness: gathers must differ and must not be a constant octet | -| `wc_NoiseSrc_Free` | Zeroes state and clears the latched failure | +| `wc_NoiseSrc_Free` | Zeroes state and resets the health-test state | `WC_NOISE_RAW_PER_SRC(hmin, margin)` sizes the work buffer from the entropy budget so the port does not duplicate the formula. Source 0 is the credited one; any further source is hashed in as defence in depth and is not budgeted. The layer takes no locks -- the instance is caller-owned state, so a port that shares one across threads provides its own mutual exclusion. -`WOLFSSL_C2000_ENTROPY` turns `WOLFSSL_NOISE_SRC` on implicitly. +`WOLFSSL_C2000_ENTROPY` turns `WOLFSSL_NOISE_SRC` on implicitly. Each source +carries its own health-test state, about 0.8 KW of `.bss` on the C28x. ### Health tests SP800-90B 4.4.1 Repetition Count and 4.4.2 Adaptive Proportion run continuously over every octet drawn from each source, with a startup test over -a full gather before anything is released. Cutoffs are derived for 4 bits of -min-entropy per octet at alpha = 2^-30 and are overridable. A failure returns -`ENTROPY_RT_E` / `ENTROPY_APT_E` and **no seed material is produced** -- the -source fails closed rather than degrading silently, and the failure is latched -until `wc_NoiseSrc_Free()`, because a source that trips and then passes is -exactly what continuous testing exists to catch. - -Latching applies to the **credited** source only. Source 0 carries the entire -entropy budget, so its failure denies output. Sources 1 and up are unaccounted -extra hash input, and the cutoffs are derived for source 0's assumed -min-entropy rather than theirs, so one of them tripping is not evidence the -seed is weak: it is dropped for the life of the instance and stops -contributing. Output stays fully seeded because the budget never counted it, -and a source the budget ignores cannot deny service. On this part that means a -failing INTOSC2 degrades the source to INTOSC1 alone -- exactly the -`WOLFSSL_C2000_ENTROPY_NUM_SRC 1` configuration -- instead of killing the RNG. +`STARTUP_OCTETS` per source before anything is released. The APT uses a +sliding 512-sample window. Cutoffs are derived for 4 bits of min-entropy per +octet at alpha = 2^-30 and are overridable. + +A failure on any source returns `ENTROPY_RT_E` / `ENTROPY_APT_E` and **no seed +material is produced**. The failing source's test state is reset, so the next +call reruns its startup test (~210 ms per source at the defaults) before +anything is released again; a source that stays broken fails every call. This +is the same reset-and-retry handling wolfEntropy uses. It applies to the +uncredited source too, so a failing INTOSC2 fails the call even though the +budget never counted it. If INTOSC2 proves marginal on a board, build with +`WOLFSSL_C2000_ENTROPY_NUM_SRC 1`. Because those paths only fire on genuinely broken hardware, `noisesrc_test()` in `wolfcrypt/test/test.c` drives them with synthetic sources -- stuck, biased, sampler-error, and periodic -- on the host: ```sh -./configure --enable-all CFLAGS="-DWOLFSSL_NOISE_SRC" && make check +./configure --enable-noisesrc --enable-all && make check ``` -Note `wolfentropy.c`'s `HAVE_ENTROPY_MEMUSE` is *not* a usable substitute -here: its noise is memory-access timing jitter, which presumes a cache, and -the C28x is in-order and cacheless with deterministic RAM timing. Its default -state array is also ~256 KW on this target against ~100 KW of RAM. Its health -tests are `static` and hardcoded to 1 bit of min-entropy per sample, so they -are not reusable at this source's cutoffs either. +Note wolfEntropy's MemUse source (`HAVE_ENTROPY_MEMUSE`) is *not* a usable +substitute here: its noise is memory-access timing jitter, which presumes a +cache, and the C28x is in-order and cacheless with deterministic RAM timing. +Its default state array is also ~256 KW on this target against ~100 KW of RAM. ## Enabling on your build diff --git a/IDE/C2000/compile.sh b/IDE/C2000/compile.sh index 745c52cbc39..5182e8aeeed 100755 --- a/IDE/C2000/compile.sh +++ b/IDE/C2000/compile.sh @@ -61,21 +61,24 @@ for s in $SRCS; do fi done -# random.c again with the entropy gate on: that turns WOLFSSL_NOISE_SRC on and -# pulls in the generic SP800-90B noise-source layer, which is otherwise left +# rng_noisesrc.c and random.c again with the entropy gate on: that turns +# WOLFSSL_NOISE_SRC on, compiling the generic SP800-90B noise-source layer in +# rng_noisesrc.c and the C2000 wc_GenerateSeed() branch in random.c, both left # out of the sweep above. Needs no C2000Ware - only the port .c touches # driverlib. -printf 'CC random.c (WOLFSSL_C2000_ENTROPY) ... ' -if "$CL" $CFLAGS $INCS --define=WOLFSSL_C2000_ENTROPY \ - --compile_only --skip_assembler \ - --asm_directory="$OUT" --obj_directory="$OUT" \ - "$WOLFROOT/wolfcrypt/src/random.c" > "$OUT/random-noise.log" 2>&1; then - echo "ok" -else - echo "FAIL" - cat "$OUT/random-noise.log" - rc=1 -fi +for s in rng_noisesrc random; do + printf 'CC %s.c (WOLFSSL_C2000_ENTROPY) ... ' "$s" + if "$CL" $CFLAGS $INCS --define=WOLFSSL_C2000_ENTROPY \ + --compile_only --skip_assembler \ + --asm_directory="$OUT" --obj_directory="$OUT" \ + "$WOLFROOT/wolfcrypt/src/$s.c" > "$OUT/$s-noise.log" 2>&1; then + echo "ok" + else + echo "FAIL" + cat "$OUT/$s-noise.log" + rc=1 + fi +done # The AESA hardware-AES port needs C2000Ware driverlib headers, which CI does # not download, so it is an opt-in extra leg: set C2000WARE to a C2000Ware diff --git a/cmake/functions.cmake b/cmake/functions.cmake index 70c9163e167..85be3150751 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -399,6 +399,9 @@ function(generate_build_flags) if(WOLFSSL_RNG_BANK OR WOLFSSL_USER_SETTINGS) set(BUILD_RNG_BANK "yes" PARENT_SCOPE) endif() + if(WOLFSSL_NOISESRC OR WOLFSSL_USER_SETTINGS) + set(BUILD_NOISESRC "yes" PARENT_SCOPE) + endif() set(USE_VALGRIND ${WOLFSSL_VALGRIND}) if(WOLFSSL_MD4 OR WOLFSSL_USER_SETTINGS) set(BUILD_MD4 "yes" PARENT_SCOPE) @@ -1391,6 +1394,10 @@ function(generate_lib_src_list LIB_SOURCES) list(APPEND LIB_SOURCES wolfcrypt/src/rng_bank.c) endif() + if(BUILD_NOISESRC) + list(APPEND LIB_SOURCES wolfcrypt/src/rng_noisesrc.c) + endif() + if(BUILD_AFALG) list(APPEND LIB_SOURCES wolfcrypt/src/port/af_alg/wc_afalg.c) endif() diff --git a/cmake/options.h.in b/cmake/options.h.in index b59a84629ac..ff777131344 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -642,6 +642,8 @@ extern "C" { #cmakedefine FP_ECC #undef HAVE_WC_RNG_BANK #cmakedefine HAVE_WC_RNG_BANK +#undef WOLFSSL_NOISE_SRC +#cmakedefine WOLFSSL_NOISE_SRC #undef WC_RNG_WANT_BANKREF_SUPPORT #cmakedefine WC_RNG_WANT_BANKREF_SUPPORT #undef WC_RNG_NO_AUTO_LOCK diff --git a/configure.ac b/configure.ac index 4f95dcd6ce3..efac83e1d38 100644 --- a/configure.ac +++ b/configure.ac @@ -7761,6 +7761,14 @@ AC_ARG_ENABLE([entropy-memuse], [ ENABLED_ENTROPY_MEMUSE=$enableval ] ) +# Generic SP800-90B noise source (wc_NoiseSrc_*) for ports with a raw physical +# noise source, e.g. the TI C2000 DCC entropy port +AC_ARG_ENABLE([noisesrc], + [AS_HELP_STRING([--enable-noisesrc],[Enable the generic SP800-90B noise source layer (default: disabled)])], + [ ENABLED_NOISESRC=$enableval ], + [ ENABLED_NOISESRC=no ] + ) + # AES key wrap # Accepts a comma-separated value list; "padding" adds RFC 5649 key wrap with # padding on top of the base RFC 3394 key wrap. @@ -8918,6 +8926,11 @@ then ENABLED_SHAKE128=yes ENABLED_SHAKE256=yes fi +# The noise source conditions with SHA3-256. +if test "$ENABLED_NOISESRC" != "no" +then + ENABLED_SHA3=yes +fi # Falcon uses SHA-3 / SHAKE256 for hash-to-point. if test "$ENABLED_FALCON" != "no" then @@ -9581,6 +9594,11 @@ then esac fi +if test "$ENABLED_NOISESRC" != "no" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_NOISE_SRC" +fi + if (test "$ENABLED_RNG" = "yes" && test "$ENABLED_ENTROPY_MEMUSE" != "no") || test "$ENABLED_BENCHMARK" = "yes" then AC_SEARCH_LIBS([clock_gettime],[rt]) @@ -14625,6 +14643,7 @@ AM_CONDITIONAL([BUILD_ECCSI],[test "x$ENABLED_ECCSI" = "xyes" || test "x$ENABLED AM_CONDITIONAL([BUILD_SAKKE],[test "x$ENABLED_SAKKE" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_MEMORY],[test "x$ENABLED_MEMORY" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_MEMUSE],[test "x$ENABLED_ENTROPY_MEMUSE" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"]) +AM_CONDITIONAL([BUILD_NOISESRC],[test "x$ENABLED_NOISESRC" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_RNG_BANK],[test "$ENABLED_RNG_BANK" = "yes" || test "$ENABLED_USERSETTINGS" = "yes"]) AM_CONDITIONAL([BUILD_RSA],[test "x$ENABLED_RSA" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"]) AM_CONDITIONAL([BUILD_DH],[test "x$ENABLED_DH" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"]) @@ -15189,6 +15208,7 @@ echo " * RNG lock on by default: $ENABLED_RNG_AUTOLOCK_DEFAULT" echo " * RNG fork handlers: $ENABLED_RNG_AUTOFORK" echo " * MmemUse Entropy:" echo " * (AKA: wolfEntropy): $ENABLED_ENTROPY_MEMUSE" +echo " * Noise source: $ENABLED_NOISESRC" echo " * PWDBASED: $ENABLED_PWDBASED" echo " * Encrypted keys: $ENABLED_ENCKEYS" echo " * scrypt: $ENABLED_SCRYPT" diff --git a/doc/dox_comments/header_files/rng_noisesrc.h b/doc/dox_comments/header_files/rng_noisesrc.h new file mode 100644 index 00000000000..fd874961d45 --- /dev/null +++ b/doc/dox_comments/header_files/rng_noisesrc.h @@ -0,0 +1,173 @@ +/*! + \ingroup Random + \brief Validates a generic SP800-90B noise source and runs its startup + test. + + Built with WOLFSSL_NOISE_SRC (--enable-noisesrc). For parts with a raw + physical noise source but no TRNG: the caller supplies a callback + returning one unconditioned octet, and this layer adds the SP800-90B 4.3 + startup test, the 4.4.1 Repetition Count and 4.4.2 Adaptive Proportion + (sliding window) continuous tests, entropy-budget oversampling and a + SHA3-256 conditioner. + + Fill the configuration members of the caller-owned wc_NoiseSrc, then call + this. The first call validates the configuration and derives rawPerSrc; + every call runs the startup test for each source whose health-test state + is empty, which after a test failure is the source that failed. The other + wc_NoiseSrc_* calls invoke it first. A configuration member left zero + selects a default: + + | Member | Default | + |---|---| + | hmin | WC_NOISE_HMIN_DEF (10, i.e. 0.1 bits per raw bit) | + | margin | WC_NOISE_MARGIN_DEF (1) | + | rctCutoff | WC_NOISE_RCT_CUTOFF_DEF (31) | + | aptWindow | WC_NOISE_APT_WINDOW_DEF (512, also the maximum) | + | aptCutoff | WC_NOISE_APT_CUTOFF_DEF (325) | + | startupOctets | WC_NOISE_STARTUP_DEF (1536) | + + With the defaults each source gathers 320 raw octets per 32-octet output + chunk. Cutoffs must match the assumed hmin, or they either never trip or + trip constantly. + + \return 0 On success. + \return BAD_FUNC_ARG src is NULL, a required member is NULL, numSrc is + not 1..WC_NOISE_SRC_MAX, hmin is over 100, a cutoff is below 2, aptWindow + is over 512 or below aptCutoff, or startupOctets is below aptWindow. + \return BUFFER_E workSz is smaller than numSrc * rawPerSrc. + \return ENTROPY_RT_E or ENTROPY_APT_E A startup test failed; that source's + state is reset and the next call retries it. + \return Other negative values as returned by the sample callback. + + \param src Caller-owned noise source instance. + + _Example_ + \code + static byte work[2 * WC_NOISE_RAW_PER_SRC(50, 2)]; + static wc_NoiseSrc src; + + src.sampleCb = my_sample; + src.tag = "my-board-osc-entropy-v1"; + src.work = work; + src.workSz = sizeof(work); + src.startupOctets = 1024; + src.numSrc = 2; + src.hmin = 50; + src.margin = 2; + src.rctCutoff = 9; + src.aptWindow = 512; + src.aptCutoff = 71; + ret = wc_NoiseSrc_Init(&src); + \endcode + + \sa wc_NoiseSrc_GenerateSeed + \sa wc_NoiseSrc_Free +*/ +int wc_NoiseSrc_Init(wc_NoiseSrc* src); + +/*! + \ingroup Random + \brief Wipes the work buffer and health-test state of a noise source. + + Configuration members are kept, so wc_NoiseSrc_Init() can be called again + on the same instance. + + \param src Noise source instance. NULL is ignored. + + _Example_ + \code + wc_NoiseSrc_Free(&src); + \endcode + + \sa wc_NoiseSrc_Init +*/ +void wc_NoiseSrc_Free(wc_NoiseSrc* src); + +/*! + \ingroup Random + \brief Produces conditioned seed material from a noise source. + + Per 32-octet chunk, gathers rawPerSrc octets from every source, + health-tests them, and hashes tag, a big-endian chunk counter and each + source's octets in index order with SHA3-256. Only source 0 is credited + with entropy; further sources are hashed in as defence in depth. + + On any failure the whole output buffer is wiped. A health-test failure on + any source resets that source's test state, so the next call reruns its + startup test. + + \return 0 On success. + \return BAD_FUNC_ARG src or output is NULL. + \return ENTROPY_RT_E or ENTROPY_APT_E A health test failed. + \return MEMORY_E Allocation failed (WOLFSSL_SMALL_STACK). + \return Other negative values from wc_NoiseSrc_Init(), the sample callback + or SHA3-256. + + \param src Noise source instance. + \param output Buffer to fill. + \param sz Octets to produce. + + _Example_ + \code + byte seed[48]; + ret = wc_NoiseSrc_GenerateSeed(&src, seed, sizeof(seed)); + \endcode + + \sa wc_NoiseSrc_Init + \sa wc_NoiseSrc_SelfTest +*/ +int wc_NoiseSrc_GenerateSeed(wc_NoiseSrc* src, byte* output, word32 sz); + +/*! + \ingroup Random + \brief Reads raw, unconditioned octets from one source of a noise source. + + For characterization and self-test only: no health tests run on these + octets, so they are not fit for keying material, and they do not advance + the continuous-test state. + + \return 0 On success. + \return BAD_FUNC_ARG src or output is NULL, or srcIdx is out of range. + \return Other negative values from wc_NoiseSrc_Init() or the sample + callback; output is wiped on a callback error. + + \param src Noise source instance. + \param output Buffer to fill. + \param len Octets to read. + \param srcIdx Source index, 0 .. numSrc - 1. + + _Example_ + \code + byte raw[4096]; + ret = wc_NoiseSrc_GetRaw(&src, raw, sizeof(raw), 0); + \endcode + + \sa wc_NoiseSrc_GenerateSeed +*/ +int wc_NoiseSrc_GetRaw(wc_NoiseSrc* src, byte* output, word32 len, + int srcIdx); + +/*! + \ingroup Random + \brief Liveness check on the raw noise of every source. + + Each source must produce two differing gathers, and a gather must not be + one repeated octet. A source that fails has its health-test state reset, + so the next call reruns its startup test. + + \return 0 On success. + \return ENTROPY_RT_E A source failed the check. + \return BUFFER_E The work buffer is too small for two gathers. + \return Other negative values from wc_NoiseSrc_Init() or the sample + callback. + + \param src Noise source instance. + + _Example_ + \code + ret = wc_NoiseSrc_SelfTest(&src); + \endcode + + \sa wc_NoiseSrc_GenerateSeed +*/ +int wc_NoiseSrc_SelfTest(wc_NoiseSrc* src); diff --git a/src/include.am b/src/include.am index 1de3c928828..41945a41bf1 100644 --- a/src/include.am +++ b/src/include.am @@ -1469,6 +1469,11 @@ if !BUILD_DO178 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/cpuid.c endif !BUILD_DO178 +# Outside the FIPS module boundary for every FIPS edition. +if BUILD_NOISESRC +src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/rng_noisesrc.c +endif + if !BUILD_FIPS_RAND if !BUILD_FIPS_V5 diff --git a/wolfcrypt/src/port/ti/ti-c2000-entropy.c b/wolfcrypt/src/port/ti/ti-c2000-entropy.c index f3af20173be..b1df83d4d0b 100644 --- a/wolfcrypt/src/port/ti/ti-c2000-entropy.c +++ b/wolfcrypt/src/port/ti/ti-c2000-entropy.c @@ -25,17 +25,15 @@ * * This file owns only the hardware: DCC setup, one measurement, and the noise * bit. The SP800-90B startup and continuous health tests, the entropy budget, - * the SHA-256 conditioner and the latched failure state are the generic - * wc_NoiseSrc_* layer in wolfcrypt/src/random.c, which this configures. + * the SHA3-256 conditioner and the failure handling are the generic + * wc_NoiseSrc_* layer in wolfcrypt/src/rng_noisesrc.c, which this configures. * * Noise bit = LSB of a DCC measurement (PLL edges counted inside a window of * INTOSC cycles), i.e. the relative phase drift of two independent * oscillators. Both sources are hashed in, but only source 0 is credited with * entropy: source 1 estimates lower and fails a chi-square uniformity check, * so it is defence-in-depth only. Extra hash input can never subtract - * entropy, and because source 1 is unbudgeted the generic layer drops it - * rather than failing closed if its health tests trip - see the latch policy - * in wolfcrypt/src/random.c. + * entropy. * * Build options (all #ifndef-guarded in ti-c2000-entropy.h, documented with * defaults in IDE/C2000/README.md): @@ -68,7 +66,7 @@ #include #include -#include +#include #include "dcc.h" #include "sysctl.h" @@ -85,7 +83,7 @@ WC_NOISE_RAW_PER_SRC(WOLFSSL_C2000_ENTROPY_HMIN, \ WOLFSSL_C2000_ENTROPY_MARGIN) -/* Domain separation from any other SHA-256 use in the system. */ +/* Domain separation from any other SHA3-256 use in the system. */ static const char c2000_entropyTag[] = "wolfssl-c2000-osc-entropy-v1"; /* .bss, not stack: the C28x stack is 16 KW below 0x10000. */ @@ -239,8 +237,10 @@ static int c2000_sampleOctet(void* ctx, int srcIdx, byte* octet) int wc_c2000_Entropy_Init(void) { + /* Already configured: reruns the startup test of any source a failure + * has reset, so a retry reports the real state. */ if (c2000_src.inited) { - return 0; + return wc_NoiseSrc_Init(&c2000_src); } #ifndef WOLFSSL_C2000_ENTROPY_NO_CLK_INIT @@ -286,7 +286,7 @@ void wc_c2000_Entropy_Free(void) /* Teardown has to be idempotent: a second Free(), or a Free() with no * Init(), would otherwise write DCC registers with the peripheral clock * already gated off. Guard on c2000_clkOn rather than inited - a - * startup-test failure returns before inited is set, but the clocks are + * configuration error returns before inited is set, but the clocks are * on by then, so inited would skip a teardown that is still owed. */ if (c2000_clkOn) #endif diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 23b7eb2b744..5d6ac67e8c4 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -9335,475 +9335,6 @@ static int wc_GenerateRand_IntelRD(OS_Seed* os, byte* output, word32 sz) #endif /* HAVE_INTEL_RDRAND || HAVE_INTEL_RDSEED || HAVE_AMD_RDSEED */ -#ifdef WOLFSSL_NOISE_SRC - -/* Generic SP800-90B noise source. Configuration struct and API: - * wolfssl/wolfcrypt/random.h. - * - * Entropy model. A port supplies raw, unconditioned octets through one - * callback. Each raw bit is credited hmin/100 bits of min-entropy, so a - * full-entropy output octet needs 8 / (hmin/100) raw bits, and margin - * oversamples on top of that; WC_NOISE_RAW_PER_SRC() turns the two into the - * raw octets gathered per conditioner chunk. Only source 0 is budgeted - any - * further source is hashed in as defence in depth, and extra hash input can - * never subtract entropy. - * - * Every gathered octet passes the 4.4.1 Repetition Count Test and the 4.4.2 - * Adaptive Proportion Test before reaching the conditioner, and _Init() runs - * the 4.3 startup test over startupOctets per source first. A failure is - * latched: 4.3/4.4 want a persistent failure state, not a transparent retry, - * because a source that trips and then passes is exactly what continuous - * testing exists to catch. Only _Free() clears it. - * - * Only test verdicts latch - the health tests and the _SelfTest() liveness - * checks. An error from the sample callback propagates unlatched and stays - * retryable: it says the hardware did not answer, not that the noise is bad. - * Either way no output is produced. - * - * Latching is also limited to the credited source. Source 0 carries the whole - * budget, so its failure fails closed. Sources 1.. are unaccounted extra hash - * input, and the cutoffs are derived for source 0's assumed min-entropy rather - * than theirs, so one of them tripping is not evidence the seed is weak - it is - * dropped for the life of the instance (recorded in src->degraded) and stops - * contributing. Output stays fully seeded because the budget never counted - * it, and a source the budget ignores cannot deny service. - * - * Cutoffs belong to the caller and must match the assumed hmin, or they either - * never trip or trip constantly. For H bits of min-entropy per octet at - * alpha = 2^-30: RCT C = 1 + ceil(30/H), APT C = 1 + CRITBINOM(W, 2^-H, - * 1-alpha). - * - * No locking. The instance is caller-owned state, so a port sharing one - * across threads provides its own mutual exclusion. */ - -#ifdef NO_SHA256 - #error "WOLFSSL_NOISE_SRC conditions with SHA-256; do not set NO_SHA256" -#endif -#if WC_NOISE_CHUNK_SZ != WC_SHA256_DIGEST_SIZE - #error "WC_NOISE_CHUNK_SZ must match WC_SHA256_DIGEST_SIZE" -#endif - -/* len raw octets from one source. No health testing - callers that keep the - * data run NoiseSrc_HealthTest() over it. */ -static int NoiseSrc_Gather(wc_NoiseSrc* src, byte* out, word32 len, int srcIdx) -{ - word32 i; - int ret; - - for (i = 0; i < len; i++) { - ret = src->sampleCb(src->ctx, srcIdx, &out[i]); - if (ret != 0) { - return ret; - } - /* The contract is raw octets, but where CHAR_BIT != 8 a callback can - * legally leave bits above 0xFF in the cell. Mask here so the - * conditioner sees exactly what the health tests scored - they mask - * already - and no high bits leak into the hash. */ - out[i] = WC_OCTET(out[i]); - } - - return 0; -} - -/* SP800-90B 4.4.1 RCT and 4.4.2 APT over every octet drawn from a source. - * Returns at the offending sample so the rest of the buffer cannot scrub the - * state that detected it, and latches the failure. */ -static int NoiseSrc_HealthTest(wc_NoiseSrc* src, const byte* buf, word32 len, - int srcIdx) -{ - wc_NoiseHealth* st; - word32 i; - word16 s; - - if (srcIdx < 0 || srcIdx >= (int)src->numSrc) { - return BAD_FUNC_ARG; - } - st = &src->health[srcIdx]; - - for (i = 0; i < len; i++) { - s = (word16)WC_OCTET(buf[i]); - - if (!st->started) { - st->started = 1; - st->rctLast = s; - st->rctCount = 1; - st->aptRef = s; - st->aptCount = 1; - st->aptPos = 1; - continue; - } - - if (s == st->rctLast) { - st->rctCount++; - if (st->rctCount >= src->rctCutoff) { - if (srcIdx == 0) { - src->failed = ENTROPY_RT_E; - } - return ENTROPY_RT_E; - } - } - else { - st->rctLast = s; - st->rctCount = 1; - } - - if (st->aptPos >= src->aptWindow) { - st->aptRef = s; - st->aptCount = 1; - st->aptPos = 1; - } - else { - if (s == st->aptRef) { - st->aptCount++; - if (st->aptCount >= src->aptCutoff) { - if (srcIdx == 0) { - src->failed = ENTROPY_APT_E; - } - return ENTROPY_APT_E; - } - } - st->aptPos++; - } - } - - return 0; -} - -int wc_NoiseSrc_Init(wc_NoiseSrc* src) -{ - word32 done; - word32 take; - word32 need; - int i; - int ret; - - if (src == NULL) { - return BAD_FUNC_ARG; - } - if (src->failed != 0) { - return src->failed; - } - if (src->inited) { - return 0; - } - - if (src->sampleCb == NULL || src->tag == NULL || src->work == NULL) { - return BAD_FUNC_ARG; - } - if (src->numSrc < 1 || src->numSrc > WC_NOISE_SRC_MAX) { - return BAD_FUNC_ARG; - } - if (src->hmin < 1 || src->hmin > 100 || src->margin < 1) { - return BAD_FUNC_ARG; - } - if (src->rctCutoff < 2 || src->aptCutoff < 2 || src->aptWindow < 2) { - return BAD_FUNC_ARG; - } - /* Below one APT window the startup pass never exercises that test. */ - if (src->startupOctets < (word32)src->aptWindow) { - return BAD_FUNC_ARG; - } - - src->rawPerSrc = WC_NOISE_RAW_PER_SRC(src->hmin, src->margin); - need = src->rawPerSrc * (word32)src->numSrc; - if (src->rawPerSrc == 0 || src->workSz < need) { - return BUFFER_E; - } - - XMEMSET(src->health, 0, sizeof(src->health)); - src->chunkCtr = 0; - src->degraded = 0; - - /* SP800-90B 4.3: push startupOctets per source through the continuous - * tests - more than one APT window - before releasing anything. */ - for (i = 0; i < (int)src->numSrc; i++) { - for (done = 0; done < src->startupOctets; done += take) { - take = src->startupOctets - done; - if (take > src->rawPerSrc) { - take = src->rawPerSrc; - } - /* Sampler errors and test verdicts are handled differently, so - * keep them apart: a callback error means the hardware did not - * answer and stays retryable for every source, while only a test - * verdict drops or latches. */ - ret = NoiseSrc_Gather(src, src->work, take, i); - if (ret != 0) { - ForceZero(src->work, src->workSz); - return ret; - } - ret = NoiseSrc_HealthTest(src, src->work, take, i); - if (ret != 0) { - if (i == 0) { - ForceZero(src->work, src->workSz); - return ret; /* credited source: fail closed */ - } - /* Uncredited: drop it and keep going - see the latch policy - * note at the top of this module. */ - src->degraded |= (word16)(1U << i); - ret = 0; - break; - } - } - } - - ForceZero(src->work, src->workSz); - src->inited = 1; - return 0; -} - -void wc_NoiseSrc_Free(wc_NoiseSrc* src) -{ - if (src == NULL) { - return; - } - - if (src->work != NULL && src->workSz > 0) { - ForceZero(src->work, src->workSz); - } - ForceZero(src->health, sizeof(src->health)); - src->chunkCtr = 0; - src->failed = 0; - src->degraded = 0; - src->inited = 0; -} - -int wc_NoiseSrc_GetRaw(wc_NoiseSrc* src, byte* output, word32 len, int srcIdx) -{ - int ret; - - if (src == NULL || output == NULL) { - return BAD_FUNC_ARG; - } - /* Before _Init(): numSrc is caller configuration, so a bad index need not - * pay for the startup test or burn hardware entropy first. */ - if (srcIdx < 0 || srcIdx >= (int)src->numSrc) { - return BAD_FUNC_ARG; - } - if (src->failed != 0) { - return src->failed; - } - - ret = wc_NoiseSrc_Init(src); - if (ret != 0) { - return ret; - } - - ret = NoiseSrc_Gather(src, output, len, srcIdx); - if (ret != 0) { - ForceZero(output, len); - } - return ret; -} - -int wc_NoiseSrc_GenerateSeed(wc_NoiseSrc* src, byte* output, word32 sz) -{ -#ifdef WOLFSSL_SMALL_STACK - wc_Sha256* sha = NULL; -#else - wc_Sha256 sha[1]; -#endif - byte digest[WC_NOISE_CHUNK_SZ]; - byte ctrBuf[4]; - byte* raw; - byte* outStart; - word32 outLen; - word32 take; - word16 contributed; - int i; - int ret; - - if (src == NULL || output == NULL) { - return BAD_FUNC_ARG; - } - if (sz == 0) { - return 0; - } - if (src->failed != 0) { - return src->failed; - } - - /* Kept so a mid-way failure can wipe what already landed: no caller - * should ever see a partially-filled seed buffer. */ - outStart = output; - outLen = sz; - - XMEMSET(digest, 0, sizeof(digest)); - - ret = wc_NoiseSrc_Init(src); - if (ret != 0) { - return ret; - } - -#ifdef WOLFSSL_SMALL_STACK - sha = (wc_Sha256*)XMALLOC(sizeof(wc_Sha256), NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (sha == NULL) { - return MEMORY_E; - } -#endif - - while (sz > 0) { - contributed = 0; - for (i = 0; i < (int)src->numSrc; i++) { - if ((src->degraded & (word16)(1U << i)) != 0) { - continue; /* uncredited source already dropped */ - } - raw = src->work + ((word32)i * src->rawPerSrc); - - /* A sampler error propagates for any source and is retryable; - * only a health-test verdict drops or latches. */ - ret = NoiseSrc_Gather(src, raw, src->rawPerSrc, i); - if (ret != 0) { - goto out; - } - ret = NoiseSrc_HealthTest(src, raw, src->rawPerSrc, i); - if (ret != 0) { - if (i == 0) { - goto out; /* fail closed; HealthTest latched src->failed */ - } - src->degraded |= (word16)(1U << i); - ret = 0; - continue; - } - contributed |= (word16)(1U << i); - } - - ret = wc_InitSha256(sha); - if (ret != 0) { - ForceZero(sha, sizeof(*sha)); - goto out; - } - - src->chunkCtr++; - ctrBuf[0] = WC_OCTET(src->chunkCtr >> 24); - ctrBuf[1] = WC_OCTET(src->chunkCtr >> 16); - ctrBuf[2] = WC_OCTET(src->chunkCtr >> 8); - ctrBuf[3] = WC_OCTET(src->chunkCtr); - - ret = wc_Sha256Update(sha, (const byte*)src->tag, - (word32)XSTRLEN(src->tag)); - if (ret == 0) { - ret = wc_Sha256Update(sha, ctrBuf, (word32)sizeof(ctrBuf)); - } - for (i = 0; (ret == 0) && (i < (int)src->numSrc); i++) { - if ((contributed & (word16)(1U << i)) == 0) { - continue; /* not gathered this chunk - never hash stale data */ - } - raw = src->work + ((word32)i * src->rawPerSrc); - ret = wc_Sha256Update(sha, raw, src->rawPerSrc); - } - if (ret == 0) { - ret = wc_Sha256Final(sha, digest); - } - wc_Sha256Free(sha); - ForceZero(sha, sizeof(*sha)); - if (ret != 0) { - goto out; - } - - take = (sz < (word32)WC_NOISE_CHUNK_SZ) ? sz - : (word32)WC_NOISE_CHUNK_SZ; - XMEMCPY(output, digest, take); - output += take; - sz -= take; - } - -out: - if (ret != 0) { - ForceZero(outStart, outLen); - } - ForceZero(digest, sizeof(digest)); - ForceZero(src->work, src->workSz); -#ifdef WOLFSSL_SMALL_STACK - XFREE(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER); -#endif - return ret; -} - -int wc_NoiseSrc_SelfTest(wc_NoiseSrc* src) -{ - /* Tests RAW noise, not conditioned seeds: the hashed chunk counter makes - * two GenerateSeed() outputs differ even with every source dead. */ - byte* a; - byte* b; - word32 len; - word32 i; - byte diff; - int s; - int ret; - - if (src == NULL) { - return BAD_FUNC_ARG; - } - - ret = wc_NoiseSrc_Init(src); - if (ret != 0) { - return ret; - } - - /* Two gathers side by side in the work buffer. */ - len = src->workSz / 2U; - if (len > 64U) { - len = 64U; - } - if (len == 0U) { - return BUFFER_E; - } - a = src->work; - b = src->work + len; - - for (s = 0; s < (int)src->numSrc; s++) { - if ((src->degraded & (word16)(1U << s)) != 0) { - continue; /* already dropped - no point re-testing it */ - } - ret = NoiseSrc_Gather(src, a, len, s); - if (ret == 0) { - ret = NoiseSrc_Gather(src, b, len, s); - } - if (ret != 0) { - break; - } - - /* A source stuck at any constant - including one whose clock was - * never enabled - produces identical gathers. */ - /* ConstantCompare, not XMEMCMP: raw pre-conditioning samples. */ - if (ConstantCompare(a, b, (int)len) == 0) { - ret = ENTROPY_RT_E; - if (s == 0) { - src->failed = ret; - break; - } - src->degraded |= (word16)(1U << s); - ret = 0; - continue; - } - - /* Constant-octet check, accumulated rather than early-exit. */ - diff = 0; - for (i = 1; i < len; i++) { - diff |= (byte)(a[i] ^ a[0]); - } - if (diff == 0) { - ret = ENTROPY_RT_E; - if (s == 0) { - src->failed = ret; - break; - } - src->degraded |= (word16)(1U << s); - ret = 0; - continue; - } - } - - /* A gather error above is deliberately not latched - see the policy note - * at the top of this module. */ - ForceZero(src->work, src->workSz); - - return ret; -} - -#endif /* WOLFSSL_NOISE_SRC */ - - /* Begin wc_GenerateSeed Implementations */ #if defined(CUSTOM_RAND_GENERATE_SEED) @@ -11603,13 +11134,16 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) /* TI C2000 (C28x) oscillator-jitter entropy source. The part has no * TRNG; the noise bit is the LSB of a Dual-Clock Comparator measurement, * oversampled past its measured min-entropy, health-tested per SP800-90B - * 4.4 and SHA-256 conditioned before feeding the Hash-DRBG. + * 4.4 and SHA3-256 conditioned by the generic wc_NoiseSrc_* layer in + * wolfcrypt/src/rng_noisesrc.c (outside the FIPS boundary) before feeding + * the Hash-DRBG. * - * Build: define WOLFSSL_C2000_ENTROPY and add - * wolfcrypt/src/port/ti/ti-c2000-entropy.c with the C2000Ware driverlib - * headers on the include path. Tuning macros, hardware overrides and the - * characterization: wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h and - * IDE/C2000/README.md. + * Build: define WOLFSSL_C2000_ENTROPY (settings.h then turns on + * WOLFSSL_NOISE_SRC and WOLFSSL_SHA3) and add wolfcrypt/src/rng_noisesrc.c + * and wolfcrypt/src/port/ti/ti-c2000-entropy.c with the C2000Ware + * driverlib headers on the include path. Tuning macros, hardware + * overrides and the characterization: + * wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h and IDE/C2000/README.md. * * Blocking by design: ~26 ms per 32-octet chunk per source at the default * window, ~100 ms for a typical seed, ~420 ms for the one-time startup diff --git a/wolfcrypt/src/rng_noisesrc.c b/wolfcrypt/src/rng_noisesrc.c new file mode 100644 index 00000000000..a57605f09c8 --- /dev/null +++ b/wolfcrypt/src/rng_noisesrc.c @@ -0,0 +1,522 @@ +/* rng_noisesrc.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include + +#ifdef WOLFSSL_NOISE_SRC + +#include + +#ifndef WOLFSSL_SHA3 + #error "rng_noisesrc.c conditions with SHA3-256: define WOLFSSL_SHA3" +#endif + +#ifdef HAVE_FIPS + /* for fips @wc_fips: this file sits outside the module boundary, and the + * fips.h redirect is what makes its SHA3 calls approved service calls. */ + #include +#endif + +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* Generic SP800-90B noise source. Configuration struct and API: + * wolfssl/wolfcrypt/rng_noisesrc.h. + * + * Entropy model. A port supplies raw, unconditioned octets through one + * callback. Each raw bit is credited hmin/100 bits of min-entropy, so a + * full-entropy output octet needs 8 / (hmin/100) raw bits, and margin + * oversamples on top of that; WC_NOISE_RAW_PER_SRC() turns the two into the + * raw octets gathered per conditioner chunk. Only source 0 is budgeted - any + * further source is hashed in as defence in depth, and extra hash input can + * never subtract entropy. + * + * Every gathered octet passes the 4.4.1 Repetition Count Test and the 4.4.2 + * Adaptive Proportion Test (sliding window, as in wolfEntropy) before + * reaching the conditioner, and _Init() runs the 4.3 startup test over + * startupOctets per source first. + * + * Failure is reset-and-retry, as for wolfEntropy. A test verdict on any + * source - a health test or a _SelfTest() liveness check - fails the call with + * no output and resets that source's test state, so the next call reruns its + * 4.3 startup test before anything is released again. A source that stays + * broken therefore fails every call. An error from the sample callback + * propagates as returned and is retryable: it says the hardware did not + * answer, not that the noise is bad. + * + * Cutoffs belong to the caller and must match the assumed hmin, or they either + * never trip or trip constantly. For H bits of min-entropy per octet at + * alpha = 2^-30: RCT C = 1 + ceil(30/H), APT C = 1 + CRITBINOM(W, 2^-H, + * 1-alpha). Zero selects the values for H = 1 and W = 512: + * RCT C = 1 + 30 = 31, APT C = 1 + CRITBINOM(512, 0.5, 1-2^-30) = 325. + * + * No locking. The instance is caller-owned state, so a port sharing one + * across threads provides its own mutual exclusion. */ + +#if WC_NOISE_CHUNK_SZ != WC_SHA3_256_DIGEST_SIZE + #error "WC_NOISE_CHUNK_SZ must match WC_SHA3_256_DIGEST_SIZE" +#endif + +/* Clear both continuous tests. A cleared state is what makes the next call + * rerun the startup test. */ +static void NoiseSrc_HealthReset(wc_NoiseHealth* st) +{ + ForceZero(st, sizeof(*st)); +} + +/* Whether a startup test must pass before samples are released: true until + * one has, and again after any failure reset the state. */ +static int NoiseSrc_HealthNeeded(const wc_NoiseHealth* st) +{ + return (st->aptTotal == 0) || (!st->rctHavePrev); +} + +/* SP800-90B 4.4.1 Repetition Count Test on one sample. */ +static int NoiseSrc_Rct(const wc_NoiseSrc* src, wc_NoiseHealth* st, byte v) +{ + if (st->rctHavePrev && (v == st->rctPrev)) { + st->rctCnt++; + if (st->rctCnt >= src->rctCutoff) { + return ENTROPY_RT_E; + } + } + else { + st->rctPrev = v; + st->rctHavePrev = 1; + st->rctCnt = 1; + } + return 0; +} + +/* SP800-90B 4.4.2 Adaptive Proportion Test on one sample, over a sliding + * window: once aptCutoff - 1 samples are queued, every sample is scored + * against the count of its value in the last aptWindow - 1 samples. */ +static int NoiseSrc_Apt(const wc_NoiseSrc* src, wc_NoiseHealth* st, byte v) +{ + st->aptSamples[st->aptLast] = v; + st->aptLast = (word16)((st->aptLast + 1) % src->aptWindow); + st->aptCnt[v]++; + st->aptTotal++; + + if (st->aptTotal >= src->aptCutoff && st->aptCnt[v] >= src->aptCutoff) { + return ENTROPY_APT_E; + } + if (st->aptTotal == src->aptWindow) { + /* Drop the oldest sample, back to aptWindow - 1 queued. */ + st->aptCnt[st->aptSamples[st->aptFirst]]--; + st->aptFirst = (word16)((st->aptFirst + 1) % src->aptWindow); + st->aptTotal--; + } + return 0; +} + +/* Run each sample through the repetition then proportion test. Stops at the + * first failing sample so the rest of the buffer cannot scrub the state that + * detected it. */ +static int NoiseSrc_HealthRun(const wc_NoiseSrc* src, wc_NoiseHealth* st, + const byte* buf, word32 len) +{ + int ret = 0; + word32 i; + + for (i = 0; (ret == 0) && (i < len); i++) { + byte v = (byte)WC_OCTET(buf[i]); + + ret = NoiseSrc_Rct(src, st, v); + if (ret == 0) { + ret = NoiseSrc_Apt(src, st, v); + } + } + + return ret; +} + +/* len raw octets from one source. No health testing - callers that keep the + * data run NoiseSrc_HealthRun() over it. */ +static int NoiseSrc_Gather(wc_NoiseSrc* src, byte* out, word32 len, int srcIdx) +{ + word32 i; + int ret; + + for (i = 0; i < len; i++) { + ret = src->sampleCb(src->ctx, srcIdx, &out[i]); + if (ret != 0) { + return ret; + } + /* The contract is raw octets, but where CHAR_BIT != 8 a callback can + * legally leave bits above 0xFF in the cell. Mask here so the + * conditioner sees exactly what the health tests scored - they mask + * already - and no high bits leak into the hash. */ + out[i] = WC_OCTET(out[i]); + } + + return 0; +} + +/* SP800-90B 4.3 startup test for one source: startupOctets through the + * continuous tests, streamed through the work buffer. The tests are + * per-sample state machines, so slicing the stream does not change the + * verdict. Any failure leaves the source's state reset so the next call + * starts over. */ +static int NoiseSrc_Startup(wc_NoiseSrc* src, int srcIdx) +{ + wc_NoiseHealth* st = &src->health[srcIdx]; + word32 done; + word32 take; + int ret = 0; + + NoiseSrc_HealthReset(st); + for (done = 0; (ret == 0) && (done < src->startupOctets); done += take) { + take = src->startupOctets - done; + if (take > src->workSz) { + take = src->workSz; + } + ret = NoiseSrc_Gather(src, src->work, take, srcIdx); + if (ret == 0) { + ret = NoiseSrc_HealthRun(src, st, src->work, take); + } + } + if (ret != 0) { + NoiseSrc_HealthReset(st); + } + + ForceZero(src->work, src->workSz); + return ret; +} + +int wc_NoiseSrc_Init(wc_NoiseSrc* src) +{ + int i; + int ret = 0; + + if (src == NULL) { + return BAD_FUNC_ARG; + } + + if (!src->inited) { + if (src->sampleCb == NULL || src->tag == NULL || src->work == NULL) { + return BAD_FUNC_ARG; + } + if (src->numSrc < 1 || src->numSrc > WC_NOISE_SRC_MAX) { + return BAD_FUNC_ARG; + } + + if (src->hmin == 0) { + src->hmin = WC_NOISE_HMIN_DEF; + } + if (src->margin == 0) { + src->margin = WC_NOISE_MARGIN_DEF; + } + if (src->rctCutoff == 0) { + src->rctCutoff = WC_NOISE_RCT_CUTOFF_DEF; + } + if (src->aptWindow == 0) { + src->aptWindow = WC_NOISE_APT_WINDOW_DEF; + } + if (src->aptCutoff == 0) { + src->aptCutoff = WC_NOISE_APT_CUTOFF_DEF; + } + if (src->startupOctets == 0) { + src->startupOctets = WC_NOISE_STARTUP_DEF; + } + + if (src->hmin > 100) { + return BAD_FUNC_ARG; + } + if (src->rctCutoff < 2 || src->aptCutoff < 2 || src->aptWindow < 2) { + return BAD_FUNC_ARG; + } + /* The APT queue in wc_NoiseHealth holds one default window. */ + if (src->aptWindow > WC_NOISE_APT_WINDOW_DEF || + src->aptCutoff > src->aptWindow) { + return BAD_FUNC_ARG; + } + /* Below one APT window the startup pass never exercises that test. */ + if (src->startupOctets < (word32)src->aptWindow) { + return BAD_FUNC_ARG; + } + + src->rawPerSrc = WC_NOISE_RAW_PER_SRC(src->hmin, src->margin); + if (src->rawPerSrc == 0 || + src->workSz < src->rawPerSrc * (word32)src->numSrc) { + return BUFFER_E; + } + + XMEMSET(src->health, 0, sizeof(src->health)); + src->chunkCtr = 0; + /* Set before the startup pass: a failed startup is then retried by + * the next call without revalidating. */ + src->inited = 1; + } + + /* SP800-90B 4.3: before releasing anything, every source not already + * passing pushes startupOctets - more than one APT window - through the + * continuous tests. */ + for (i = 0; (ret == 0) && (i < (int)src->numSrc); i++) { + if (NoiseSrc_HealthNeeded(&src->health[i])) { + ret = NoiseSrc_Startup(src, i); + } + } + + return ret; +} + +void wc_NoiseSrc_Free(wc_NoiseSrc* src) +{ + if (src == NULL) { + return; + } + + if (src->work != NULL && src->workSz > 0) { + ForceZero(src->work, src->workSz); + } + /* The APT queue holds recent raw samples. */ + ForceZero(src->health, sizeof(src->health)); + src->chunkCtr = 0; + src->inited = 0; +} + +int wc_NoiseSrc_GetRaw(wc_NoiseSrc* src, byte* output, word32 len, int srcIdx) +{ + int ret; + + if (src == NULL || output == NULL) { + return BAD_FUNC_ARG; + } + /* Before _Init(): numSrc is caller configuration, so a bad index need not + * pay for the startup test or burn hardware entropy first. */ + if (srcIdx < 0 || srcIdx >= (int)src->numSrc) { + return BAD_FUNC_ARG; + } + + ret = wc_NoiseSrc_Init(src); + if (ret != 0) { + return ret; + } + + ret = NoiseSrc_Gather(src, output, len, srcIdx); + if (ret != 0) { + ForceZero(output, len); + } + return ret; +} + +/* SHA3-256 over tag || BE32 chunkCtr || each source's raw octets in index + * order, into digest. */ +static int NoiseSrc_Condition(wc_NoiseSrc* src, wc_Sha3* sha, byte* digest) +{ + byte ctrBuf[4]; + int i; + int ret; + + ret = wc_InitSha3_256(sha, NULL, INVALID_DEVID); + if (ret != 0) { + ForceZero(sha, sizeof(*sha)); + return ret; + } + + ctrBuf[0] = WC_OCTET(src->chunkCtr >> 24); + ctrBuf[1] = WC_OCTET(src->chunkCtr >> 16); + ctrBuf[2] = WC_OCTET(src->chunkCtr >> 8); + ctrBuf[3] = WC_OCTET(src->chunkCtr); + + ret = wc_Sha3_256_Update(sha, (const byte*)src->tag, + (word32)XSTRLEN(src->tag)); + if (ret == 0) { + ret = wc_Sha3_256_Update(sha, ctrBuf, (word32)sizeof(ctrBuf)); + } + for (i = 0; (ret == 0) && (i < (int)src->numSrc); i++) { + ret = wc_Sha3_256_Update(sha, + src->work + ((word32)i * src->rawPerSrc), + src->rawPerSrc); + } + if (ret == 0) { + ret = wc_Sha3_256_Final(sha, digest); + } + wc_Sha3_256_Free(sha); + ForceZero(sha, sizeof(*sha)); + + return ret; +} + +int wc_NoiseSrc_GenerateSeed(wc_NoiseSrc* src, byte* output, word32 sz) +{ +#ifdef WOLFSSL_SMALL_STACK + wc_Sha3* sha = NULL; +#else + wc_Sha3 sha[1]; +#endif + byte digest[WC_NOISE_CHUNK_SZ]; + byte* raw; + byte* outStart; + word32 outLen; + word32 take; + int i; + int ret; + + if (src == NULL || output == NULL) { + return BAD_FUNC_ARG; + } + if (sz == 0) { + return 0; + } + + /* Kept so a mid-way failure can wipe what already landed: no caller + * should ever see a partially-filled seed buffer. */ + outStart = output; + outLen = sz; + + XMEMSET(digest, 0, sizeof(digest)); + + ret = wc_NoiseSrc_Init(src); + if (ret != 0) { + goto out; + } + +#ifdef WOLFSSL_SMALL_STACK + sha = (wc_Sha3*)XMALLOC(sizeof(wc_Sha3), NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (sha == NULL) { + ret = MEMORY_E; + goto out; + } +#endif + + while (sz > 0) { + for (i = 0; i < (int)src->numSrc; i++) { + raw = src->work + ((word32)i * src->rawPerSrc); + + ret = NoiseSrc_Gather(src, raw, src->rawPerSrc, i); + if (ret != 0) { + goto out; + } + ret = NoiseSrc_HealthRun(src, &src->health[i], raw, + src->rawPerSrc); + if (ret != 0) { + /* The next call reruns this source's startup test. */ + NoiseSrc_HealthReset(&src->health[i]); + goto out; + } + } + + src->chunkCtr++; + ret = NoiseSrc_Condition(src, sha, digest); + if (ret != 0) { + goto out; + } + + take = (sz < (word32)WC_NOISE_CHUNK_SZ) ? sz + : (word32)WC_NOISE_CHUNK_SZ; + XMEMCPY(output, digest, take); + output += take; + sz -= take; + } + +out: + if (ret != 0) { + ForceZero(outStart, outLen); + } + ForceZero(digest, sizeof(digest)); + if (src->work != NULL) { + ForceZero(src->work, src->workSz); + } +#ifdef WOLFSSL_SMALL_STACK + XFREE(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return ret; +} + +int wc_NoiseSrc_SelfTest(wc_NoiseSrc* src) +{ + /* Tests RAW noise, not conditioned seeds: the hashed chunk counter makes + * two GenerateSeed() outputs differ even with every source dead. */ + byte* a; + byte* b; + word32 len; + word32 i; + byte diff; + int s; + int ret; + + if (src == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_NoiseSrc_Init(src); + if (ret != 0) { + return ret; + } + + /* Two gathers side by side in the work buffer. */ + len = src->workSz / 2U; + if (len > 64U) { + len = 64U; + } + if (len == 0U) { + return BUFFER_E; + } + a = src->work; + b = src->work + len; + + for (s = 0; (ret == 0) && (s < (int)src->numSrc); s++) { + /* A gather error propagates without touching the test state. */ + ret = NoiseSrc_Gather(src, a, len, s); + if (ret == 0) { + ret = NoiseSrc_Gather(src, b, len, s); + } + if (ret != 0) { + break; + } + + /* A source stuck at any constant - including one whose clock was + * never enabled - produces identical gathers. */ + /* ConstantCompare, not XMEMCMP: raw pre-conditioning samples. */ + if (ConstantCompare(a, b, (int)len) == 0) { + ret = ENTROPY_RT_E; + } + else { + /* Constant-octet check, accumulated rather than early-exit. */ + diff = 0; + for (i = 1; i < len; i++) { + diff |= (byte)(a[i] ^ a[0]); + } + if (diff == 0) { + ret = ENTROPY_RT_E; + } + } + if (ret != 0) { + /* The source must pass its startup test again before it feeds + * the conditioner. */ + NoiseSrc_HealthReset(&src->health[s]); + } + } + + ForceZero(src->work, src->workSz); + + return ret; +} + +#endif /* WOLFSSL_NOISE_SRC */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 8f9cd7acda9..b74e3d6b633 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -387,6 +387,9 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #ifdef HAVE_WC_RNG_BANK #include #endif + #ifdef WOLFSSL_NOISE_SRC + #include + #endif #endif #include #include @@ -31335,8 +31338,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) /* Synthetic noise sources for the generic wc_NoiseSrc_* layer. A physical * source only trips SP800-90B on genuinely broken hardware, so these drive the - * failure paths - RCT, APT, sampler error, the latch and the self-test - on - * the host instead. */ + * failure paths - RCT, APT, sampler error, the startup rerun after a failure + * and the self-test - on the host instead. */ #define NOISE_TEST_GOOD 0 #define NOISE_TEST_STUCK 1 #define NOISE_TEST_BIASED 2 @@ -31346,10 +31349,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #define NOISE_TEST_SRC1_HWFAIL 6 /* Healthy through the startup tests, then stuck. The failure therefore lands * inside wc_NoiseSrc_GenerateSeed() rather than wc_NoiseSrc_Init(), which is - * the only way to reach the continuous-test branch, the seed wipe and the - * mid-flight drop of an uncredited source. */ + * the only way to reach the continuous-test branch and the seed wipe. */ #define NOISE_TEST_LATE_STUCK 7 #define NOISE_TEST_SRC1_LATE_STUCK 8 +/* Stuck for the first NOISE_TEST_PERIOD draws, then healthy: a source that + * fails its startup test and passes the rerun. */ +#define NOISE_TEST_RECOVER 9 /* Health-test settings the synthetic sources are built against. */ #define NOISE_TEST_HMIN 50 @@ -31360,6 +31365,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #define NOISE_TEST_STARTUP 512 #define NOISE_TEST_PERIOD 64 +/* Two sources at the test budget, or one at the wolfEntropy defaults. */ +#define NOISE_TEST_RAW \ + WC_NOISE_RAW_PER_SRC(NOISE_TEST_HMIN, NOISE_TEST_MARGIN) +#define NOISE_TEST_RAW_DEF \ + WC_NOISE_RAW_PER_SRC(WC_NOISE_HMIN_DEF, WC_NOISE_MARGIN_DEF) +#define NOISE_TEST_WORK_SZ \ + (((2 * NOISE_TEST_RAW) > NOISE_TEST_RAW_DEF) ? (2 * NOISE_TEST_RAW) : \ + NOISE_TEST_RAW_DEF) + typedef struct NoiseTestCtx { word32 pos; word32 drawn; /* samples served, so a source can turn bad after startup */ @@ -31396,7 +31410,7 @@ static int noise_test_sample(void* ctx, int srcIdx, byte* octet) break; /* Credited source healthy, uncredited source's sampler erroring: the - * error must propagate rather than silently dropping the source. */ + * error must propagate and stay retryable. */ case NOISE_TEST_SRC1_HWFAIL: if (srcIdx != 0) { return WC_HW_E; @@ -31405,8 +31419,8 @@ static int noise_test_sample(void* ctx, int srcIdx, byte* octet) *octet = (byte)((c->pos >> 16) & 0xFFU); break; - /* Credited source healthy, uncredited source stuck: the uncredited - * one must be dropped rather than denying service. */ + /* Credited source healthy, uncredited source stuck: the call fails + * and only the stuck source reruns its startup test. */ case NOISE_TEST_SRC1_DEAD: if (srcIdx != 0) { *octet = 0x5A; @@ -31428,7 +31442,7 @@ static int noise_test_sample(void* ctx, int srcIdx, byte* octet) break; /* Two sources, so startup draws startupOctets twice. Only the - * uncredited source goes stuck: seeding must continue. */ + * uncredited source goes stuck, and that still fails the call. */ case NOISE_TEST_SRC1_LATE_STUCK: if ((srcIdx != 0) && (c->drawn > (word32)(2 * NOISE_TEST_STARTUP))) { @@ -31440,6 +31454,15 @@ static int noise_test_sample(void* ctx, int srcIdx, byte* octet) (byte)((unsigned int)srcIdx * 0x5AU)); break; + case NOISE_TEST_RECOVER: + if (c->drawn <= (word32)NOISE_TEST_PERIOD) { + *octet = 0x3C; + break; + } + c->pos = (c->pos * 1103515245U) + 12345U; + *octet = (byte)((c->pos >> 16) & 0xFFU); + break; + case NOISE_TEST_GOOD: default: /* Cheap LCG - only has to be well distributed, not secure. The @@ -31479,44 +31502,45 @@ static void noise_test_cfg(wc_NoiseSrc* src, NoiseTestCtx* ctx, byte* work, * The chunk counter is hashed into every chunk, so "seed1 != seed2" and the * chunk-inequality check below hold even if no noise were gathered at all - * wc_NoiseSrc_SelfTest() says as much. Those checks therefore prove nothing - * about the conditioner. Pinning the actual output covers what they cannot: - * that noise really reaches the hash, that the contributed-mask selects the - * right sources, and that the per-source buffers are hashed in the right - * order. Values are octet streams, so a CHAR_BIT != 8 target must reproduce - * them exactly. */ + * about the conditioner. Pinning the actual SHA3-256 output covers what they + * cannot: that noise really reaches the hash, that every source is hashed, + * and that the per-source buffers are hashed in the right order. Values are + * octet streams, so a CHAR_BIT != 8 target must reproduce them exactly. */ static const byte noise_kat_1src[WC_NOISE_CHUNK_SZ] = { - 0xc4, 0xea, 0x79, 0x58, 0x9b, 0xe4, 0x09, 0x34, - 0xa2, 0x1a, 0x90, 0xb0, 0xd0, 0xac, 0x04, 0x83, - 0xc0, 0xaf, 0x12, 0x08, 0x68, 0xcc, 0xf9, 0x2a, - 0x8b, 0xef, 0x77, 0xce, 0xc6, 0xd5, 0xba, 0xaa, + 0x1b, 0xcb, 0xf3, 0x98, 0xfd, 0x75, 0x75, 0x46, + 0x5f, 0x5b, 0x20, 0x41, 0x0f, 0x76, 0x7c, 0xe9, + 0x98, 0xda, 0x93, 0x7f, 0x85, 0xb9, 0xb0, 0xb4, + 0x7f, 0x70, 0x25, 0x6f, 0x88, 0x19, 0x14, 0xfb, }; #if WC_NOISE_SRC_MAX >= 2 static const byte noise_kat_2src[WC_NOISE_CHUNK_SZ * 3] = { - 0xbf, 0x52, 0x8d, 0xfa, 0x85, 0x08, 0x11, 0xb7, - 0x5b, 0xb6, 0xdd, 0x55, 0x88, 0x75, 0xb3, 0x70, - 0xd4, 0x41, 0x5d, 0x36, 0xac, 0x33, 0x1b, 0xa2, - 0x1c, 0x6a, 0x43, 0x37, 0x72, 0xa5, 0x6b, 0x0d, - 0x0b, 0xd8, 0x7c, 0x1b, 0x6d, 0x99, 0xe4, 0x10, - 0xc2, 0x2e, 0x1b, 0xf2, 0x37, 0x44, 0xca, 0x5e, - 0x6b, 0xc0, 0x72, 0xdb, 0x4e, 0x75, 0xc9, 0x1f, - 0x6a, 0x67, 0xf0, 0x4d, 0xd0, 0x64, 0x50, 0xcc, - 0x6f, 0x43, 0xbf, 0xe9, 0xea, 0x38, 0x97, 0xee, - 0xab, 0xa3, 0xe2, 0xfe, 0xe9, 0x8d, 0x11, 0xbe, - 0x7d, 0x06, 0xba, 0xf9, 0xea, 0xce, 0xda, 0xb5, - 0xc4, 0xd1, 0xcf, 0x57, 0x5c, 0x54, 0x44, 0xa4, + 0xa7, 0x7b, 0x57, 0x8b, 0x82, 0x11, 0x03, 0xec, + 0x03, 0x8f, 0x35, 0xa0, 0x23, 0x55, 0x17, 0x89, + 0xa8, 0x2f, 0x76, 0x54, 0x28, 0xb5, 0x67, 0xfb, + 0x88, 0x53, 0x1c, 0x17, 0x2a, 0x71, 0x65, 0x94, + 0x55, 0xa9, 0xb0, 0xd0, 0xe6, 0x18, 0xea, 0x92, + 0x73, 0xd4, 0x16, 0x3f, 0x86, 0xa2, 0x42, 0x86, + 0xc4, 0xbc, 0xcd, 0xe3, 0x67, 0x49, 0x44, 0x12, + 0x05, 0xb1, 0x49, 0xad, 0x21, 0xa6, 0xe6, 0x62, + 0x62, 0x2e, 0x29, 0xc4, 0x12, 0x03, 0x59, 0xff, + 0x32, 0xec, 0x65, 0xce, 0xf1, 0x7a, 0x02, 0x39, + 0x4a, 0x3c, 0x32, 0xde, 0x39, 0x03, 0x79, 0x81, + 0xd7, 0xd1, 0xda, 0xb2, 0x3e, 0x9d, 0xf0, 0x3a, }; #endif WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) { - wc_NoiseSrc src; + /* ~3 KB with its health-test state: off the stack where that is tight. */ + WOLFSSL_SMALL_STACK_STATIC wc_NoiseSrc src; NoiseTestCtx ctx; - byte work[2 * WC_NOISE_RAW_PER_SRC(NOISE_TEST_HMIN, NOISE_TEST_MARGIN)]; + WOLFSSL_SMALL_STACK_STATIC byte work[NOISE_TEST_WORK_SZ]; byte seed1[WC_NOISE_CHUNK_SZ]; byte seed2[WC_NOISE_CHUNK_SZ]; /* Spans several conditioner chunks. Declared unconditionally: the - * post-startup fail-closed case below needs only one source. */ - byte seedLong[WC_NOISE_CHUNK_SZ * 3]; + * post-startup failure case below needs only one source. */ + WOLFSSL_SMALL_STACK_STATIC byte seedLong[WC_NOISE_CHUNK_SZ * 3]; + word32 drawn; wc_test_ret_t ret; int i; @@ -31527,8 +31551,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) ret = wc_NoiseSrc_Init(&src); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); - if (src.rawPerSrc != - WC_NOISE_RAW_PER_SRC(NOISE_TEST_HMIN, NOISE_TEST_MARGIN)) + if (src.rawPerSrc != NOISE_TEST_RAW) return WC_TEST_RET_ENC_NC; ret = wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)); if (ret != 0) @@ -31545,15 +31568,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) return WC_TEST_RET_ENC_EC(ret); wc_NoiseSrc_Free(&src); - /* Two sources: exercises the per-source work-buffer slicing, per-source - * health state and the uncredited hash-in - the shipping C2000 config. - * The seed spans several conditioner chunks. */ /* A source that only goes bad AFTER the startup tests: the failure must be - * caught by the continuous tests inside wc_NoiseSrc_GenerateSeed(), which - * is the module's fail-closed guarantee. Assert the error is returned AND - * that the caller's buffer is wiped rather than left holding partial - * output. Needs one source only, so it must sit outside the two-source - * region below. */ + * caught by the continuous tests inside wc_NoiseSrc_GenerateSeed(). + * Assert the error is returned AND that the caller's buffer is wiped + * rather than left holding partial output. The failure resets the + * source, so the next call reruns its startup test: still stuck, it fails + * again; healthy, it passes and seeding resumes. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_LATE_STUCK); ret = wc_NoiseSrc_Init(&src); @@ -31567,13 +31587,34 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) if (seedLong[i] != 0) return WC_TEST_RET_ENC_NC; } - /* and the instance must stay failed */ if (wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; + ctx.mode = NOISE_TEST_GOOD; + drawn = ctx.drawn; + ret = wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (ctx.drawn - drawn < (word32)NOISE_TEST_STARTUP + NOISE_TEST_RAW) + return WC_TEST_RET_ENC_NC; + wc_NoiseSrc_Free(&src); + + /* Fails its first startup test, passes the rerun with no intervention. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_RECOVER); + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) + return WC_TEST_RET_ENC_NC; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); wc_NoiseSrc_Free(&src); #if WC_NOISE_SRC_MAX >= 2 + /* Two sources: exercises the per-source work-buffer slicing, per-source + * health state and the uncredited hash-in - the shipping C2000 config. + * The seed spans several conditioner chunks. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); src.numSrc = 2; ret = wc_NoiseSrc_Init(&src); @@ -31610,85 +31651,89 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) return WC_TEST_RET_ENC_EC(ret); wc_NoiseSrc_Free(&src); - /* An uncredited source that trips must not deny service: source 0 carries - * the whole entropy budget, so a dead source 1 is dropped and seeding - * continues. The reverse (source 0 dead) must still fail closed. */ + /* A dead uncredited source fails the call like any other, every time, + * while source 0 keeps its passing state: once source 1 is healthy only + * its startup test reruns. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_SRC1_DEAD); src.numSrc = 2; + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) + return WC_TEST_RET_ENC_NC; + ctx.mode = NOISE_TEST_GOOD; + drawn = ctx.drawn; ret = wc_NoiseSrc_Init(&src); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); - if (src.failed != 0) - return WC_TEST_RET_ENC_NC; - if ((src.degraded & 0x2) == 0) /* source 1 dropped */ - return WC_TEST_RET_ENC_NC; - if ((src.degraded & 0x1) != 0) /* source 0 untouched */ + if (ctx.drawn - drawn != (word32)NOISE_TEST_STARTUP) return WC_TEST_RET_ENC_NC; ret = wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); - ret = wc_NoiseSrc_GenerateSeed(&src, seed2, (word32)sizeof(seed2)); - if (ret != 0) - return WC_TEST_RET_ENC_EC(ret); - if (XMEMCMP(seed1, seed2, sizeof(seed1)) == 0) - return WC_TEST_RET_ENC_NC; wc_NoiseSrc_Free(&src); - /* Same stuck pattern on the credited source still fails closed. */ + /* Same stuck pattern on the credited source fails too. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_STUCK); src.numSrc = 2; if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; wc_NoiseSrc_Free(&src); - /* A sampler error is not a test verdict: it must propagate unlatched and - * stay retryable, never silently drop an uncredited source. */ + /* A sampler error is not a test verdict: it propagates as returned and a + * retry once the hardware answers succeeds, rerunning only that source's + * startup test. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_SRC1_HWFAIL); src.numSrc = 2; if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(WC_HW_E)) return WC_TEST_RET_ENC_NC; - if (src.degraded != 0) /* not a verdict - nothing dropped */ - return WC_TEST_RET_ENC_NC; - if (src.failed != 0) /* retryable - nothing latched */ + ctx.mode = NOISE_TEST_GOOD; + drawn = ctx.drawn; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (ctx.drawn - drawn != (word32)NOISE_TEST_STARTUP) return WC_TEST_RET_ENC_NC; wc_NoiseSrc_Free(&src); - /* Only the uncredited source goes bad after startup: it must be dropped - * mid-flight and seeding must continue, not fail. */ + /* Only the uncredited source goes bad after startup: the call still fails + * with the seed wiped. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_SRC1_LATE_STUCK); src.numSrc = 2; ret = wc_NoiseSrc_Init(&src); if (ret != 0) return WC_TEST_RET_ENC_EC(ret); + XMEMSET(seedLong, 0xA5, sizeof(seedLong)); ret = wc_NoiseSrc_GenerateSeed(&src, seedLong, (word32)sizeof(seedLong)); - if (ret != 0) - return WC_TEST_RET_ENC_EC(ret); - if ((src.degraded & 0x2) == 0) /* source 1 dropped */ - return WC_TEST_RET_ENC_NC; - if (src.failed != 0) /* but the instance is still usable */ + if (ret != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; + for (i = 0; i < (int)sizeof(seedLong); i++) { + if (seedLong[i] != 0) + return WC_TEST_RET_ENC_NC; + } wc_NoiseSrc_Free(&src); #endif - /* Stuck source: RCT trips inside the startup test, and the failure is - * latched for every later call until _Free() clears it. */ + /* Stuck source: RCT trips inside the startup test, and every later call + * reruns it and fails again until the source recovers. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_STUCK); if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; if (wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)) != - ENTROPY_RT_E) + WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; if (wc_NoiseSrc_SelfTest(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; + ctx.mode = NOISE_TEST_GOOD; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); wc_NoiseSrc_Free(&src); - if (src.failed != 0) - return WC_TEST_RET_ENC_NC; /* Biased source: RCT stays clear, APT catches it. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_BIASED); @@ -31703,7 +31748,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) wc_NoiseSrc_Free(&src); /* Periodic source: passes both health tests, so only the self-test's - * identical-gather check catches it. Its constant-octet arm is not + * identical-gather check catches it, and that resets the source so the + * next call reruns its startup test. The constant-octet arm is not * reachable from here - a constant source trips the RCT first - and stays * defence in depth. */ noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), @@ -31713,6 +31759,41 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) return WC_TEST_RET_ENC_EC(ret); if (wc_NoiseSrc_SelfTest(&src) != WC_NO_ERR_TRACE(ENTROPY_RT_E)) return WC_TEST_RET_ENC_NC; + drawn = ctx.drawn; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (ctx.drawn - drawn != (word32)NOISE_TEST_STARTUP) + return WC_TEST_RET_ENC_NC; + wc_NoiseSrc_Free(&src); + + /* Zero configuration selects the wolfEntropy defaults. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.hmin = 0; + src.margin = 0; + src.rctCutoff = 0; + src.aptWindow = 0; + src.aptCutoff = 0; + src.startupOctets = 0; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if ((src.hmin != WC_NOISE_HMIN_DEF) || + (src.margin != WC_NOISE_MARGIN_DEF) || + (src.rctCutoff != WC_NOISE_RCT_CUTOFF_DEF) || + (src.aptWindow != WC_NOISE_APT_WINDOW_DEF) || + (src.aptCutoff != WC_NOISE_APT_CUTOFF_DEF) || + (src.startupOctets != (word32)WC_NOISE_STARTUP_DEF)) + return WC_TEST_RET_ENC_NC; + if (src.rawPerSrc != 320) + return WC_TEST_RET_ENC_NC; + if (ctx.drawn != (word32)WC_NOISE_STARTUP_DEF) + return WC_TEST_RET_ENC_NC; + ret = wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (ctx.drawn != (word32)WC_NOISE_STARTUP_DEF + 320) + return WC_TEST_RET_ENC_NC; wc_NoiseSrc_Free(&src); /* Configuration validation. */ @@ -31730,12 +31811,28 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) return WC_TEST_RET_ENC_NC; noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); - src.hmin = 0; + src.numSrc = 0; if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) return WC_TEST_RET_ENC_NC; noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); - src.numSrc = 0; + src.hmin = 101; + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.rctCutoff = 1; + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + + /* The APT queue holds at most one default window. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.aptWindow = WC_NOISE_APT_WINDOW_DEF + 1; + if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.aptCutoff = (word16)(src.aptWindow + 1); if (wc_NoiseSrc_Init(&src) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) return WC_TEST_RET_ENC_NC; diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index f3bc453cc24..b4562d7ceef 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -54,6 +54,7 @@ nobase_include_HEADERS+= \ wolfssl/wolfcrypt/wolfentropy.h \ wolfssl/wolfcrypt/entropy-timers.h \ wolfssl/wolfcrypt/rng_bank.h \ + wolfssl/wolfcrypt/rng_noisesrc.h \ wolfssl/wolfcrypt/ripemd.h \ wolfssl/wolfcrypt/rsa.h \ wolfssl/wolfcrypt/rc2.h \ diff --git a/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h b/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h index cccff0942bd..5839e457882 100644 --- a/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h +++ b/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h @@ -29,9 +29,10 @@ * distinct oscillators. * * The port supplies only that measurement. The SP800-90B startup and - * continuous health tests, the entropy budget, the SHA-256 conditioner and the - * latched failure state come from the generic wc_NoiseSrc_* layer declared in - * wolfssl/wolfcrypt/random.h, which the tuning macros below configure. + * continuous health tests, the entropy budget, the SHA3-256 conditioner and + * the reset-and-retry failure handling come from the generic wc_NoiseSrc_* + * layer declared in wolfssl/wolfcrypt/rng_noisesrc.h, which the tuning macros + * below configure. * * Characterization behind the defaults is in IDE/C2000/README.md. */ @@ -40,13 +41,12 @@ #define WOLF_CRYPT_PORT_TI_C2000_ENTROPY_H #include -#include +#include #ifdef WOLFSSL_C2000_ENTROPY -/* random.h turns this on for us; catch an include order that defeats it. */ #ifndef WOLFSSL_NOISE_SRC - #error "WOLFSSL_C2000_ENTROPY needs random.h included first" + #error "WOLFSSL_C2000_ENTROPY implies WOLFSSL_NOISE_SRC (settings.h)" #endif /* Owns the DCCs and static buffers with no locking. */ @@ -60,8 +60,9 @@ * C2000Ware driverlib enums, so set them to e.g. DCC_COUNT0SRC_XTAL. * * Set NUM_SRC to 1 to use source 0 only and leave DCC0 free. Source 0 is the - * credited one; source 1 is unaccounted defence-in-depth, so dropping it - * costs no budgeted entropy. */ + * credited one; source 1 is unaccounted defence-in-depth, but a health-test + * failure on either fails the call and reruns that source's startup test on + * the next one. Use 1 if INTOSC2 proves marginal on a board. */ #ifndef WOLFSSL_C2000_ENTROPY_NUM_SRC #define WOLFSSL_C2000_ENTROPY_NUM_SRC 2 #endif @@ -128,7 +129,8 @@ DCC instances when WOLFSSL_C2000_ENTROPY_NUM_SRC > 1" #endif /* ---- SP800-90B 4.4 health tests ----------------------------------------- - * Cutoffs assume 4 bits of min-entropy per octet (8 raw bits x HMIN 0.5) at + * The APT is a sliding window of APT_WINDOW samples (at most 512). Cutoffs + * assume 4 bits of min-entropy per octet (8 raw bits x HMIN 0.5) at * alpha = 2^-30, from the exact binomial: * RCT 4.4.1: C = 1 + ceil(30/H) = 9 * APT 4.4.2: C = 1 + CRITBINOM(W, 2^-H, 1-alpha) = 71 at W = 512 @@ -158,14 +160,14 @@ DCC instances when WOLFSSL_C2000_ENTROPY_NUM_SRC > 1" #endif /* These wrap a wc_NoiseSrc instance configured from the macros above; the - * generic wc_NoiseSrc_* API in random.h is usable directly too. */ + * generic wc_NoiseSrc_* API in rng_noisesrc.h is usable directly too. */ /* Enable the DCCs and run the startup health test. Called automatically on * first use. Claims the configured DCCs for the life of the source; costs * ~420 ms at the defaults. */ WOLFSSL_API int wc_c2000_Entropy_Init(void); -/* Release the DCCs and clear any latched failure. */ +/* Release the DCCs and reset the health-test state. */ WOLFSSL_API void wc_c2000_Entropy_Free(void); /* Unconditioned noise, for characterization and self-test only - runs no @@ -173,8 +175,9 @@ WOLFSSL_API void wc_c2000_Entropy_Free(void); WOLFSSL_API int wc_c2000_Entropy_GetRaw(byte* out, word32 len, int srcIdx); /* Conditioned seed material, called by the wc_GenerateSeed() branch in - * random.c. After a health-test failure this returns the latched error on - * every later call until wc_c2000_Entropy_Free(). */ + * random.c. A health-test failure returns ENTROPY_RT_E or ENTROPY_APT_E with + * no output and resets the failing source; the next call reruns its startup + * test (~210 ms) and fails again if the source is still bad. */ WOLFSSL_API int wc_c2000_GenerateSeed(byte* output, word32 sz); /* Liveness check on the raw noise: each source must produce differing diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index d0f7ef788d7..38875d4a3e8 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -743,104 +743,6 @@ struct WC_RNG { WOLFSSL_API int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz); -/* Ports layered on the generic noise source turn it on implicitly, so a - * user_settings.h only has to name the port. */ -#if defined(WOLFSSL_C2000_ENTROPY) && !defined(WOLFSSL_NOISE_SRC) - #define WOLFSSL_NOISE_SRC -#endif - -#ifdef WOLFSSL_NOISE_SRC - -/* Generic SP800-90B noise source, for parts with a raw physical noise source - * but no TRNG. The port supplies one callback returning an unconditioned - * octet; this layer adds the 4.3 startup test, the 4.4.1 RCT and 4.4.2 APT - * continuous tests, a latched fail-closed state, entropy-budget oversampling - * and a SHA-256 conditioner suitable for feeding wc_GenerateSeed(). - * Implementation and entropy model: wolfcrypt/src/random.c. Worked example: - * wolfcrypt/src/port/ti/ti-c2000-entropy.c. */ - -/* Noise sources one instance can combine. */ -#ifndef WC_NOISE_SRC_MAX - #define WC_NOISE_SRC_MAX 2 -#endif -#if (WC_NOISE_SRC_MAX) < 1 || (WC_NOISE_SRC_MAX) > 16 - /* wc_NoiseSrc.degraded is a word16 bitmask of dropped sources. */ - #error "WC_NOISE_SRC_MAX must be 1..16" -#endif - -/* Conditioner output per chunk in octets (SHA-256). random.c asserts this - * against WC_SHA256_DIGEST_SIZE so the two cannot drift; kept as a literal - * here to avoid pulling sha256.h into random.h. */ -#define WC_NOISE_CHUNK_SZ 32 - -/* Raw octets drawn per source per chunk, from the assumed min-entropy hmin - * (hundredths of a bit per raw bit) and the oversample factor margin, rounded - * up so an hmin that does not divide evenly never under-gathers. Sizes the - * gather of the credited source (index 0); any further source is extra hash - * input and is not budgeted. Exposed so a port can size its work buffer - * without duplicating the formula. */ -#define WC_NOISE_RAW_PER_SRC(hmin, margin) \ - ((word32)((WC_NOISE_CHUNK_SZ * \ - (((8UL * 100UL * (unsigned long)(margin)) + \ - ((unsigned long)(hmin) - 1UL)) / (unsigned long)(hmin))) / 8U)) - -/* Fill *octet with one raw, unconditioned noise octet from source srcIdx. - * Returns 0 on success, negative on hardware failure. */ -typedef int (*wc_NoiseSampleCb)(void* ctx, int srcIdx, byte* octet); - -/* SP800-90B 4.4 state, one per source. Persists across calls by design: the - * tests are continuous, not per-request. */ -typedef struct wc_NoiseHealth { - word16 rctCount; - word16 rctLast; - word16 aptCount; - word16 aptRef; - word16 aptPos; - byte started; -} wc_NoiseHealth; - -/* Caller-owned instance. Fill the configuration members, then call - * wc_NoiseSrc_Init(); it derives rawPerSrc and owns everything after it. */ -typedef struct wc_NoiseSrc { - wc_NoiseSampleCb sampleCb; /* required */ - void* ctx; /* opaque, passed to sampleCb */ - const char* tag; /* domain separation string, required */ - byte* work; /* caller owned, >= numSrc * rawPerSrc */ - word32 workSz; - word32 startupOctets; /* SP800-90B 4.3, per source */ - word32 chunkCtr; /* hashed in so chunks cannot repeat */ - word32 rawPerSrc; /* derived by wc_NoiseSrc_Init */ - wc_NoiseHealth health[WC_NOISE_SRC_MAX]; - int failed; /* latched, cleared only by _Free */ - word16 rctCutoff; /* SP800-90B 4.4.1 */ - word16 aptWindow; /* SP800-90B 4.4.2 */ - word16 aptCutoff; - word16 degraded; /* bitmask of uncredited sources dropped */ - byte numSrc; /* 1 .. WC_NOISE_SRC_MAX */ - byte hmin; /* 1..100, hundredths of a bit per raw bit */ - byte margin; /* oversample factor, >= 1 */ - byte inited; -} wc_NoiseSrc; - -WOLFSSL_API int wc_NoiseSrc_Init(wc_NoiseSrc* src); -WOLFSSL_API void wc_NoiseSrc_Free(wc_NoiseSrc* src); -WOLFSSL_API int wc_NoiseSrc_GenerateSeed(wc_NoiseSrc* src, byte* output, - word32 sz); -/* Raw, unconditioned octets for characterization and self-test only: these run - * no health tests, so the output is not fit for keying material. - * - * They also do not advance the continuous-test state, which has a consequence - * worth knowing: SP800-90B 4.4 scores a repetition run across consecutive - * samples, and the octets drawn here are invisible to that accounting. A run - * that straddles one of these calls is therefore under-counted. Draw - * characterization data before seeding starts, or accept that a stuck source - * spanning the call may take longer to be caught. */ -WOLFSSL_API int wc_NoiseSrc_GetRaw(wc_NoiseSrc* src, byte* output, word32 len, - int srcIdx); -WOLFSSL_API int wc_NoiseSrc_SelfTest(wc_NoiseSrc* src); - -#endif /* WOLFSSL_NOISE_SRC */ - #ifndef WC_NO_RNG diff --git a/wolfssl/wolfcrypt/rng_noisesrc.h b/wolfssl/wolfcrypt/rng_noisesrc.h new file mode 100644 index 00000000000..7bff4c8853f --- /dev/null +++ b/wolfssl/wolfcrypt/rng_noisesrc.h @@ -0,0 +1,159 @@ +/* rng_noisesrc.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! + \file wolfssl/wolfcrypt/rng_noisesrc.h +*/ + +/* Generic SP800-90B noise source, for parts with a raw physical noise source + * but no TRNG. The port supplies one callback returning an unconditioned + * octet; this layer adds the 4.3 startup test, the 4.4.1 RCT and 4.4.2 APT + * continuous tests, entropy-budget oversampling and a SHA3-256 conditioner + * suitable for feeding wc_GenerateSeed(). Implementation and entropy model: + * wolfcrypt/src/rng_noisesrc.c. Worked example: + * wolfcrypt/src/port/ti/ti-c2000-entropy.c. */ + +#ifndef WOLF_CRYPT_RNG_NOISESRC_H +#define WOLF_CRYPT_RNG_NOISESRC_H + +#include + +#ifdef WOLFSSL_NOISE_SRC + +#ifdef __cplusplus + extern "C" { +#endif + +/* Noise sources one instance can combine. Each costs one wc_NoiseHealth, + * about 1.5 KB. */ +#ifndef WC_NOISE_SRC_MAX + #define WC_NOISE_SRC_MAX 2 +#endif +#if (WC_NOISE_SRC_MAX) < 1 || (WC_NOISE_SRC_MAX) > 16 + #error "WC_NOISE_SRC_MAX must be 1..16" +#endif + +/* SP800-90B 4.4 defaults, the wolfEntropy values for H = 1 bit of + * min-entropy per octet at alpha = 2^-30 (derivation in + * wolfcrypt/src/rng_noisesrc.c). A zero cutoff, window or startupOctets in + * a wc_NoiseSrc selects these. */ +#define WC_NOISE_RCT_CUTOFF_DEF 31 +#define WC_NOISE_APT_WINDOW_DEF 512 +#define WC_NOISE_APT_CUTOFF_DEF 325 +/* SP800-90B 4.3: 1024 samples through the continuous tests, plus one APT + * window to fill it. */ +#define WC_NOISE_STARTUP_DEF (1024 + WC_NOISE_APT_WINDOW_DEF) + +/* Conditioner output per chunk in octets (SHA3-256). rng_noisesrc.c asserts + * this against WC_SHA3_256_DIGEST_SIZE so the two cannot drift; kept as a + * literal here to avoid pulling sha3.h into this header. */ +#define WC_NOISE_CHUNK_SZ 32 + +/* Defaults for a zero hmin / margin: wolfEntropy's 0.1 bits per raw bit, no + * extra oversampling, which gathers 320 raw octets per 32-octet chunk. */ +#define WC_NOISE_HMIN_DEF 10 +#define WC_NOISE_MARGIN_DEF 1 + +/* Raw octets drawn per source per chunk, from the assumed min-entropy hmin + * (hundredths of a bit per raw bit) and the oversample factor margin, rounded + * up so an hmin that does not divide evenly never under-gathers. Sizes the + * gather of the credited source (index 0); any further source is extra hash + * input and is not budgeted. Exposed so a port can size its work buffer + * without duplicating the formula. */ +#define WC_NOISE_RAW_PER_SRC(hmin, margin) \ + ((word32)((WC_NOISE_CHUNK_SZ * \ + (((8UL * 100UL * (unsigned long)(margin)) + \ + ((unsigned long)(hmin) - 1UL)) / (unsigned long)(hmin))) / 8U)) + +/* Fill *octet with one raw, unconditioned noise octet from source srcIdx. + * Returns 0 on success, negative on hardware failure. */ +typedef int (*wc_NoiseSampleCb)(void* ctx, int srcIdx, byte* octet); + +/* SP800-90B 4.4.1 Repetition Count Test and 4.4.2 Adaptive Proportion Test + * state, one per source. Persists across calls by design: the tests are + * continuous, not per-request. */ +typedef struct wc_NoiseHealth { + word16 aptSamples[WC_NOISE_APT_WINDOW_DEF]; /* APT circular queue */ + word16 aptCnt[256]; /* per-value count in queue */ + word16 rctCnt; /* run length of rctPrev */ + word16 aptTotal; /* samples in queue */ + word16 aptFirst; + word16 aptLast; + byte rctHavePrev; + byte rctPrev; +} wc_NoiseHealth; + +/* Caller-owned instance. Fill the configuration members, then call + * wc_NoiseSrc_Init(). A zero hmin, margin, cutoff, window or startupOctets + * selects the default. Init derives rawPerSrc and owns everything else. */ +typedef struct wc_NoiseSrc { + wc_NoiseHealth health[WC_NOISE_SRC_MAX]; + wc_NoiseSampleCb sampleCb; /* required */ + void* ctx; /* opaque, passed to sampleCb */ + const char* tag; /* domain separation string, required */ + byte* work; /* caller owned, >= numSrc * rawPerSrc */ + word32 workSz; + word32 startupOctets; /* SP800-90B 4.3, per source */ + word32 chunkCtr; /* hashed in so chunks cannot repeat */ + word32 rawPerSrc; /* derived by wc_NoiseSrc_Init */ + word16 rctCutoff; /* SP800-90B 4.4.1 */ + word16 aptWindow; /* SP800-90B 4.4.2, <= 512 */ + word16 aptCutoff; + byte numSrc; /* 1 .. WC_NOISE_SRC_MAX */ + byte hmin; /* 1..100, 1/100 bits per raw bit */ + byte margin; /* oversample factor, >= 1 */ + byte inited; +} wc_NoiseSrc; + +/* Validates the configuration once, then runs the SP800-90B 4.3 startup test + * for every source whose health-test state is empty: all of them on the first + * call, and on later calls any source a test verdict has reset. The other + * entry points call it first, so a failed startup is retried by the next + * call. */ +WOLFSSL_API int wc_NoiseSrc_Init(wc_NoiseSrc* src); +WOLFSSL_API void wc_NoiseSrc_Free(wc_NoiseSrc* src); +/* A health-test failure on any source returns ENTROPY_RT_E or ENTROPY_APT_E, + * wipes output, and resets that source's test state; the next call reruns the + * 4.3 startup test for it. A sampler error propagates as returned by the + * callback, also with output wiped. */ +WOLFSSL_API int wc_NoiseSrc_GenerateSeed(wc_NoiseSrc* src, byte* output, + word32 sz); +/* Raw, unconditioned octets for characterization and self-test only: these run + * no health tests, so the output is not fit for keying material. + * + * They also do not advance the continuous-test state, which has a consequence + * worth knowing: SP800-90B 4.4 scores a repetition run across consecutive + * samples, and the octets drawn here are invisible to that accounting. A run + * that straddles one of these calls is therefore under-counted. Draw + * characterization data before seeding starts, or accept that a stuck source + * spanning the call may take longer to be caught. */ +WOLFSSL_API int wc_NoiseSrc_GetRaw(wc_NoiseSrc* src, byte* output, word32 len, + int srcIdx); +/* Liveness check on the raw noise. A source that fails it has its test state + * reset, so the next call reruns its startup test. */ +WOLFSSL_API int wc_NoiseSrc_SelfTest(wc_NoiseSrc* src); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFSSL_NOISE_SRC */ +#endif /* WOLF_CRYPT_RNG_NOISESRC_H */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 0400501fcb6..11fbebdda03 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -2212,6 +2212,16 @@ #define WOLFSSL_SHAKE256 #endif +/* Ports layered on the generic noise source (rng_noisesrc.c) turn it on + * implicitly, so a user_settings.h only has to name the port. The noise + * source conditions with SHA3-256, so it pulls SHA-3 in as well. */ +#if defined(WOLFSSL_C2000_ENTROPY) && !defined(WOLFSSL_NOISE_SRC) + #define WOLFSSL_NOISE_SRC +#endif +#if defined(WOLFSSL_NOISE_SRC) && !defined(WOLFSSL_SHA3) + #define WOLFSSL_SHA3 +#endif + /* tiny TLS 1.3 sanity checks: catch incompatible selections early. */ #ifdef WOLFSSL_TINY_TLS13 #if !defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || \