diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index ce0c7faa743..555ce87459a 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -764,6 +764,7 @@ WC_INIT_ERROR_WHEN_CONTENDED WC_LINUXKM_NO_USE_HEAP_WRAPPERS WC_MLDSA_NO_ASM WC_MLKEM_KERNEL_ASM +WC_NOISE_SRC_MAX WC_NO_ASYNC_SLEEP WC_NO_RNG_SIMPLE WC_NO_STATIC_ASSERT @@ -842,6 +843,27 @@ WOLFSSL_BIGINT_TYPES WOLFSSL_BIO_NO_FLOW_STATS WOLFSSL_BUILD_MSG_NO_ZERO_COPY WOLFSSL_BYTESWAP32_ASM +WOLFSSL_C2000_AES +WOLFSSL_C2000_AES_BASE +WOLFSSL_C2000_AES_NO_LOCK +WOLFSSL_C2000_AES_SS_BASE +WOLFSSL_C2000_DEVID +WOLFSSL_C2000_ENTROPY +WOLFSSL_C2000_ENTROPY_APT_CUTOFF +WOLFSSL_C2000_ENTROPY_APT_WINDOW +WOLFSSL_C2000_ENTROPY_HMIN +WOLFSSL_C2000_ENTROPY_MARGIN +WOLFSSL_C2000_ENTROPY_NO_CLK_INIT +WOLFSSL_C2000_ENTROPY_NO_LOCK +WOLFSSL_C2000_ENTROPY_NUM_SRC +WOLFSSL_C2000_ENTROPY_RCT_CUTOFF +WOLFSSL_C2000_ENTROPY_REF_CLK +WOLFSSL_C2000_ENTROPY_SRC0_CLK +WOLFSSL_C2000_ENTROPY_SRC0_DCC +WOLFSSL_C2000_ENTROPY_SRC1_CLK +WOLFSSL_C2000_ENTROPY_SRC1_DCC +WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS +WOLFSSL_C2000_ENTROPY_WINDOW WOLFSSL_CAAM_BLACK_KEY_AESCCM WOLFSSL_CAAM_BLACK_KEY_SM WOLFSSL_CAAM_NO_BLACK_KEY @@ -947,6 +969,7 @@ WOLFSSL_MP_INVMOD_CONSTANT_TIME WOLFSSL_MULTICIRCULATE_ALTNAMELIST WOLFSSL_NETX_DUO WOLFSSL_NEW_PRIME_CHECK +WOLFSSL_NOISE_SRC WOLFSSL_NONBLOCK_OCSP WOLFSSL_NOSHA3_384 WOLFSSL_NOT_WINDOWS_API diff --git a/IDE/C2000/README.md b/IDE/C2000/README.md index f92a62bb501..bbe48fb7af0 100644 --- a/IDE/C2000/README.md +++ b/IDE/C2000/README.md @@ -69,6 +69,274 @@ TI `cl2000` (C28x) miscompiles a couple of ML-DSA 32-bit reductions on this With both, ML-DSA builds at full `-O2` on the C28x with no per-file overrides. +## Hardware AES (AESA) + +The F28P55x/F28P65x carry an "AESA" accelerator (a TI EIP-120t instance) at +`0x00042000` supporting ECB/CBC/CTR/CFB/GCM/CCM with 128/192/256-bit keys. +wolfCrypt drives it through the **crypto-callback** framework rather than by +replacing `wolfcrypt/src/aes.c`: + +- `wolfcrypt/src/port/ti/ti-c2000-aes.c` + `wolfssl/wolfcrypt/port/ti/ti-c2000.h`, + gated on `WOLFSSL_C2000_AES` (which also needs `WOLF_CRYPTO_CB`). +- `wc_C2000_Init(devId)` enables/resets the block and registers the callback. + It must be called **after `wolfCrypt_Init()`** -- that is what marks the + device table slots `INVALID_DEVID`, and registration claims one of those. +- A context opts in with `wc_AesInit(&aes, NULL, WOLFSSL_C2000_DEVID)`; one + initialised with `INVALID_DEVID` stays pure software. Software AES remains + compiled in, so a single image can run identical vectors through both paths + and compare -- which is how this port is validated. +- Anything the hardware cannot do returns `CRYPTOCB_UNAVAILABLE` and falls + through to software: non-block-multiple CBC (ciphertext stealing), key + lengths other than 16/24/32, and every mode outside ECB/CBC/CTR. + +Phase 1 covers ECB, CBC and CTR. GCM/CCM/CMAC remain software for now. + +### Measured throughput (LAUNCHXL-F28P55X at 150 MHz) + +`benchmark` with `WC_USE_DEVID` pointing at the AESA device, so it emits paired +`SW`/`HW` rows: + +| Operation | Software | AESA | Speedup | +|---|---|---|---| +| AES-128-ECB encrypt | 471 KiB/s | 2.39 MiB/s | 5.2x | +| AES-256-ECB encrypt | 377 KiB/s | 2.34 MiB/s | 6.3x | +| AES-128-CBC encrypt | 405 KiB/s | 2.37 MiB/s | 6.0x | +| AES-128-CBC decrypt | 388 KiB/s | 2.36 MiB/s | 6.2x | +| AES-256-CBC encrypt | 333 KiB/s | 2.32 MiB/s | 7.1x | +| AES-256-CBC decrypt | 322 KiB/s | 2.31 MiB/s | 7.3x | +| AES-128-CTR | 408 KiB/s | 1.45 MiB/s | 3.6x | +| AES-256-CTR | 335 KiB/s | 1.45 MiB/s | 4.4x | + +Hardware throughput is essentially key-length independent, as expected for a +pipelined block engine -- so the speedup grows with key size, where software +pays for more rounds. CTR lands lower than ECB/CBC because the port does the +counter increment and the keystream XOR in software (see above); it is still +the mode that gains least in relative terms but it is unambiguously worth +offloading. AES-GCM moves only from ~32 to ~34 KiB/s: only its internal ECB +calls reach the accelerator, and the `GCM_SMALL` byte-wise GHASH dominates. +Doing GCM properly means using the block's own GCM mode, which is phase 2. + +### Build overrides + +All `#ifndef`-guarded in `wolfssl/wolfcrypt/port/ti/ti-c2000.h`: + +| Macro | Default | Purpose | +|---|---|---| +| `WOLFSSL_C2000_DEVID` | `0x2000` | devId passed to `wc_AesInit()` and `wc_CryptoCb_RegisterDevice()` | +| `WOLFSSL_C2000_AES_BASE` | `0x00042000` | AESA register base (`AESA_BASE`) | +| `WOLFSSL_C2000_AES_SS_BASE` | `0x00042C00` | AESA wrapper base (`AESA_SS_BASE`) | +| `WOLFSSL_C2000_AES_NO_LOCK` | off | Assert an external lock instead of requiring `SINGLE_THREADED` | + +The two base addresses are defaulted in the port rather than taken from a +device header, so the same source builds against any C2000 part that places +the block elsewhere. + +### Two things the 16-bit byte forces + +**Octet packing.** C2000Ware's AES API is `uint32_t*`-based. On the C28x a +`byte` buffer holds one octet per 16-bit cell and `sizeof(word32)` is 2, so the +`(uint32_t*)in` cast the TivaWare port (`ti-aes.c`) uses is wrong here. Every +transfer is staged through a local `uint32_t` block using the packing driverlib +actually expects, confirmed against its own vectors +(`driverlib/f28p55x/examples/aes/aes_ex1_ecb_encrypt.c` writes the FIPS-197 key +`2b7e1516...` as `{0x16157e2b, ...}`): + +``` +word[i] = b[4i] | (b[4i+1] << 8) | (b[4i+2] << 16) | (b[4i+3] << 24) +``` + +little-endian octets within each word, words in natural order. The +word-index reversal inside `AES_writeDataBlocking()` is internal to driverlib +and must not be compensated for. The port packs and unpacks locally +(`c2000_WordsFromOctets()` / `c2000_OctetsFromWords()`), staging through +`uint32_t` rather than wolfSSL's `word32`: `word32` is only 32-bit under +`WC_16BIT_CPU`, while driverlib writes `uint32_t` either way, so a `word32` +staging array would be half the size the hardware fills. + +**The hardware CTR counter does not match wolfCrypt's.** Measured on a +LAUNCHXL-F28P55X: with `AES_OPMODE_CTR` + `AES_CTR_WIDTH_128BIT` the first +block matches NIST SP800-38A F.5.1, but later blocks diverge from software as +soon as an increment carries across an octet boundary (the F.5 counter starts +at `...fe ff`, so block 2 already does). The block's 128-bit counter increment +therefore disagrees with `IncrementAesCounter()`, which carries through all 16 +octets. The port instead runs the accelerator in **ECB** mode and keeps the +counter in software: identical hardware block-operation count, correct by +construction. + +Both traps are silent -- the first block is right either way, which is exactly +why the KAT harness checks multi-block, split-call and in-place cases. + +### Chaining state + +`aes->reg` is updated in software (last ciphertext block on encrypt, a copy of +the last input block saved *before* processing on decrypt, so in-place calls +work). `AES_readInitializationVector()` is deliberately not used: the +`IV_IN_OUT` registers only hold the saved context when `CTRL.SAVE_CONTEXT` is +set, which `AES_configureModule()` does not set, and driverlib's reader does +not poll `CTRL.SVCTXTRDY` the way `AES_readTag()` does. + +### Threading + +The AESA block is a single shared resource and the port reloads key, IV and +mode on every operation, so it is re-entrant across `Aes` contexts but **not** +across preemption or an ISR. `ti-c2000.h` therefore `#error`s unless +`SINGLE_THREADED` is defined, or `WOLFSSL_C2000_AES_NO_LOCK` asserts that an +external lock provides the guarantee. + +## Entropy (no TRNG on this part) + +The F28P55x has **no hardware TRNG** -- there is no RNG peripheral anywhere in +C2000Ware for this device, and the one "TRNG" string in `hw_asysctl.h` +(`ASYSCTL_PMMCONFIGDFT_VREFTRNG1P225`) is a voltage-reference *trim range* +field. What the part does have is three independent oscillators -- INTOSC1 and +INTOSC2 (on-chip ~10 MHz RC) and the external crystal that SYSCLK/PLLRAWCLK +derive from -- and two Dual-Clock Comparators that can count one against +another. + +`wolfcrypt/src/port/ti/ti-c2000-entropy.c` (gate `WOLFSSL_C2000_ENTROPY`) +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 +Hash-DRBG. + +The work is split in two. The port file owns only the hardware -- DCC setup, +one measurement, and the noise bit. Everything above that is the generic +`wc_NoiseSrc_*` layer described below, which is not C2000-specific. + +### Measured on hardware + +Captured with the reference example's `ENTROPY_PROBE=1` build (262144 raw bits +per source, LSB extraction) and analyzed on a host with the example's +`tools/entropy_analyze.py`, so these numbers are reproducible rather than +asserted: + +| Source | Hmin/bit | bias | max \|acf\| lag 1..64 | chi-square p | +|---|---|---|---|---| +| INTOSC1 window / PLL counted (DCC1) | **0.932** | -0.0000 | 0.005 | 0.623 | +| INTOSC2 window / PLL counted (DCC0) | 0.843 | -0.0027 | 0.005 | 0.000 | +| ADC LSB, floating input | 0.834 | -0.0086 | 0.073 | 0.000 | + +Min-entropy is the SP800-90B 6.3.1 most-common-value estimate at the 99% upper +confidence bound, taken over the 8-bit octet alphabet and divided by 8. The +octet alphabet is used rather than the bit alphabet because it also catches +structure across adjacent bits: on a stream with strong lag-1 correlation but +no marginal bias, a per-bit estimate reports 0.99 and sees nothing while the +octet estimate collapses. **This is an MCV estimate plus bias and correlation +screening, not a full SP800-90B non-IID assessment** (`ea_non_iid` was not +run); MCV assumes IID, so it is an upper bound, and the low measured +correlation on the credited source is what makes it a reasonable one. + +Read 0.932 against the estimator's ceiling, not against 1.0: at this sample +count a synthetic uniform stream estimates to 0.930 (`entropy_analyze.py +--selftest` prints it), so the credited source is statistically +indistinguishable from uniform here. These are single-run measurements of a +physical source and move a little between runs -- an earlier capture gave +0.924 / 0.775 / 0.865 for the three rows -- but the pass/fail conclusions have +been identical in every run. + +Both DCC sources are gathered and hashed together, but **only INTOSC1 is +credited** with entropy: INTOSC2 estimates lower and fails a chi-square +uniformity check decisively (stat 1972 against 255 degrees of freedom), so it +is defence-in-depth that the budget does not rely on. Hashing extra input can +only add entropy, never remove it. The ADC source is not used by default -- it +also fails chi-square, and it depends on a spare analog pin being left +floating, which is a board property rather than a device one. + +The port then assumes **0.5 bits per raw bit** (`WOLFSSL_C2000_ENTROPY_HMIN`) +and oversamples 2x on top of that (`WOLFSSL_C2000_ENTROPY_MARGIN`), i.e. 32 +raw bits gathered per output octet -- roughly a 4x cushion over the credited +source's measured 0.924. At a 256-cycle window (~25.6 us per bit) a 32-octet +conditioning chunk costs about 26 ms per source. + +### Build overrides + +Everything is `#ifndef`-guarded in `wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h`, so a project can retune it from its own `user_settings.h`: + +| Macro | Default | Purpose | +|---|---|---| +| `WOLFSSL_C2000_ENTROPY_NUM_SRC` | 2 | Set to 1 to use the credited source only and leave the second DCC free | +| `WOLFSSL_C2000_ENTROPY_SRC0_DCC` / `_SRC1_DCC` | `DCC1_BASE` / `DCC0_BASE` | Which DCC instance each source uses | +| `WOLFSSL_C2000_ENTROPY_SRC0_CLK` / `_SRC1_CLK` | `INTOSC1` / `INTOSC2` | Slow (window) clock per source | +| `WOLFSSL_C2000_ENTROPY_REF_CLK` | `DCC_COUNT1SRC_PLL` | Fast clock being counted; `SYSCLK` works at coarser quantization | +| `WOLFSSL_C2000_ENTROPY_NO_CLK_INIT` | off | Application manages the DCC peripheral clocks itself | +| `WOLFSSL_C2000_ENTROPY_WINDOW` | 256 | Slow-clock cycles per noise bit | +| `WOLFSSL_C2000_ENTROPY_HMIN` | 50 | Assumed min-entropy per raw bit, in 1/100 bits | +| `WOLFSSL_C2000_ENTROPY_MARGIN` | 2 | Oversample factor on top of `HMIN` | +| `WOLFSSL_C2000_ENTROPY_RCT_CUTOFF` | 9 | SP800-90B 4.4.1 cutoff | +| `WOLFSSL_C2000_ENTROPY_APT_WINDOW` / `_APT_CUTOFF` | 512 / 71 | SP800-90B 4.4.2 window and cutoff | +| `WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS` | 1024 | SP800-90B 4.3 startup test size per source | +| `WOLFSSL_C2000_ENTROPY_NO_LOCK` | off | Assert an external lock instead of requiring `SINGLE_THREADED` | + +The hardware-selection group is what to reach for if the board already uses a DCC for clock monitoring, or the part is not an F28P55x. If you change `HMIN`, recompute both health-test cutoffs for the new assumed entropy per octet -- a cutoff that does not match either never trips or trips constantly. A build-time check rejects a startup size smaller than one APT window. + +### 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: + +```c +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: + +| 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_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_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. + +### 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. + +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 +``` + +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. + ## Enabling on your build Define a user-settings header (see `IDE/C2000/user_settings.h` for a diff --git a/IDE/C2000/compile.sh b/IDE/C2000/compile.sh index 13de96b92f7..84dc0cc2764 100755 --- a/IDE/C2000/compile.sh +++ b/IDE/C2000/compile.sh @@ -30,7 +30,10 @@ fi OUT=$(mktemp -d) trap 'rm -rf "$OUT"' EXIT -INCS="-I$CGT_ROOT/include -I$WOLFROOT -I$SELF_DIR" +# $SELF_DIR before $WOLFROOT: wolfSSL's documented user_settings.h workflow +# puts one at the repo root, and if $WOLFROOT came first that copy would shadow +# this guard's config and silently compile a different build. +INCS="-I$CGT_ROOT/include -I$SELF_DIR -I$WOLFROOT" CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \ --define=WOLFSSL_USER_SETTINGS --display_error_number --diag_warning=225" @@ -42,7 +45,7 @@ CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \ # covered by the on-target example build, not by this minimal guard. SRCS="error wc_port memory logging misc coding \ sha sha256 sha512 sha3 wc_mldsa random ecc sp_int sp_c32 \ - aes cmac chacha poly1305 \ + aes cmac chacha poly1305 cryptocb \ curve25519 ed25519 fe_operations ge_operations \ curve448 ed448 fe_448 ge_448" @@ -60,6 +63,51 @@ 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 +# 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 + +# 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 +# install to include it. +if [ -n "${C2000WARE:-}" ]; then + DRV="$C2000WARE/driverlib/f28p55x/driverlib" + DRVINCS="-I$DRV \ + -I$C2000WARE/device_support/f28p55x/common/include \ + -I$C2000WARE/device_support/f28p55x/headers/include" + for p in "ti-c2000-aes:--define=WOLF_CRYPTO_CB --define=WOLFSSL_C2000_AES" \ + "ti-c2000-entropy:--define=WOLFSSL_C2000_ENTROPY"; do + f=${p%%:*} + d=${p#*:} + printf 'CC port/ti/%s.c ... ' "$f" + if "$CL" $CFLAGS $INCS $DRVINCS $d \ + --compile_only --skip_assembler \ + --asm_directory="$OUT" --obj_directory="$OUT" \ + "$WOLFROOT/wolfcrypt/src/port/ti/$f.c" \ + > "$OUT/$f.log" 2>&1; then + echo "ok" + else + echo "FAIL" + cat "$OUT/$f.log" + rc=1 + fi + done +else + echo "SKIP port/ti/ti-c2000-*.c (set C2000WARE to include them)" +fi + if [ "$rc" -eq 0 ]; then echo "TI C2000 compile-only guard: PASS" else diff --git a/IDE/C2000/user_settings.h b/IDE/C2000/user_settings.h index 01978fcbeda..b6c202bb253 100644 --- a/IDE/C2000/user_settings.h +++ b/IDE/C2000/user_settings.h @@ -17,6 +17,13 @@ #define WOLFSSL_NO_ASM #define NO_INLINE #define SINGLE_THREADED + +/* C28x has a 16-bit int. Without this word32 is `unsigned int` (16 bits) and + * every 32-bit crypto value silently truncates. long is 32-bit and long long + * 64-bit on this toolchain. */ +#define WC_16BIT_CPU +#define SIZEOF_LONG 4 +#define SIZEOF_LONG_LONG 8 #define NO_FILESYSTEM #define NO_WOLFSSL_DIR #define NO_MAIN_DRIVER @@ -47,6 +54,15 @@ #define WOLFSSL_AES_SIV #define WOLFSSL_AES_EAX #define WOLFSSL_AES_DIRECT +#define HAVE_AES_ECB + +/* Crypto callbacks, so cryptocb.c is compile-guarded here too. This is what + * the AESA hardware-AES port (WOLFSSL_C2000_AES, wolfcrypt/src/port/ti/ + * ti-c2000-aes.c) plugs into. WOLFSSL_C2000_AES itself is deliberately NOT + * set: it needs C2000Ware driverlib headers, which this hardware-free guard + * does not have. compile.sh builds that file as a separate opt-in leg when + * C2000WARE is set in the environment. */ +#define WOLF_CRYPTO_CB /* ChaCha20-Poly1305 (chunk size, keystream and Poly1305 length octet I/O) */ #define HAVE_CHACHA diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 1d1a510183e..7aabec99206 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -7902,7 +7902,10 @@ int wc_AesCbcEncrypt(Aes* aes, byte* out, const byte* in, word32 sz) /* in network byte order so start at end and work back */ int i; for (i = WC_AES_BLOCK_SIZE - 1; i >= 0; i--) { - if (++inOutCtr[i]) /* we're done unless we overflow */ + /* WC_OCTET, not a bare ++: where CHAR_BIT != 8 a byte cell + * holds 0x100 and never wraps, so the carry is lost. */ + inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1); + if (inOutCtr[i] != 0) /* we're done unless we overflow */ return; } } @@ -8247,7 +8250,9 @@ static WC_INLINE void IncCtr(byte* ctr, word32 ctrSz) { int i; for (i = (int)ctrSz - 1; i >= 0; i--) { - if (++ctr[i]) + /* See IncrementAesCounter() on why this masks to an octet. */ + ctr[i] = WC_OCTET(ctr[i] + 1); + if (ctr[i] != 0) break; } } @@ -8353,7 +8358,9 @@ static WC_INLINE void IncrementGcmCounter(byte* inOutCtr) /* in network byte order so start at end and work back */ for (i = WC_AES_BLOCK_SIZE - 1; i >= WC_AES_BLOCK_SIZE - CTR_SZ; i--) { - if (++inOutCtr[i]) /* we're done unless we overflow */ + /* See IncrementAesCounter() on why this masks to an octet. */ + inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1); + if (inOutCtr[i] != 0) /* we're done unless we overflow */ return; } } @@ -8377,19 +8384,21 @@ static WC_INLINE void IncrementGcmCounter(byte* inOutCtr) static WC_INLINE void FlattenSzInBits(byte* buf, word32 sz) { - /* Multiply the sz by 8 */ - word32 szHi = (sz >> (8*sizeof(sz) - 3)); + /* Multiply the sz by 8. CHAR_BIT * sizeof, not 8 * sizeof: sizeof counts + * cells, so the latter is a 16-bit width where CHAR_BIT == 16. */ + word32 szHi = (sz >> (CHAR_BIT * sizeof(sz) - 3)); sz <<= 3; - /* copy over the words of the sz into the destination buffer */ - buf[0] = (byte)(szHi >> 24); - buf[1] = (byte)(szHi >> 16); - buf[2] = (byte)(szHi >> 8); - buf[3] = (byte)szHi; - buf[4] = (byte)(sz >> 24); - buf[5] = (byte)(sz >> 16); - buf[6] = (byte)(sz >> 8); - buf[7] = (byte)sz; + /* WC_OCTET, not (byte): the cast keeps the full cell where CHAR_BIT != 8, + * so a 60-octet ciphertext (480 bits) would store 0x1E0 in buf[7]. */ + buf[0] = WC_OCTET(szHi >> 24); + buf[1] = WC_OCTET(szHi >> 16); + buf[2] = WC_OCTET(szHi >> 8); + buf[3] = WC_OCTET(szHi); + buf[4] = WC_OCTET(sz >> 24); + buf[5] = WC_OCTET(sz >> 16); + buf[6] = WC_OCTET(sz >> 8); + buf[7] = WC_OCTET(sz); } @@ -10418,9 +10427,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c, word32 len[4]; /* Lengths are in bytes. Convert to bits. */ - len[0] = (aSz >> (8*sizeof(aSz) - 3)); + len[0] = (aSz >> (CHAR_BIT*sizeof(aSz) - 3)); len[1] = aSz << 3; - len[2] = (cSz >> (8*sizeof(cSz) - 3)); + len[2] = (cSz >> (CHAR_BIT*sizeof(cSz) - 3)); len[3] = cSz << 3; x[0] ^= len[0]; @@ -10479,9 +10488,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c, word32 len[4]; \ word32* x = (word32*)AES_TAG(aes); \ word32* h = (word32*)aes->gcm.H; \ - len[0] = (aes->aSz >> (8*sizeof(aes->aSz) - 3)); \ + len[0] = (aes->aSz >> (CHAR_BIT*sizeof(aes->aSz) - 3)); \ len[1] = aes->aSz << 3; \ - len[2] = (aes->cSz >> (8*sizeof(aes->cSz) - 3)); \ + len[2] = (aes->cSz >> (CHAR_BIT*sizeof(aes->cSz) - 3)); \ len[3] = aes->cSz << 3; \ x[0] ^= len[0]; \ x[1] ^= len[1]; \ @@ -10528,9 +10537,9 @@ void GHASH(Gcm* gcm, const byte* a, word32 aSz, const byte* c, word32 len[4]; \ word32* x = (word32*)AES_TAG(aes); \ word32* h = (word32*)aes->gcm.H; \ - len[0] = (aes->aSz >> (8*sizeof(aes->aSz) - 3)); \ + len[0] = (aes->aSz >> (CHAR_BIT*sizeof(aes->aSz) - 3)); \ len[1] = aes->aSz << 3; \ - len[2] = (aes->cSz >> (8*sizeof(aes->cSz) - 3)); \ + len[2] = (aes->cSz >> (CHAR_BIT*sizeof(aes->cSz) - 3)); \ len[3] = aes->cSz << 3; \ x[0] ^= len[0]; \ x[1] ^= len[1]; \ @@ -15290,20 +15299,22 @@ static WARN_UNUSED_RESULT int roll_auth( word32 remainder; int ret; - /* encode the length in */ + /* encode the length in. WC_OCTET, not (byte): the cast keeps the whole + * cell where CHAR_BIT != 8, so any length above 0xFF would XOR stray bits + * into the CBC-MAC input block. */ if (inSz <= 0xFEFF) { authLenSz = 2; - out[0] ^= (byte)(inSz >> 8); - out[1] ^= (byte)inSz; + out[0] ^= WC_OCTET(inSz >> 8); + out[1] ^= WC_OCTET(inSz); } else { authLenSz = 6; out[0] ^= 0xFF; out[1] ^= 0xFE; - out[2] ^= (byte)(inSz >> 24); - out[3] ^= (byte)(inSz >> 16); - out[4] ^= (byte)(inSz >> 8); - out[5] ^= (byte)inSz; + out[2] ^= WC_OCTET(inSz >> 24); + out[3] ^= WC_OCTET(inSz >> 16); + out[4] ^= WC_OCTET(inSz >> 8); + out[5] ^= WC_OCTET(inSz); } /* Note, the protocol handles auth data up to 2^64, but we are * using 32-bit sizes right now, so the bigger data isn't handled @@ -15342,7 +15353,11 @@ static WC_INLINE void AesCcmCtrInc(byte* B, word32 lenSz) word32 i; for (i = 0; i < lenSz; i++) { - if (++B[WC_AES_BLOCK_SIZE - 1 - i] != 0) return; + /* See IncrementAesCounter(): a bare ++byte leaves 0x100 in the cell + * and never carries. */ + B[WC_AES_BLOCK_SIZE - 1 - i] = + WC_OCTET(B[WC_AES_BLOCK_SIZE - 1 - i] + 1); + if (B[WC_AES_BLOCK_SIZE - 1 - i] != 0) return; } } @@ -16834,13 +16849,15 @@ static void shiftLeftArray(byte* ary, byte shift) ary[i] = 0; } else { - /* shifting over by 7 or less bits */ + /* shifting over by 7 or less bits. WC_OCTET on the stores: a (byte) + * cast does not drop bits shifted past bit 7 where CHAR_BIT != 8, so + * cells would exceed 0xFF and corrupt the feedback register. */ for (i = 0; i < WC_AES_BLOCK_SIZE - 1; i++) { byte carry = (byte)(ary[i+1] & (0XFF << (WOLFSSL_BIT_SIZE - shift))); carry = (byte)(carry >> (WOLFSSL_BIT_SIZE - shift)); - ary[i] = (byte)((ary[i] << shift) + carry); + ary[i] = WC_OCTET((ary[i] << shift) + carry); } - ary[i] = (byte)(ary[i] << shift); + ary[i] = WC_OCTET(ary[i] << shift); } } @@ -17208,7 +17225,9 @@ static WC_INLINE void IncrementKeyWrapCounter(byte* inOutCtr) /* in network byte order so start at end and work back */ for (i = KEYWRAP_BLOCK_SIZE - 1; i >= 0; i--) { - if (++inOutCtr[i]) /* we're done unless we overflow */ + /* See IncrementAesCounter() on why this masks to an octet. */ + inOutCtr[i] = WC_OCTET(inOutCtr[i] + 1); + if (inOutCtr[i] != 0) /* we're done unless we overflow */ return; } } @@ -17219,7 +17238,10 @@ static WC_INLINE void DecrementKeyWrapCounter(byte* inOutCtr) int i; for (i = KEYWRAP_BLOCK_SIZE - 1; i >= 0; i--) { - if (--inOutCtr[i] != 0xFF) /* we're done unless we underflow */ + /* Where CHAR_BIT != 8 a bare --byte underflows 0x00 to 0xFFFF, not + * 0xFF, so the borrow is lost. */ + inOutCtr[i] = WC_OCTET(inOutCtr[i] - 1); + if (inOutCtr[i] != 0xFF) /* we're done unless we underflow */ return; } } diff --git a/wolfcrypt/src/include.am b/wolfcrypt/src/include.am index f0e7ca3c717..86a031b986b 100644 --- a/wolfcrypt/src/include.am +++ b/wolfcrypt/src/include.am @@ -80,6 +80,8 @@ EXTRA_DIST += wolfcrypt/src/port/ti/ti-aes.c \ wolfcrypt/src/port/ti/ti-des3.c \ wolfcrypt/src/port/ti/ti-hash.c \ wolfcrypt/src/port/ti/ti-ccm.c \ + wolfcrypt/src/port/ti/ti-c2000-aes.c \ + wolfcrypt/src/port/ti/ti-c2000-entropy.c \ wolfcrypt/src/port/pic32/pic32mz-crypt.c \ wolfcrypt/src/port/nrf51.c \ wolfcrypt/src/port/aria/aria-crypt.c \ diff --git a/wolfcrypt/src/port/ti/ti-c2000-aes.c b/wolfcrypt/src/port/ti/ti-c2000-aes.c new file mode 100644 index 00000000000..25bf85fba3b --- /dev/null +++ b/wolfcrypt/src/port/ti/ti-c2000-aes.c @@ -0,0 +1,524 @@ +/* port/ti/ti-c2000-aes.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 + */ + +/* AES offload to the TI C2000 "AESA" block (EIP-120t) via crypto callbacks. + * Model and build options: wolfssl/wolfcrypt/port/ti/ti-c2000.h. + * + * Octet/word contract, confirmed against driverlib's own vectors in + * driverlib/f28p55x/examples/aes/aes_ex1_ecb_encrypt.c (FIPS-197 key + * 2b7e1516... is written {0x16157e2b, ...}): word j holds octets 4j..4j+3, + * little-endian within the word, index increasing with octet offset. The + * register-index reversal inside AES_writeDataBlocking() is internal to + * driverlib - do not compensate for it. + * + * This matters because CHAR_BIT == 16 here: a byte buffer is one octet per + * 16-bit cell and sizeof(word32) is 2, so the (uint32_t*) casts the TivaWare + * port uses are wrong. Every transfer stages through a local word32 block, + * which also makes alignment and short trailing blocks non-issues. + * + * Build options (all #ifndef-guarded in ti-c2000.h, see IDE/C2000/README.md): + * WOLFSSL_C2000_AES enable this port (needs WOLF_CRYPTO_CB) + * WOLFSSL_C2000_DEVID devId for wc_AesInit()/RegisterDevice (0x2000) + * WOLFSSL_C2000_AES_BASE AESA register base (0x00042000) + * WOLFSSL_C2000_AES_SS_BASE AESA wrapper base (0x00042C00) + * WOLFSSL_C2000_AES_NO_LOCK assert an external lock instead of requiring + * SINGLE_THREADED + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFSSL_C2000_AES) && !defined(NO_AES) + +#if !defined(WOLF_CRYPTO_CB) + #error "WOLFSSL_C2000_AES requires WOLF_CRYPTO_CB" +#endif + +#include +#include +#include +#include + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +/* C2000Ware driverlib. No name collisions with wolfssl/wolfcrypt/aes.h: + * driverlib prefixes AES_DIRECTION_/AES_KEY_SIZE_/AES_OPMODE_, wolfCrypt uses + * AES_ENCRYPTION/AES_128_KEY_SIZE. */ +#include "aes.h" +#include "sysctl.h" + +/* Words per AES block. Not sizeof-based: sizeof(word32) is 2 here, so + * WC_AES_BLOCK_SIZE / sizeof(word32) would be 8. */ +#define C2000_BLOCK_WORDS 4 + +/* Largest key in words (AES-256). */ +#define C2000_MAX_KEY_WORDS 8 + + +/* Pack octets into words, little-endian within each word. + * + * Deliberately uint32_t, not wolfSSL's word32: word32 is only 32-bit under + * WC_16BIT_CPU, and driverlib writes uint32_t either way - a word32 staging + * array would be half the size the hardware fills. Accumulates with <<= 8 + * because cl2000 miscompiles a single (uint32_t)octet << 24 as a 16-bit + * shift (see misc.c WordsFromBytesBE32). */ +static void c2000_WordsFromOctets(uint32_t* w, const byte* b, word32 wordCnt) +{ + word32 i; + uint32_t r; + for (i = 0; i < wordCnt; i++) { + r = (uint32_t)(b[(i * 4) + 3] & 0xFF); r <<= 8; + r |= (uint32_t)(b[(i * 4) + 2] & 0xFF); r <<= 8; + r |= (uint32_t)(b[(i * 4) + 1] & 0xFF); r <<= 8; + r |= (uint32_t)(b[(i * 4) + 0] & 0xFF); + w[i] = r; + } +} +static void c2000_OctetsFromWords(byte* b, const uint32_t* w, word32 byteCnt) +{ + word32 i; + for (i = 0; i < byteCnt; i++) { + b[i] = WC_OCTET(w[i >> 2] >> ((i & 0x3) * 8)); + } +} +#define C2000_WORDS_FROM_OCTETS(w, b, n) c2000_WordsFromOctets((w), (b), (n)) +#define C2000_OCTETS_FROM_WORDS(b, w, n) c2000_OctetsFromWords((b), (w), (n)) + + +/* Stage one block, zero-padding a short tail. The zeros are what make the + * unused part of a CTR output block equal the raw keystream. */ +static void c2000_LoadBlock(uint32_t blk[C2000_BLOCK_WORDS], const byte* b, + word32 nOctets) +{ + byte tmp[WC_AES_BLOCK_SIZE]; + + if (nOctets >= WC_AES_BLOCK_SIZE) { + C2000_WORDS_FROM_OCTETS(blk, b, C2000_BLOCK_WORDS); + } + else { + XMEMSET(tmp, 0, WC_AES_BLOCK_SIZE); + XMEMCPY(tmp, b, nOctets); + C2000_WORDS_FROM_OCTETS(blk, tmp, C2000_BLOCK_WORDS); + ForceZero(tmp, WC_AES_BLOCK_SIZE); + } +} + + +/* The context must actually be bound to this device. Under + * WOLF_CRYPTO_CB_FIND wolfCrypt offers contexts of any devId, including ones + * whose devKey the backend never populated - encrypting with that all-zero + * key would be silent and catastrophic. Defined ahead of its callers: an + * implicit declaration here would defeat the guard's own prototype. */ +static int c2000_DevIdOk(int devId, const Aes* aes) +{ + if (aes == NULL) { + return 0; + } + if (aes->devId == INVALID_DEVID) { + return 0; + } + return (aes->devId == devId); +} + + +/* Key length (octets) -> driverlib enum; CRYPTOCB_UNAVAILABLE otherwise so + * software takes the operation. */ +static int c2000_KeySize(const Aes* aes, AES_KeySize* ks) +{ + switch (aes->keylen) { + case 16: + *ks = AES_KEY_SIZE_128BIT; + break; + case 24: + *ks = AES_KEY_SIZE_192BIT; + break; + case 32: + *ks = AES_KEY_SIZE_256BIT; + break; + default: + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + return 0; +} + + +#ifdef WOLFSSL_AES_COUNTER +/* Big-endian 128-bit increment, matching the static IncrementAesCounter() in + * aes.c. aes->reg stays authoritative in software so a caller can interleave + * hardware and software CTR calls on one context. */ +static void c2000_IncrCounter(byte* ctr) +{ + int i; + for (i = WC_AES_BLOCK_SIZE - 1; i >= 0; i--) { + /* WC_OCTET, not a bare ++: a byte cell here is 16 bits, so 0xFF + 1 + * is 0x100 and the carry would never propagate. */ + ctr[i] = WC_OCTET(ctr[i] + 1); + if (ctr[i] != 0) { + return; + } + } +} +#endif /* WOLFSSL_AES_COUNTER */ + + +/* Program the block for one operation. Order is load-bearing: a soft reset + * clears CTRL, KEY1 and IV, so it must be reset -> configure -> IV -> key -> + * length, and the length write starts the engine. dataLen is padded to whole + * blocks so the hardware always emits a complete final block. */ +static int c2000_AesSetup(Aes* aes, AES_Direction dir, AES_OpMode mode, + AES_CounterWidth ctrWidth, const byte* iv16, word32 dataLen) +{ + AES_ConfigParams cfg; + AES_KeySize ks; + uint32_t kw[C2000_MAX_KEY_WORDS]; + uint32_t ivw[C2000_BLOCK_WORDS]; + int ret; + + ret = c2000_KeySize(aes, &ks); + if (ret != 0) { + return ret; + } + + AES_disableGlobalInterrupt(WOLFSSL_C2000_AES_SS_BASE); + AES_performSoftReset(WOLFSSL_C2000_AES_BASE); + + XMEMSET(&cfg, 0, sizeof(cfg)); + cfg.direction = dir; + cfg.keySize = ks; + cfg.opMode = mode; + cfg.ctrWidth = ctrWidth; + cfg.ccmLenWidth = AES_CCM_L_1; + cfg.ccmAuthLenWidth = AES_CCM_M_0; + AES_configureModule(WOLFSSL_C2000_AES_BASE, &cfg); + + if (iv16 != NULL) { + C2000_WORDS_FROM_OCTETS(ivw, iv16, C2000_BLOCK_WORDS); + AES_setInitializationVector(WOLFSSL_C2000_AES_BASE, + (const uint32_t*)ivw); + ForceZero(ivw, sizeof(ivw)); + } + + XMEMSET(kw, 0, sizeof(kw)); + C2000_WORDS_FROM_OCTETS(kw, (const byte*)aes->devKey, + (word32)aes->keylen / 4); + AES_setKey1(WOLFSSL_C2000_AES_BASE, (const uint32_t*)kw, ks); + ForceZero(kw, sizeof(kw)); + + AES_setDataLength(WOLFSSL_C2000_AES_BASE, (uint64_t)dataLen); + + return 0; +} + + +/* Straight in -> out loop for the modes the hardware chains itself (ECB, CBC). + * Not AES_processData(): that redoes a 64-bit division every iteration, which + * is costly on a C28x, and we need per-block marshalling anyway. */ +static int c2000_AesProcess(Aes* aes, byte* out, const byte* in, word32 sz, + AES_Direction dir, AES_OpMode mode, const byte* iv16) +{ + uint32_t blk[C2000_BLOCK_WORDS]; + uint32_t outw[C2000_BLOCK_WORDS]; + word32 off; + word32 n; + int ret; + + ret = c2000_AesSetup(aes, dir, mode, AES_CTR_WIDTH_32BIT, iv16, sz); + if (ret != 0) { + return ret; + } + + for (off = 0; off < sz; off += WC_AES_BLOCK_SIZE) { + n = sz - off; + if (n > WC_AES_BLOCK_SIZE) { + n = WC_AES_BLOCK_SIZE; + } + + c2000_LoadBlock(blk, in + off, n); + AES_writeDataBlocking(WOLFSSL_C2000_AES_BASE, (const uint32_t*)blk); + AES_readDataBlocking(WOLFSSL_C2000_AES_BASE, (uint32_t*)outw); + C2000_OCTETS_FROM_WORDS(out + off, outw, n); + } + + ForceZero(blk, sizeof(blk)); + ForceZero(outw, sizeof(outw)); + + return 0; +} + + +#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \ + defined(WOLF_CRYPTO_CB_ONLY_AES) +static int c2000_Ecb(int devId, struct wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesecb.aes; + byte* out = info->cipher.aesecb.out; + const byte* in = info->cipher.aesecb.in; + word32 sz = info->cipher.aesecb.sz; + + if (aes == NULL || out == NULL || in == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (!c2000_DevIdOk(devId, aes)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (sz == 0) { + return 0; + } + if ((sz % WC_AES_BLOCK_SIZE) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + return c2000_AesProcess(aes, out, in, sz, + info->cipher.enc ? AES_DIRECTION_ENCRYPT : AES_DIRECTION_DECRYPT, + AES_OPMODE_ECB, NULL); +} +#endif /* HAVE_AES_ECB || WOLFSSL_AES_DIRECT || WOLF_CRYPTO_CB_ONLY_AES */ + + +#ifdef HAVE_AES_CBC +static int c2000_Cbc(int devId, struct wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aescbc.aes; + byte* out = info->cipher.aescbc.out; + const byte* in = info->cipher.aescbc.in; + word32 sz = info->cipher.aescbc.sz; + byte lastIn[WC_AES_BLOCK_SIZE]; + int enc = info->cipher.enc; + int ret; + + if (aes == NULL || out == NULL || in == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (!c2000_DevIdOk(devId, aes)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (sz == 0) { + return 0; + } + /* Non-block-multiple means a ciphertext-stealing caller; leave to SW. */ + if ((sz % WC_AES_BLOCK_SIZE) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* Saved before processing so an in-place call (in == out) still has it. */ + if (!enc) { + XMEMCPY(lastIn, in + sz - WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE); + } + + ret = c2000_AesProcess(aes, out, in, sz, + enc ? AES_DIRECTION_ENCRYPT : AES_DIRECTION_DECRYPT, + AES_OPMODE_CBC, (const byte*)aes->reg); + if (ret != 0) { + ForceZero(lastIn, sizeof(lastIn)); + return ret; + } + + /* aes->reg must hold the last ciphertext block so successive calls chain. + * Derived in software, not via AES_readInitializationVector(): IV_IN_OUT + * only holds saved context when CTRL.SAVE_CONTEXT is set (which + * AES_configureModule() does not do), and driverlib's reader does not poll + * CTRL.SVCTXTRDY the way AES_readTag() does. */ + if (enc) { + XMEMCPY(aes->reg, out + sz - WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE); + } + else { + XMEMCPY(aes->reg, lastIn, WC_AES_BLOCK_SIZE); + ForceZero(lastIn, WC_AES_BLOCK_SIZE); + } + + return 0; +} +#endif /* HAVE_AES_CBC */ + + +#ifdef WOLFSSL_AES_COUNTER +static int c2000_Ctr(int devId, struct wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesctr.aes; + byte* out = info->cipher.aesctr.out; + const byte* in = info->cipher.aesctr.in; + word32 sz = info->cipher.aesctr.sz; + uint32_t lastOut[C2000_BLOCK_WORDS]; + uint32_t ctrw[C2000_BLOCK_WORDS]; + AES_KeySize ksz; + byte ks[WC_AES_BLOCK_SIZE]; + word32 blocks; + word32 used; + word32 tail; + word32 off; + word32 n; + word32 i; + int ret; + + if (aes == NULL || out == NULL || in == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (!c2000_DevIdOk(devId, aes)) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + /* Decide before mutating state: CRYPTOCB_UNAVAILABLE makes wolfCrypt redo + * the operation in software from the original pointers, so consuming + * leftover keystream first would double-consume it. */ + ret = c2000_KeySize(aes, &ksz); + if (ret != 0) { + return ret; + } + (void)ksz; /* only the accept/reject verdict is needed here */ + + /* Consume leftover keystream: the callback fires before wc_AesCtrEncrypt + * does this itself, so the state is ours. */ + if (aes->left > 0) { + used = (aes->left < sz) ? aes->left : sz; + xorbufout(out, in, + (byte*)aes->tmp + WC_AES_BLOCK_SIZE - aes->left, used); + out += used; + in += used; + sz -= used; + aes->left -= used; + } + if (sz == 0) { + return 0; + } + + /* sz is bounded by RAM on this part, so this cannot overflow in practice; + * the division is written to be safe anyway. */ + blocks = (sz / WC_AES_BLOCK_SIZE) + + (((sz % WC_AES_BLOCK_SIZE) != 0) ? 1U : 0U); + + /* Keystream comes from hardware ECB with the counter kept in software, + * not AES_OPMODE_CTR. Measured on a LAUNCHXL-F28P55X: with + * AES_CTR_WIDTH_128BIT the first block matches NIST SP800-38A F.5.1 but + * later blocks diverge once an increment carries across an octet boundary + * (F.5 starts at ...fe ff, so block 2 already does) - the hardware counter + * disagrees with wolfCrypt's IncrementAesCounter(). ECB costs the same + * number of hardware block operations and is correct by construction. */ + ret = c2000_AesSetup(aes, AES_DIRECTION_ENCRYPT, AES_OPMODE_ECB, + AES_CTR_WIDTH_32BIT, NULL, blocks * WC_AES_BLOCK_SIZE); + if (ret != 0) { + return ret; + } + + for (i = 0; i < blocks; i++) { + off = i * WC_AES_BLOCK_SIZE; + n = sz - off; + if (n > WC_AES_BLOCK_SIZE) { + n = WC_AES_BLOCK_SIZE; + } + + /* Encrypt the counter block to get this block's keystream. */ + C2000_WORDS_FROM_OCTETS(ctrw, (const byte*)aes->reg, + C2000_BLOCK_WORDS); + AES_writeDataBlocking(WOLFSSL_C2000_AES_BASE, (const uint32_t*)ctrw); + AES_readDataBlocking(WOLFSSL_C2000_AES_BASE, (uint32_t*)lastOut); + C2000_OCTETS_FROM_WORDS(ks, lastOut, WC_AES_BLOCK_SIZE); + + xorbufout(out + off, in + off, ks, n); + c2000_IncrCounter((byte*)aes->reg); + } + + tail = sz % WC_AES_BLOCK_SIZE; + if (tail != 0) { + /* Octets tail..15 are unconsumed keystream. Store the whole block; + * software reads it from the end (tmp + BLOCK - left). */ + XMEMCPY(aes->tmp, ks, WC_AES_BLOCK_SIZE); + aes->left = WC_AES_BLOCK_SIZE - tail; + } + else { + aes->left = 0; + } + + ForceZero(ks, sizeof(ks)); + ForceZero(ctrw, sizeof(ctrw)); + ForceZero(lastOut, sizeof(lastOut)); + + return 0; +} +#endif /* WOLFSSL_AES_COUNTER */ + + +int wc_C2000_CryptoCb(int devId, struct wc_CryptoInfo* info, void* ctx) +{ + (void)ctx; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->algo_type != WC_ALGO_TYPE_CIPHER) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + switch (info->cipher.type) { +#ifdef HAVE_AES_CBC + case WC_CIPHER_AES_CBC: + return c2000_Cbc(devId, info); +#endif +#ifdef WOLFSSL_AES_COUNTER + case WC_CIPHER_AES_CTR: + return c2000_Ctr(devId, info); +#endif +#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \ + defined(WOLF_CRYPTO_CB_ONLY_AES) + case WC_CIPHER_AES_ECB: + return c2000_Ecb(devId, info); +#endif + default: + break; + } + + /* CFB, OFB, XTS, GCM, CCM and DES3 fall through to software. */ + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} + + +int wc_C2000_Init(int devId) +{ + /* Device_init() already does this on the LaunchPad BSP; repeated so the + * port works without that BSP. */ + SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_AESA); + SysCtl_delay(10); + SysCtl_resetPeripheral(SYSCTL_PERIPH_RES_AESA); + + AES_disableGlobalInterrupt(WOLFSSL_C2000_AES_SS_BASE); + AES_performSoftReset(WOLFSSL_C2000_AES_BASE); + + return wc_CryptoCb_RegisterDevice(devId, wc_C2000_CryptoCb, NULL); +} + + +int wc_C2000_Cleanup(int devId) +{ + /* A soft reset clears KEY1, so the last key used does not linger in the + * peripheral after the device is unregistered. */ + AES_performSoftReset(WOLFSSL_C2000_AES_BASE); + wc_CryptoCb_UnRegisterDevice(devId); + return 0; +} + +#endif /* WOLFSSL_C2000_AES && !NO_AES */ diff --git a/wolfcrypt/src/port/ti/ti-c2000-entropy.c b/wolfcrypt/src/port/ti/ti-c2000-entropy.c new file mode 100644 index 00000000000..68aa86ba1d2 --- /dev/null +++ b/wolfcrypt/src/port/ti/ti-c2000-entropy.c @@ -0,0 +1,293 @@ +/* port/ti/ti-c2000-entropy.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 + */ + +/* Oscillator-jitter entropy source for the TI C2000 (C28x). Model and build + * options: wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h. Characterization: + * IDE/C2000/README.md. + * + * 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. + * + * 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. + * + * Build options (all #ifndef-guarded in ti-c2000-entropy.h, documented with + * defaults in IDE/C2000/README.md): + * WOLFSSL_C2000_ENTROPY enable this source + * ..._NUM_SRC 1 to use source 0 only, freeing a DCC + * ..._SRC0_DCC / ..._SRC1_DCC DCC instance per source + * ..._SRC0_CLK / ..._SRC1_CLK slow (window) clock per source + * ..._REF_CLK fast clock counted (PLL or SYSCLK) + * ..._NO_CLK_INIT application owns the DCC clocks + * ..._WINDOW slow-clock cycles per noise bit + * ..._HMIN / ..._MARGIN assumed min-entropy and oversample + * ..._RCT_CUTOFF SP800-90B 4.4.1 cutoff + * ..._APT_WINDOW / ..._APT_CUTOFF SP800-90B 4.4.2 window and cutoff + * ..._STARTUP_OCTETS SP800-90B 4.3 startup size per source + * ..._NO_LOCK assert an external lock instead of + * requiring SINGLE_THREADED + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_C2000_ENTROPY + +#include +#include + +#include "dcc.h" +#include "sysctl.h" + +#include + +/* Counter1 seed; the window must not exhaust it. */ +#define C2000_CNT1_SEED 0xFFFFFUL +#define C2000_NUM_SRC WOLFSSL_C2000_ENTROPY_NUM_SRC + +/* Raw octets the generic layer gathers per source per chunk, from the entropy + * budget. Sizes the work buffer; wc_NoiseSrc_Init() rederives and checks it. */ +#define C2000_RAW_PER_SRC \ + WC_NOISE_RAW_PER_SRC(WOLFSSL_C2000_ENTROPY_HMIN, \ + WOLFSSL_C2000_ENTROPY_MARGIN) + +/* Domain separation from any other SHA-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. */ +static byte c2000_work[C2000_NUM_SRC * C2000_RAW_PER_SRC]; + +static wc_NoiseSrc c2000_src; +#ifndef WOLFSSL_C2000_ENTROPY_NO_CLK_INIT +static int c2000_clkOn = 0; +#endif + + +#ifndef WOLFSSL_C2000_ENTROPY_NO_CLK_INIT +/* Map a DCC base to its peripheral-clock enum, so the SRC*_DCC overrides + * decide which clocks are touched instead of hardcoding both instances. */ +static void c2000_clkEnable(uint32_t base) +{ + if (base == DCC0_BASE) { + SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_DCC0); + } + else { + SysCtl_enablePeripheral(SYSCTL_PERIPH_CLK_DCC1); + } +} +static void c2000_clkDisable(uint32_t base) +{ + if (base == DCC0_BASE) { + SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_DCC0); + } + else { + SysCtl_disablePeripheral(SYSCTL_PERIPH_CLK_DCC1); + } +} +#endif + + +/* One DCC measurement. Driven at register level rather than through + * DCC_measureClockFrequency(), which uses float32_t - no FP in the RNG path. + * + * STATUS.ERR is expected, not a fault: the DCC verifies a clock ratio and + * flags an error when counter0 expires with counter1 far from zero, which is + * every measurement here since counter1 is seeded at maximum. The count is + * still valid; driverlib itself waits on (DONE || ERR) and reads it either + * way. Only a real timeout returns WC_HW_E - folding one in as a zero would + * silently inject a deterministic bit. */ +static int c2000_dccSample(uint32_t base, DCC_Count0ClockSource src0, + uint32_t* outCnt) +{ + uint32_t guard; + uint32_t limit; + + DCC_clearErrorFlag(base); + DCC_clearDoneFlag(base); + DCC_disableModule(base); + DCC_disableErrorSignal(base); + DCC_disableDoneSignal(base); + + DCC_setCounter0ClkSource(base, src0); + DCC_setCounter1ClkSource(base, WOLFSSL_C2000_ENTROPY_REF_CLK); + DCC_setCounterSeeds(base, (uint32_t)WOLFSSL_C2000_ENTROPY_WINDOW, + DCC_VALIDSEED_MIN, C2000_CNT1_SEED); + DCC_enableSingleShotMode(base, DCC_MODE_COUNTER_ZERO); + + /* DONE only latches with the done/error signals enabled. */ + DCC_enableErrorSignal(base); + DCC_enableDoneSignal(base); + DCC_enableModule(base); + + limit = ((uint32_t)WOLFSSL_C2000_ENTROPY_WINDOW * 256UL) + 100000UL; + for (guard = 0; guard < limit; guard++) { + if (DCC_getSingleShotStatus(base) || DCC_getErrorStatus(base)) { + *outCnt = (C2000_CNT1_SEED - + (DCC_getCounter1Value(base) & C2000_CNT1_SEED)); + return 0; + } + } + + return WC_HW_E; +} + + +/* wc_NoiseSampleCb: one raw octet, one LSB per measurement, 8 per octet. */ +static int c2000_sampleOctet(void* ctx, int srcIdx, byte* octet) +{ + uint32_t base; + uint32_t cnt; + DCC_Count0ClockSource s0; + int b; + int ret; + word16 acc; + + (void)ctx; + + if (srcIdx == 0) { + base = WOLFSSL_C2000_ENTROPY_SRC0_DCC; + s0 = WOLFSSL_C2000_ENTROPY_SRC0_CLK; + } + else { + base = WOLFSSL_C2000_ENTROPY_SRC1_DCC; + s0 = WOLFSSL_C2000_ENTROPY_SRC1_CLK; + } + + acc = 0; + for (b = 0; b < 8; b++) { + ret = c2000_dccSample(base, s0, &cnt); + if (ret != 0) { + return ret; + } + acc = (word16)(acc | (word16)((cnt & 1U) << b)); + } + *octet = WC_OCTET(acc); + + return 0; +} + + +int wc_c2000_Entropy_Init(void) +{ + if (c2000_src.inited) { + return 0; + } + +#ifndef WOLFSSL_C2000_ENTROPY_NO_CLK_INIT + if (!c2000_clkOn) { + c2000_clkEnable(WOLFSSL_C2000_ENTROPY_SRC0_DCC); +#if C2000_NUM_SRC > 1 + c2000_clkEnable(WOLFSSL_C2000_ENTROPY_SRC1_DCC); +#endif + SysCtl_delay(100); + c2000_clkOn = 1; + } +#endif + + /* Refilled on every attempt: _Free() zeroes the instance. */ + c2000_src.sampleCb = c2000_sampleOctet; + c2000_src.ctx = NULL; + c2000_src.tag = c2000_entropyTag; + c2000_src.work = c2000_work; + c2000_src.workSz = (word32)sizeof(c2000_work); + c2000_src.startupOctets = (word32)WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS; + c2000_src.numSrc = (byte)C2000_NUM_SRC; + c2000_src.hmin = (byte)WOLFSSL_C2000_ENTROPY_HMIN; + c2000_src.margin = (byte)WOLFSSL_C2000_ENTROPY_MARGIN; + c2000_src.rctCutoff = (word16)WOLFSSL_C2000_ENTROPY_RCT_CUTOFF; + c2000_src.aptWindow = (word16)WOLFSSL_C2000_ENTROPY_APT_WINDOW; + c2000_src.aptCutoff = (word16)WOLFSSL_C2000_ENTROPY_APT_CUTOFF; + + return wc_NoiseSrc_Init(&c2000_src); +} + + +void wc_c2000_Entropy_Free(void) +{ + /* Not gated on inited: a startup-test failure returns before that is set, + * and the clocks are already on by then. */ + DCC_disableModule(WOLFSSL_C2000_ENTROPY_SRC0_DCC); +#if C2000_NUM_SRC > 1 + DCC_disableModule(WOLFSSL_C2000_ENTROPY_SRC1_DCC); +#endif +#ifndef WOLFSSL_C2000_ENTROPY_NO_CLK_INIT + c2000_clkDisable(WOLFSSL_C2000_ENTROPY_SRC0_DCC); +#if C2000_NUM_SRC > 1 + c2000_clkDisable(WOLFSSL_C2000_ENTROPY_SRC1_DCC); +#endif + c2000_clkOn = 0; +#endif + + wc_NoiseSrc_Free(&c2000_src); + XMEMSET(&c2000_src, 0, sizeof(c2000_src)); +} + + +int wc_c2000_Entropy_GetRaw(byte* out, word32 len, int srcIdx) +{ + int ret; + + ret = wc_c2000_Entropy_Init(); + if (ret != 0) { + return ret; + } + + return wc_NoiseSrc_GetRaw(&c2000_src, out, len, srcIdx); +} + + +int wc_c2000_GenerateSeed(byte* output, word32 sz) +{ + int ret; + + ret = wc_c2000_Entropy_Init(); + if (ret != 0) { + return ret; + } + + return wc_NoiseSrc_GenerateSeed(&c2000_src, output, sz); +} + + +int wc_c2000_Entropy_SelfTest(void) +{ + int ret; + + ret = wc_c2000_Entropy_Init(); + if (ret != 0) { + return ret; + } + + return wc_NoiseSrc_SelfTest(&c2000_src); +} + +#endif /* WOLFSSL_C2000_ENTROPY */ diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index a600356d9e4..537c932be8a 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -190,6 +190,8 @@ This library contains implementation for the random number generator. #include "wolfssl/wolfcrypt/port/xilinx/xil-versal-trng.h" #elif defined(WOLFSSL_RPIPICO) #include "wolfssl/wolfcrypt/port/rpi_pico/pico.h" +#elif defined(WOLFSSL_C2000_ENTROPY) + #include "wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h" #elif defined(NO_DEV_RANDOM) #elif defined(CUSTOM_RAND_GENERATE) #elif defined(CUSTOM_RAND_GENERATE_BLOCK) @@ -1757,9 +1759,11 @@ int wc_RNG_TestSeed(const byte* seed, word32 seedSz) #endif XMEMSET(byteCounts, 0, MAX_ENTROPY_BITS * sizeof(word16)); - /* Initialize counts for first window */ + /* Indices are WC_OCTET-masked: byteCounts has 256 entries, but a + * byte cell can exceed 255 where CHAR_BIT != 8, so an unmasked seed + * value would index out of bounds. */ for (i = 0; i < windowSize; i++) { - byteCounts[seed[i]]++; + byteCounts[WC_OCTET(seed[i])]++; } /* Check first window - scan all 256 counts */ @@ -1770,15 +1774,16 @@ int wc_RNG_TestSeed(const byte* seed, word32 seedSz) /* Slide window through remaining seed data */ while ((windowStart + windowSize) < seedSz) { /* Remove byte leaving the window */ - byteCounts[seed[windowStart]]--; + byteCounts[WC_OCTET(seed[windowStart])]--; windowStart++; /* Add byte entering the window */ newIdx = windowStart + windowSize - 1; - byteCounts[seed[newIdx]]++; + byteCounts[WC_OCTET(seed[newIdx])]++; /* Accumulate failure flag for new byte's count */ - aptFailed |= (byteCounts[seed[newIdx]] >= WC_RNG_SEED_APT_CUTOFF); + aptFailed |= (byteCounts[WC_OCTET(seed[newIdx])] >= + WC_RNG_SEED_APT_CUTOFF); } #if defined(WOLFSSL_SMALL_STACK) && !defined(WOLFSSL_SMALL_STACK_CACHE) @@ -4036,6 +4041,470 @@ 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; + } + } + + 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); + } + XMEMSET(src->health, 0, 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) @@ -5801,6 +6270,38 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) return 0; } +#elif defined(WOLFSSL_C2000_ENTROPY) + /* 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. + * + * 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. + * + * 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 + * test. Fine at boot, not for a control loop. */ + #if !defined(HAVE_HASHDRBG) + #error "WOLFSSL_C2000_ENTROPY expects HAVE_HASHDRBG to expand the seed" + #endif + + #include + + int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) + { + (void)os; + + if (output == NULL) { + return BUFFER_E; + } + + return wc_c2000_GenerateSeed(output, sz); + } + #elif defined(DOLPHIN_EMULATOR) || defined (WOLFSSL_NDS) int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) @@ -6138,8 +6639,10 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) { word32 i; + /* WC_OCTET, not (byte): the cast does not truncate where + * CHAR_BIT != 8, so sz > 256 would emit values above 0xFF. */ for (i = 0; i < sz; i++ ) - output[i] = (byte)i; + output[i] = WC_OCTET(i); (void)os; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index eed540a0f4a..ed266be61f8 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -928,6 +928,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void); #ifdef WC_RNG_BANK_SUPPORT WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_bank_test(void); #endif +#ifdef WOLFSSL_NOISE_SRC +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void); +#endif #endif /* WC_NO_RNG */ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pwdbased_test(void); #if defined(USE_CERT_BUFFERS_2048) && \ @@ -2564,6 +2567,12 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ else TEST_PASS("RNGBANK test passed!\n"); #endif +#ifdef WOLFSSL_NOISE_SRC + if ((ret = noisesrc_test()) != 0) + TEST_FAIL("NOISESRC test failed!\n", ret); + else + TEST_PASS("NOISESRC test passed!\n"); +#endif #endif /* WC_NO_RNG */ #ifdef WOLFSSL_SHAKE128 @@ -26473,6 +26482,316 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) #endif /* !HAVE_HASHDRBG || CUSTOM_RAND_GENERATE_BLOCK || HAVE_INTEL_RDRAND */ +#if defined(WOLFSSL_NOISE_SRC) && !defined(WC_NO_RNG) + +/* 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. */ +#define NOISE_TEST_GOOD 0 +#define NOISE_TEST_STUCK 1 +#define NOISE_TEST_BIASED 2 +#define NOISE_TEST_HWFAIL 3 +#define NOISE_TEST_PERIODIC 4 +#define NOISE_TEST_SRC1_DEAD 5 +#define NOISE_TEST_SRC1_HWFAIL 6 + +/* Health-test settings the synthetic sources are built against. */ +#define NOISE_TEST_HMIN 50 +#define NOISE_TEST_MARGIN 2 +#define NOISE_TEST_RCT 9 +#define NOISE_TEST_APT_W 512 +#define NOISE_TEST_APT_C 71 +#define NOISE_TEST_STARTUP 512 +#define NOISE_TEST_PERIOD 64 + +typedef struct NoiseTestCtx { + word32 pos; + int mode; +} NoiseTestCtx; + +static int noise_test_sample(void* ctx, int srcIdx, byte* octet) +{ + NoiseTestCtx* c = (NoiseTestCtx*)ctx; + word32 p; + + switch (c->mode) { + case NOISE_TEST_STUCK: + *octet = 0xA5; + break; + + /* Runs of seven keep the RCT clear (cutoff 9) while one value takes + * 448 of every 512 slots, well past the APT cutoff. */ + case NOISE_TEST_BIASED: + p = c->pos++; + *octet = ((p % 8U) == 7U) ? (byte)(p & 0xFFU) : (byte)0xAA; + break; + + case NOISE_TEST_HWFAIL: + return WC_HW_E; + + /* Repeats every NOISE_TEST_PERIOD octets: passes both health tests, + * but successive equal-length gathers come out identical. */ + case NOISE_TEST_PERIODIC: + p = c->pos++; + *octet = (byte)(p % (word32)NOISE_TEST_PERIOD); + break; + + /* Credited source healthy, uncredited source's sampler erroring: the + * error must propagate rather than silently dropping the source. */ + case NOISE_TEST_SRC1_HWFAIL: + if (srcIdx != 0) { + return WC_HW_E; + } + c->pos = (c->pos * 1103515245U) + 12345U; + *octet = (byte)((c->pos >> 16) & 0xFFU); + break; + + /* Credited source healthy, uncredited source stuck: the uncredited + * one must be dropped rather than denying service. */ + case NOISE_TEST_SRC1_DEAD: + if (srcIdx != 0) { + *octet = 0x5A; + 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 + * srcIdx term keeps two sources from producing the same stream. */ + c->pos = (c->pos * 1103515245U) + 12345U; + *octet = (byte)(((c->pos >> 16) & 0xFFU) ^ + (byte)((unsigned int)srcIdx * 0x5AU)); + break; + } + + return 0; +} + +static void noise_test_cfg(wc_NoiseSrc* src, NoiseTestCtx* ctx, byte* work, + word32 workSz, int mode) +{ + XMEMSET(src, 0, sizeof(*src)); + XMEMSET(ctx, 0, sizeof(*ctx)); + ctx->mode = mode; + + src->sampleCb = noise_test_sample; + src->ctx = ctx; + src->tag = "wolfssl-noisesrc-test"; + src->work = work; + src->workSz = workSz; + src->startupOctets = (word32)NOISE_TEST_STARTUP; + src->numSrc = 1; + src->hmin = (byte)NOISE_TEST_HMIN; + src->margin = (byte)NOISE_TEST_MARGIN; + src->rctCutoff = (word16)NOISE_TEST_RCT; + src->aptWindow = (word16)NOISE_TEST_APT_W; + src->aptCutoff = (word16)NOISE_TEST_APT_C; +} + +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t noisesrc_test(void) +{ + wc_NoiseSrc src; + NoiseTestCtx ctx; + byte work[2 * WC_NOISE_RAW_PER_SRC(NOISE_TEST_HMIN, NOISE_TEST_MARGIN)]; + byte seed1[WC_NOISE_CHUNK_SZ]; + byte seed2[WC_NOISE_CHUNK_SZ]; +#if WC_NOISE_SRC_MAX >= 2 + byte seedLong[WC_NOISE_CHUNK_SZ * 3]; +#endif + wc_test_ret_t ret; + + WOLFSSL_ENTER("noisesrc_test"); + + /* Well-distributed source: startup passes, seeds differ, self-test OK. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + 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)) + 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; + ret = wc_NoiseSrc_SelfTest(&src); + if (ret != 0) + 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. */ +#if WC_NOISE_SRC_MAX >= 2 + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.numSrc = 2; + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_NoiseSrc_GenerateSeed(&src, seedLong, (word32)sizeof(seedLong)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + /* Chunk boundaries must not repeat: the counter is hashed in. */ + if (XMEMCMP(seedLong, seedLong + WC_NOISE_CHUNK_SZ, + WC_NOISE_CHUNK_SZ) == 0) + return WC_TEST_RET_ENC_NC; + + /* Raw access per source, and a rejected index. */ + ret = wc_NoiseSrc_GetRaw(&src, seed1, (word32)sizeof(seed1), 0); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_NoiseSrc_GetRaw(&src, seed2, (word32)sizeof(seed2), 1); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(seed1, seed2, sizeof(seed1)) == 0) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_GetRaw(&src, seed1, (word32)sizeof(seed1), 2) != + BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_GetRaw(&src, seed1, (word32)sizeof(seed1), -1) != + BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + ret = wc_NoiseSrc_SelfTest(&src); + if (ret != 0) + 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. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), + NOISE_TEST_SRC1_DEAD); + src.numSrc = 2; + 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 */ + 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. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_STUCK); + src.numSrc = 2; + if (wc_NoiseSrc_Init(&src) != 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. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), + NOISE_TEST_SRC1_HWFAIL); + src.numSrc = 2; + if (wc_NoiseSrc_Init(&src) != 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 */ + 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. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_STUCK); + if (wc_NoiseSrc_Init(&src) != ENTROPY_RT_E) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_Init(&src) != ENTROPY_RT_E) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_GenerateSeed(&src, seed1, (word32)sizeof(seed1)) != + ENTROPY_RT_E) + return WC_TEST_RET_ENC_NC; + if (wc_NoiseSrc_SelfTest(&src) != ENTROPY_RT_E) + return WC_TEST_RET_ENC_NC; + 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); + if (wc_NoiseSrc_Init(&src) != ENTROPY_APT_E) + return WC_TEST_RET_ENC_NC; + wc_NoiseSrc_Free(&src); + + /* A sampler failure propagates instead of becoming a deterministic bit. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_HWFAIL); + if (wc_NoiseSrc_Init(&src) != WC_HW_E) + return WC_TEST_RET_ENC_NC; + 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 + * reachable from here - a constant source trips the RCT first - and stays + * defence in depth. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), + NOISE_TEST_PERIODIC); + ret = wc_NoiseSrc_Init(&src); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (wc_NoiseSrc_SelfTest(&src) != ENTROPY_RT_E) + return WC_TEST_RET_ENC_NC; + wc_NoiseSrc_Free(&src); + + /* Configuration validation. */ + if (wc_NoiseSrc_Init(NULL) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.sampleCb = NULL; + if (wc_NoiseSrc_Init(&src) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.tag = NULL; + if (wc_NoiseSrc_Init(&src) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.hmin = 0; + if (wc_NoiseSrc_Init(&src) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.numSrc = 0; + if (wc_NoiseSrc_Init(&src) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + /* A startup pass shorter than one APT window never exercises the APT. */ + noise_test_cfg(&src, &ctx, work, (word32)sizeof(work), NOISE_TEST_GOOD); + src.startupOctets = 8; + if (wc_NoiseSrc_Init(&src) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + /* Work buffer too small for the entropy budget. */ + noise_test_cfg(&src, &ctx, work, 16, NOISE_TEST_GOOD); + if (wc_NoiseSrc_Init(&src) != BUFFER_E) + return WC_TEST_RET_ENC_NC; + + return 0; +} + +#endif /* WOLFSSL_NOISE_SRC && !WC_NO_RNG */ + #ifdef WC_RNG_BANK_SUPPORT static char *rng_bank_affinity_lock_lock; diff --git a/wolfssl/wolfcrypt/include.am b/wolfssl/wolfcrypt/include.am index 6deb44ebc91..9c5bfe8001e 100644 --- a/wolfssl/wolfcrypt/include.am +++ b/wolfssl/wolfcrypt/include.am @@ -97,6 +97,8 @@ noinst_HEADERS+= \ wolfssl/wolfcrypt/port/pic32/pic32mz-crypt.h \ wolfssl/wolfcrypt/port/ti/ti-hash.h \ wolfssl/wolfcrypt/port/ti/ti-ccm.h \ + wolfssl/wolfcrypt/port/ti/ti-c2000.h \ + wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h \ wolfssl/wolfcrypt/port/nrf51.h \ wolfssl/wolfcrypt/port/nxp/ksdk_port.h \ wolfssl/wolfcrypt/port/nxp/dcp_port.h \ diff --git a/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h b/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h new file mode 100644 index 00000000000..202f5407a7d --- /dev/null +++ b/wolfssl/wolfcrypt/port/ti/ti-c2000-entropy.h @@ -0,0 +1,179 @@ +/* ti-c2000-entropy.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 + */ + +/* Oscillator-jitter entropy source for the TI C2000 (C28x). + * + * The F28P55x has no TRNG, but it has three independent oscillators (INTOSC1, + * INTOSC2 - on-chip ~10 MHz RC - and the crystal behind SYSCLK/PLLRAWCLK) and + * two Dual-Clock Comparators that can count one against another. A DCC + * counts PLL edges inside a window of RC-clock cycles; the LSB of that count + * is the noise bit, carrying the relative phase drift of two physically + * 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. + * + * Characterization behind the defaults is in IDE/C2000/README.md. + */ + +#ifndef WOLF_CRYPT_PORT_TI_C2000_ENTROPY_H +#define WOLF_CRYPT_PORT_TI_C2000_ENTROPY_H + +#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" +#endif + +/* Owns the DCCs and static buffers with no locking. */ +#if !defined(SINGLE_THREADED) && !defined(WOLFSSL_C2000_ENTROPY_NO_LOCK) + #error "WOLFSSL_C2000_ENTROPY needs SINGLE_THREADED or WOLFSSL_C2000_ENTROPY_NO_LOCK" +#endif + +/* ---- Hardware selection ------------------------------------------------- + * Override these if the board needs a DCC for clock monitoring, targets a + * different C2000 part, or wants a different oscillator pairing. Values are + * 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. */ +#ifndef WOLFSSL_C2000_ENTROPY_NUM_SRC + #define WOLFSSL_C2000_ENTROPY_NUM_SRC 2 +#endif +#if (WOLFSSL_C2000_ENTROPY_NUM_SRC < 1) || (WOLFSSL_C2000_ENTROPY_NUM_SRC > 2) + #error "WOLFSSL_C2000_ENTROPY_NUM_SRC must be 1 or 2" +#endif + +#ifndef WOLFSSL_C2000_ENTROPY_SRC0_DCC + #define WOLFSSL_C2000_ENTROPY_SRC0_DCC DCC1_BASE +#endif +#ifndef WOLFSSL_C2000_ENTROPY_SRC0_CLK + #define WOLFSSL_C2000_ENTROPY_SRC0_CLK DCC_COUNT0SRC_INTOSC1 +#endif +#ifndef WOLFSSL_C2000_ENTROPY_SRC1_DCC + #define WOLFSSL_C2000_ENTROPY_SRC1_DCC DCC0_BASE +#endif +#ifndef WOLFSSL_C2000_ENTROPY_SRC1_CLK + #define WOLFSSL_C2000_ENTROPY_SRC1_CLK DCC_COUNT0SRC_INTOSC2 +#endif +/* The fast clock both sources count. SYSCLK works if PLLRAWCLK is unusable, + * at coarser quantization. */ +#ifndef WOLFSSL_C2000_ENTROPY_REF_CLK + #define WOLFSSL_C2000_ENTROPY_REF_CLK DCC_COUNT1SRC_PLL +#endif + +/* Define if the application already manages the DCC peripheral clocks; the + * port then neither enables nor disables them. */ +/* #define WOLFSSL_C2000_ENTROPY_NO_CLK_INIT */ + +/* ---- Sampling ----------------------------------------------------------- + * Window is in slow-clock cycles per noise bit. Counter1 seeds at 0xFFFFF + * and counts down, so the window must stay well under 2^20 PLL cycles; 256 + * (~25.6 us) measured as well as any larger window and is the fastest. */ +#ifndef WOLFSSL_C2000_ENTROPY_WINDOW + #define WOLFSSL_C2000_ENTROPY_WINDOW 256U +#endif + +/* ---- Entropy budget ----------------------------------------------------- + * HMIN is assumed min-entropy per raw bit in 1/100 bits; MARGIN oversamples + * on top. Measured ~0.92 bits/bit for the credited source, so the 0.5 + * default plus 2x is roughly a 4x cushion. Raising HMIN gathers less. */ +#ifndef WOLFSSL_C2000_ENTROPY_HMIN + #define WOLFSSL_C2000_ENTROPY_HMIN 50 +#endif +#ifndef WOLFSSL_C2000_ENTROPY_MARGIN + #define WOLFSSL_C2000_ENTROPY_MARGIN 2 +#endif +#if (WOLFSSL_C2000_ENTROPY_MARGIN) < 1 + #error "WOLFSSL_C2000_ENTROPY_MARGIN must be >= 1" +#endif +#if (WOLFSSL_C2000_ENTROPY_HMIN) < 1 || (WOLFSSL_C2000_ENTROPY_HMIN) > 100 + #error "WOLFSSL_C2000_ENTROPY_HMIN is 1/100 bits per raw bit: use 1..100" +#endif + +/* ---- SP800-90B 4.4 health tests ----------------------------------------- + * 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 + * Recompute both if HMIN changes: a cutoff that does not match the assumed H + * either never trips or trips constantly. */ +#ifndef WOLFSSL_C2000_ENTROPY_RCT_CUTOFF + #define WOLFSSL_C2000_ENTROPY_RCT_CUTOFF 9 +#endif +#ifndef WOLFSSL_C2000_ENTROPY_APT_WINDOW + #define WOLFSSL_C2000_ENTROPY_APT_WINDOW 512 +#endif +#ifndef WOLFSSL_C2000_ENTROPY_APT_CUTOFF + #define WOLFSSL_C2000_ENTROPY_APT_CUTOFF 71 +#endif + +/* SP800-90B 4.3 startup test, octets per source. Must exceed one APT window + * or the startup pass never exercises that test. */ +#ifndef WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS + #define WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS 1024 +#endif +#if WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS < WOLFSSL_C2000_ENTROPY_APT_WINDOW + #error "WOLFSSL_C2000_ENTROPY_STARTUP_OCTETS must be >= APT window" +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* These wrap a wc_NoiseSrc instance configured from the macros above; the + * generic wc_NoiseSrc_* API in random.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. */ +WOLFSSL_API void wc_c2000_Entropy_Free(void); + +/* Unconditioned noise, for characterization and self-test only - runs no + * health tests, so never use it for keying material. srcIdx 0 or 1. */ +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(). */ +WOLFSSL_API int wc_c2000_GenerateSeed(byte* output, word32 sz); + +/* Liveness check on the raw noise: each source must produce differing + * gathers and never a constant octet. */ +WOLFSSL_API int wc_c2000_Entropy_SelfTest(void); + +#ifdef __cplusplus + } +#endif + +#endif /* WOLFSSL_C2000_ENTROPY */ + +#endif /* WOLF_CRYPT_PORT_TI_C2000_ENTROPY_H */ diff --git a/wolfssl/wolfcrypt/port/ti/ti-c2000.h b/wolfssl/wolfcrypt/port/ti/ti-c2000.h new file mode 100644 index 00000000000..9bf44ea950d --- /dev/null +++ b/wolfssl/wolfcrypt/port/ti/ti-c2000.h @@ -0,0 +1,97 @@ +/* ti-c2000.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 + */ + +/* TI C2000 (C28x) on-chip crypto support. + * + * The F28P55x/F28P65x carry an "AESA" accelerator (a TI EIP-120t instance) + * offering ECB/CBC/CTR/CFB/GCM/CCM with 128/192/256-bit keys. wolfCrypt + * reaches it through the crypto-callback framework rather than by replacing + * wolfcrypt/src/aes.c, so software AES stays available: a given Aes context + * opts in by passing WOLFSSL_C2000_DEVID to wc_AesInit(), and a context + * initialised with INVALID_DEVID runs pure software. Anything the hardware + * cannot do returns CRYPTOCB_UNAVAILABLE and falls through to software. + * + * This is a different device from the TivaWare/TM4C block behind + * WOLFSSL_TI_CRYPT (wolfcrypt/src/port/ti/ti-aes.c); the two are not + * interchangeable and must not both be enabled. + */ + +#ifndef WOLF_CRYPT_PORT_TI_C2000_H +#define WOLF_CRYPT_PORT_TI_C2000_H + +#include + +#ifdef WOLFSSL_C2000_AES + +#if defined(WOLFSSL_TI_CRYPT) + #error "WOLFSSL_C2000_AES and WOLFSSL_TI_CRYPT are different devices" +#endif + +/* The AESA block is a single shared resource with no per-context state (key, + * IV and mode are reloaded on every operation), so the port is re-entrant + * across Aes contexts but not across preemption or an ISR. Define + * WOLFSSL_C2000_AES_NO_LOCK to assert that an external lock provides that + * guarantee. */ +#if !defined(SINGLE_THREADED) && !defined(WOLFSSL_C2000_AES_NO_LOCK) + #error "WOLFSSL_C2000_AES needs SINGLE_THREADED or WOLFSSL_C2000_AES_NO_LOCK" +#endif + +/* devId handed to wc_AesInit() and wc_CryptoCb_RegisterDevice(). */ +#ifndef WOLFSSL_C2000_DEVID + #define WOLFSSL_C2000_DEVID 0x2000 +#endif + +/* AESA_BASE / AESA_SS_BASE from C2000Ware inc/hw_memmap.h. Defaulted here so + * the port does not depend on which device header happens to be on the + * include path. */ +#ifndef WOLFSSL_C2000_AES_BASE + #define WOLFSSL_C2000_AES_BASE 0x00042000U +#endif +#ifndef WOLFSSL_C2000_AES_SS_BASE + #define WOLFSSL_C2000_AES_SS_BASE 0x00042C00U +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +struct wc_CryptoInfo; + +/* Enable and reset the AESA block, then register the callback for devId. + * Must be called after wolfCrypt_Init(): wc_CryptoCb_RegisterDevice() looks + * for a slot whose devId is INVALID_DEVID, and the device table is only + * initialised to that value by wolfCrypt_Init(). */ +WOLFSSL_API int wc_C2000_Init(int devId); + +/* Unregister the callback. */ +WOLFSSL_API int wc_C2000_Cleanup(int devId); + +/* The callback itself, exposed so an application can register it by hand. */ +WOLFSSL_API int wc_C2000_CryptoCb(int devId, struct wc_CryptoInfo* info, + void* ctx); + +#ifdef __cplusplus + } +#endif + +#endif /* WOLFSSL_C2000_AES */ + +#endif /* WOLF_CRYPT_PORT_TI_C2000_H */ diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index d9c5a9289b9..4fde71ec290 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -438,6 +438,95 @@ 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); +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 */ + #ifdef HAVE_WNR /* Whitewood netRandom client library */