Skip to content

Commit 3f25c79

Browse files
dgarskedanielinux
authored andcommitted
Peer review fixes
Address wolfIP maintainer review on the supplicant PR: - rp2350: replace ROSC entropy with the hardware TRNG at 0x400f0000. New rp2350_rng.h holds the TRNG register map + a dependency-free block reader (fail-closed on health-test errors); rp2350_rng.c feeds the 192-bit EHR straight to the DRBG seed. wolfIP_getrandom() now routes through a seeded WC_RNG (raw-TRNG fallback / non-crypto build). - raspberry-pico-usb-server: replace the weak 3-LSB ADC sampler with von-Neumann debiased ROSC RANDOMBIT (RP2040 has no TRNG; no crypto consumer in this build). Drop the now-unused hardware_adc link. - supplicant PMKSA: carry pmksa_pmkid / pmksa_have_pmkid through the init snapshot/restore so SAE fast reconnect survives re-init. Add a test that drives the real re-init path. - sae confirm receive: bound the min length by sae.kck_len (32 for H&P, per-group hash for H2E) instead of a hardcoded 8+32. - sae_crypto: fix H2E length comment (P-521 is 99, not 98).
1 parent fdf13c5 commit 3f25c79

9 files changed

Lines changed: 313 additions & 167 deletions

File tree

.github/workflows/codespell.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ jobs:
2323
uses: codespell-project/actions-codespell@v2
2424
with:
2525
skip: .git,./IDE,*.der,*.pem
26-
ignore_words_list: inh,inout,keypair,nd,parm,rcv,ser,tha,HSI,TE,UE,Synopsys,synopsys,FRO
26+
ignore_words_list: inh,inout,keypair,nd,parm,rcv,ser,tha,HSI,TE,UE,Synopsys,synopsys,FRO,ehr

src/port/raspberry-pico-usb-server/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
4646
elseif (CMAKE_C_COMPILER_ID STREQUAL "IAR")
4747

4848
endif ()
49-
target_link_libraries(${PROJECT} PUBLIC hardware_adc)
49+
# rand.c seeds wolfIP_getrandom() from the ROSC RANDOMBIT register (part of
50+
# the pico core headers); no separate hardware library is required.
5051

5152
# Configure compilation flags and libraries for the example without RTOS.
5253
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.

src/port/raspberry-pico-usb-server/src/rand.c

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,57 +18,70 @@
1818
* along with this program; if not, write to the Free Software
1919
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
2020
*/
21+
22+
/* Entropy for wolfIP_getrandom() (see main.c): TCP initial sequence numbers,
23+
* ephemeral ports, DHCP / DNS transaction ids.
24+
*
25+
* The RP2040 has no hardware TRNG, so this uses the ROSC (ring oscillator)
26+
* RANDOMBIT jitter - the same source the pico-sdk pico_rand uses - von-Neumann
27+
* debiased to remove first-order bias. This replaces the previous 3-LSB ADC
28+
* sampler, which was slow (1 ms/sample) and weakly random. There is no
29+
* cryptographic consumer in this build (no wolfCrypt / TLS is linked), so no
30+
* SHA-256 conditioning stage is applied; debiased ROSC bits are sufficient for
31+
* protocol randomization. A crypto build should instead feed a DRBG from a
32+
* conditioned source (cf. the RP2350 hardware TRNG port).
33+
*/
2134
#include "pico/stdlib.h"
22-
#include "hardware/gpio.h"
23-
#include "hardware/adc.h"
35+
#include "hardware/structs/rosc.h"
2436
#include <string.h>
2537

38+
/* Per debiased bit: raw pairs to sample before giving up on a differing pair.
39+
* A healthy ROSC differs roughly every other sample; this budget is only
40+
* approached on a hardware fault. */
41+
#define ROSC_VN_MAX_PAIRS 1024u
2642

27-
#define IN3_PIN 29
28-
#define IN0_PIN 28
29-
#define IN1_PIN 27
30-
#define IN2_PIN 26
31-
32-
const uint32_t IN[4] = {IN0_PIN, IN1_PIN, IN2_PIN, IN3_PIN};
33-
static int adc_initialized = 0;
34-
35-
const int in_a[8] = { 0, 1, 2, 3, 1, 3, 0, 2 };
43+
/* One raw ROSC jitter bit, with a short settle so consecutive samples
44+
* decorrelate. */
45+
static unsigned int rosc_raw_bit(void)
46+
{
47+
volatile int d;
48+
for (d = 0; d < 16; d++) {
49+
}
50+
return (unsigned int)(rosc_hw->randombit & 1u);
51+
}
3652

37-
int custom_random_seed(unsigned char *output, unsigned int sz) {
38-
uint32_t i;
39-
uint32_t result = 0;
40-
uint32_t rd = 0, wsz;
53+
/* von Neumann extractor: emit a bit only when a raw pair differs (10 -> 1,
54+
* 01 -> 0). Falls back to a single raw bit if the pair budget is exhausted so
55+
* a seed is always produced (best-effort, non-crypto path). */
56+
static unsigned int rosc_vn_bit(void)
57+
{
58+
unsigned int a, b, tries;
4159

42-
if (!adc_initialized) {
43-
adc_init();
44-
for (i = 0; i < 4; i++) {
45-
adc_gpio_init(IN[i]);
60+
for (tries = 0; tries < ROSC_VN_MAX_PAIRS; tries++) {
61+
a = rosc_raw_bit();
62+
b = rosc_raw_bit();
63+
if (a != b) {
64+
return a;
4665
}
47-
adc_initialized = 1;
48-
sleep_ms(10);
4966
}
67+
return rosc_raw_bit();
68+
}
5069

51-
/* Perform eight 3-bit samples with sources 0-1-2-4 */
52-
for (i = 0; rd < sz; i = (i + 1) % 8) {
53-
adc_select_input(in_a[i]);
54-
55-
/* Read the least significant 3 bits from the ADC */
56-
result = (result << 3) | (adc_read() & 0x00000007);
57-
58-
/* Introduce a delay to capture environmental noise */
59-
sleep_ms(1);
70+
int custom_random_seed(unsigned char *output, unsigned int sz)
71+
{
72+
unsigned int i, nbits;
73+
unsigned char byte;
6074

61-
/* If we've completed eight samples, copy the result to the output */
62-
if (i == 7) {
63-
wsz = 3;
64-
if (wsz > (sz - rd)) {
65-
wsz = sz - rd;
66-
}
75+
if (output == NULL) {
76+
return -1;
77+
}
6778

68-
memcpy(output + rd, &result, wsz);
69-
rd += wsz;
70-
result = 0;
79+
for (i = 0; i < sz; i++) {
80+
byte = 0;
81+
for (nbits = 0; nbits < 8u; nbits++) {
82+
byte = (unsigned char)((byte << 1) | (rosc_vn_bit() & 1u));
7183
}
84+
output[i] = byte;
7285
}
7386

7487
return 0;

src/port/rp2350_cyw43439/main.c

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,56 @@
2626
#include "cyw43439_driver.h"
2727
#include "cyw43439_wifi.h"
2828
#include "rp2350_clocks.h"
29+
#include "rp2350_rng.h"
2930

3031
#if defined(WOLFIP_WITH_SUPPLICANT)
3132
#include "supplicant.h"
33+
#include <wolfssl/wolfcrypt/random.h> /* WC_RNG for the DRBG-backed path */
3234
#endif
3335

3436
extern void rp2350_uart_init(void);
3537

36-
/* wolfIP entropy hook. The RP2350 ROSC (ring oscillator) jitter at
37-
* 0x400C8000 + 0x1C provides one random bit per read - good for ISN /
38-
* ephemeral-port seeding but not for crypto. For SAE keying material
39-
* the supplicant pulls entropy directly from wolfCrypt's RNG. */
40-
uint32_t wolfIP_getrandom(void)
38+
/* Best-effort hardware TRNG words for the non-crypto stack (TCP ISN,
39+
* ephemeral ports, DHCP/DNS xids). Caches one 192-bit block and dispenses
40+
* six 32-bit words so the hardware is read once per six calls. */
41+
static uint32_t rp2350_trng_word(void)
4142
{
42-
static uint32_t lfsr;
43-
uint32_t bit;
44-
int i;
45-
if (lfsr == 0U) {
46-
for (i = 0; i < 32; i++) {
47-
bit = (*(volatile uint32_t *)0x400C801CUL) & 1U;
48-
lfsr = (lfsr << 1) | bit;
43+
static uint32_t cache[RP2350_TRNG_EHR_WORDS];
44+
static unsigned int have;
45+
if (have == 0U) {
46+
if (rp2350_trng_read_block(cache) != 0) {
47+
/* Hardware fault on a best-effort path: return a non-zero
48+
* constant so the stack still gets a usable value. */
49+
return 0xDEADBEEFU;
4950
}
50-
if (lfsr == 0U) lfsr = 0xDEADBEEFU;
51+
have = RP2350_TRNG_EHR_WORDS;
52+
}
53+
return cache[--have];
54+
}
55+
56+
/* wolfIP entropy hook. When the supplicant (and thus wolfCrypt) is present
57+
* this returns bytes from the seeded Hash-DRBG - the same crypto-quality RNG
58+
* the WPA/SAE keying uses, seeded from the hardware TRNG via
59+
* CUSTOM_RAND_GENERATE_SEED (rp2350_wc_genseed). Otherwise it reads the
60+
* hardware TRNG directly. */
61+
uint32_t wolfIP_getrandom(void)
62+
{
63+
#if defined(WOLFIP_WITH_SUPPLICANT)
64+
static WC_RNG rng;
65+
static int rng_ready;
66+
uint32_t v;
67+
68+
if (!rng_ready && wc_InitRng(&rng) == 0) {
69+
rng_ready = 1;
5170
}
52-
/* Mix in one fresh ROSC bit per call on top of the LFSR. */
53-
bit = (*(volatile uint32_t *)0x400C801CUL) & 1U;
54-
/* Unsigned wrap (0 or 0xFFFFFFFF) for the feedback mask - avoids the
55-
* signed-overflow cppcheck flags on -(int32_t)(...). */
56-
lfsr = (lfsr >> 1) ^ ((0U - (lfsr & 1U)) & 0xD0000001U);
57-
lfsr ^= bit;
58-
return lfsr;
71+
if (rng_ready
72+
&& wc_RNG_GenerateBlock(&rng, (unsigned char *)&v, sizeof(v)) == 0) {
73+
return v;
74+
}
75+
/* DRBG unavailable (e.g. called before wolfCrypt is ready): fall back to
76+
* the raw hardware TRNG so the TCP/IP stack still gets entropy. */
77+
#endif
78+
return rp2350_trng_word();
5979
}
6080

6181
/* wolfIP state - opaque struct allocated by wolfIP_init_static. */
Lines changed: 21 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* rp2350_rng.c - RP2350 ring-oscillator entropy for wolfCrypt
1+
/* rp2350_rng.c - RP2350 hardware TRNG seed source for wolfCrypt
22
*
33
* Copyright (C) 2026 wolfSSL Inc.
44
*
@@ -13,124 +13,47 @@
1313
* (CUSTOM_RAND_GENERATE_SEED in user_settings.h), which in turn generates the
1414
* WPA 4-way SNonce and the WPA3-SAE ephemeral rand/mask scalars.
1515
*
16-
* The RP2350 ROSC_RANDOMBIT register is documented as biased and strongly
17-
* autocorrelated and is explicitly NOT a cryptographic entropy source on its
18-
* own. So this does three things before handing bytes to the DRBG:
19-
* 1. von Neumann debiasing - sample raw ROSC bits in pairs and keep one only
20-
* when the pair differs, removing first-order bias;
21-
* 2. heavy oversampling - a large debiased pool feeds each output block;
22-
* 3. cryptographic conditioning - SHA-256 over the pool produces the seed
23-
* block, compressing a conservative per-bit entropy estimate into a
24-
* full-entropy 256-bit block.
25-
* A startup/continuous health test fails closed (returns -1) if the oscillator
26-
* stops producing differing pairs, rather than emitting a weak seed.
16+
* Entropy comes from the RP2350 hardware TRNG (0x400f0000): a ring-oscillator
17+
* true-RNG with built-in autocorrelation, continuous (CRNGT) and von-Neumann
18+
* health tests (RP2350 datasheet sec. 12.12). Its 192-bit EHR output is a NIST
19+
* SP800-90B entropy source, so it is fed straight into the DRBG seed buffer -
20+
* wolfCrypt's Hash-DRBG (SP800-90A) does the conditioning. The register access
21+
* lives in rp2350_rng.h so the non-crypto wolfIP_getrandom() path can share it.
2722
*
28-
* NOTE: this conditioning is the minimum bar for enabling the SAE/EAP
29-
* supplicant paths on real hardware; if the RP2350 TRNG/SHA hardware entropy
30-
* peripheral is brought up later it should supersede the ROSC pool here.
23+
* A block read fails closed (returns -1) if the source is stuck or a health
24+
* test does not clear, rather than emitting a weak seed.
3125
*/
3226
#include <stdint.h>
3327
#include <wolfssl/wolfcrypt/settings.h>
34-
#include <wolfssl/wolfcrypt/sha256.h>
28+
#include <wolfssl/wolfcrypt/memory.h> /* wc_ForceZero */
3529

36-
/* ROSC random-bit register (RP2350: ROSC base 0x400C8000, RANDOMBIT
37-
* at offset 0x1C). Bit 0 is the raw oscillator-jitter bit. */
38-
#define ROSC_RANDOMBIT (*(volatile uint32_t *)0x400C801CUL)
39-
40-
/* Per debiased bit: how many raw pairs to sample before declaring the
41-
* oscillator stuck. A healthy ROSC produces a differing pair roughly every
42-
* other sample, so this budget is only hit on a hardware fault. */
43-
#define ROSC_VN_MAX_PAIRS 1024U
44-
/* Debiased bytes hashed into each 32-byte conditioned output block. */
45-
#define ROSC_POOL_BYTES 64U
46-
47-
static unsigned int rosc_bit(void)
48-
{
49-
volatile int d;
50-
/* Let the ROSC bit settle so consecutive raw samples decorrelate. */
51-
for (d = 0; d < 16; d++) {
52-
}
53-
return (unsigned int)(ROSC_RANDOMBIT & 1U);
54-
}
55-
56-
/* von Neumann extractor: emit a bit only when a raw pair differs (10 -> 1,
57-
* 01 -> 0). Returns 0 and sets *out_bit on success, -1 if the per-bit pair
58-
* budget is exhausted (oscillator stuck). */
59-
static int rosc_vn_bit(unsigned int *out_bit)
60-
{
61-
unsigned int a, b, tries;
62-
63-
for (tries = 0; tries < ROSC_VN_MAX_PAIRS; tries++) {
64-
a = rosc_bit();
65-
b = rosc_bit();
66-
if (a != b) {
67-
*out_bit = a;
68-
return 0;
69-
}
70-
}
71-
return -1;
72-
}
30+
#include "rp2350_rng.h"
7331

7432
int rp2350_wc_genseed(unsigned char *output, unsigned int sz)
7533
{
76-
wc_Sha256 sha;
77-
unsigned char digest[WC_SHA256_DIGEST_SIZE];
78-
unsigned char pool[ROSC_POOL_BYTES];
79-
unsigned int produced;
80-
unsigned int pi, nbits, bit, take, j;
81-
unsigned char byte;
82-
int ret;
34+
uint32_t block[RP2350_TRNG_EHR_WORDS];
35+
unsigned int produced, take, i;
8336

8437
if (output == NULL) {
8538
return -1;
8639
}
8740

8841
for (produced = 0; produced < sz; ) {
89-
/* Fill the pool with von Neumann-debiased ROSC bits. */
90-
for (pi = 0; pi < ROSC_POOL_BYTES; pi++) {
91-
byte = 0;
92-
for (nbits = 0; nbits < 8U; nbits++) {
93-
if (rosc_vn_bit(&bit) != 0) {
94-
/* Health test failed: fail closed, do not emit weak seed. */
95-
wc_ForceZero(pool, sizeof(pool));
96-
return -1;
97-
}
98-
byte = (unsigned char)((byte << 1) | (bit & 1U));
99-
}
100-
pool[pi] = byte;
101-
}
102-
103-
/* SHA-256 conditioner over the pool (+ running offset so per-block
104-
* hashes never collide even on an identical pool). */
105-
ret = wc_InitSha256(&sha);
106-
if (ret == 0) {
107-
ret = wc_Sha256Update(&sha, pool, (word32)sizeof(pool));
108-
}
109-
if (ret == 0) {
110-
ret = wc_Sha256Update(&sha, (const unsigned char *)&produced,
111-
(word32)sizeof(produced));
112-
}
113-
if (ret == 0) {
114-
ret = wc_Sha256Final(&sha, digest);
115-
}
116-
wc_Sha256Free(&sha);
117-
if (ret != 0) {
118-
wc_ForceZero(pool, sizeof(pool));
119-
wc_ForceZero(digest, sizeof(digest));
42+
if (rp2350_trng_read_block(block) != 0) {
43+
/* Health test failed: fail closed, do not emit a weak seed. */
44+
wc_ForceZero(block, sizeof(block));
12045
return -1;
12146
}
122-
12347
take = sz - produced;
124-
if (take > (unsigned int)sizeof(digest)) {
125-
take = (unsigned int)sizeof(digest);
48+
if (take > (unsigned int)sizeof(block)) {
49+
take = (unsigned int)sizeof(block);
12650
}
127-
for (j = 0; j < take; j++) {
128-
output[produced + j] = digest[j];
51+
for (i = 0; i < take; i++) {
52+
output[produced + i] = ((const unsigned char *)block)[i];
12953
}
13054
produced += take;
13155
}
13256

133-
wc_ForceZero(pool, sizeof(pool));
134-
wc_ForceZero(digest, sizeof(digest));
57+
wc_ForceZero(block, sizeof(block));
13558
return 0;
13659
}

0 commit comments

Comments
 (0)