Fixes from peer review. Thank you Daniele

pull/467/merge
David Garske 2026-05-01 15:46:07 -07:00 committed by Daniele Lacamera
parent 6a036ffe6e
commit cab3cb7ff6
5 changed files with 197 additions and 61 deletions

View File

@ -34,13 +34,18 @@ make
### Real Hardware Mode
For actual SRAM PUF on hardware, edit `user_settings.h` and comment out
`WOLFSSL_PUF_TEST`, then build:
Test mode is the default and is selected by the Makefile (not the
header). To build for the real SRAM PUF on hardware, override the
`PUF_TEST` variable:
```bash
make
make PUF_TEST=0
```
This drops the `-DWOLFSSL_PUF_TEST` define and includes `puf_sram_region`
(placed in the `.puf_sram` NOLOAD section) so `wc_PufReadSram()` reads
the real power-on SRAM contents.
### Output
Build output is placed in `./Build/`:
@ -146,5 +151,28 @@ wc_PufZeroize(&ctx);
unencrypted in flash or transmit over the network.
- **SRAM must not be accessed before PUF read** - Any read or write to the
PUF SRAM region before `wc_PufReadSram()` will corrupt the power-on entropy.
- **Production RNG** - Replace the dummy `my_rng_seed_gen()` with your
MCU's hardware RNG (e.g., STM32 RNG peripheral).
- **Production RNG** - This example wires wolfCrypt's RNG through
`CUSTOM_RAND_GENERATE_BLOCK` (in `user_settings.h`) to
`custom_rand_gen_block()` in `stm32.c`, which uses the STM32H5 RNG
peripheral over HSI48. When porting to another MCU, replace the
implementation behind `custom_rand_gen_block()` (or remap
`CUSTOM_RAND_GENERATE_BLOCK` to your platform's hardware RNG hook).
## Reproducing on the m33mu Emulator
The m33mu Cortex-M33 emulator can simulate cold-boot SRAM and seeded
noise so the BCH reconstruction path can be exercised without rebooting
real hardware:
```bash
# Deterministic SRAM, boot 0 - enrolls and reconstructs cleanly
m33mu --puf-seed 0xDEADBEEF --puf-cold-boot 0 Build/puf_example.elf
# Same seed/boot with 2 bit flips per 127-bit codeword - within BCH(t=10)
m33mu --puf-seed 0xDEADBEEF --puf-cold-boot 0 --puf-noise 2 \
Build/puf_example.elf
```
Identity must match between the enrollment and reconstruction prints as
long as noise stays within the BCH correction budget (10 flips per
127-bit codeword; safe margin 2-4).

View File

@ -125,7 +125,11 @@ SECTIONS
} > RAM
/* PUF SRAM section: NOLOAD prevents startup code from zeroing.
* The raw power-on state of these bytes is the PUF source. */
* The raw power-on state of these bytes is the PUF source.
* Required size is driven by WC_PUF_RAW_BYTES in
* wolfssl/wolfcrypt/puf.h (currently 256 bytes). The 4 KB
* reservation in MEMORY{} above is generous headroom; the ASSERT
* below catches future overflow if WC_PUF_RAW_BYTES grows. */
.puf_sram (NOLOAD) :
{
. = ALIGN(4);
@ -134,4 +138,7 @@ SECTIONS
KEEP(*(.puf_sram.*))
_puf_sram_end = .;
} > PUF_RAM
ASSERT(SIZEOF(.puf_sram) <= LENGTH(PUF_RAM),
"Error: .puf_sram exceeds reserved PUF_RAM region size")
}

View File

@ -219,49 +219,115 @@ int main(void)
/* ================================================================== */
/* REAL HARDWARE: Read actual SRAM PUF */
/* ================================================================== */
printf("Mode: HARDWARE (real SRAM PUF)\n\n");
{
uint8_t identity2[WC_PUF_ID_SZ];
uint8_t key2[WC_PUF_KEY_SZ];
/* Step 2: Read raw SRAM (must be done before any other SRAM access) */
ret = wc_PufReadSram(&ctx, (const uint8_t*)puf_sram_region,
sizeof(puf_sram_region));
if (ret != 0) {
printf("ERROR: wc_PufReadSram failed: %d\n", ret);
goto cleanup;
printf("Mode: HARDWARE (real SRAM PUF)\n\n");
/* ---- Phase 1: Enrollment ---- */
/* Read raw SRAM (must happen before any other access to the
* .puf_sram region - the power-on entropy is consumed once). */
ret = wc_PufReadSram(&ctx, (const uint8_t*)puf_sram_region,
sizeof(puf_sram_region));
if (ret != 0) {
printf("ERROR: wc_PufReadSram failed: %d\n", ret);
goto cleanup;
}
printf("SRAM read complete (%d bytes).\n",
(int)sizeof(puf_sram_region));
ret = wc_PufEnroll(&ctx);
if (ret != 0) {
printf("ERROR: wc_PufEnroll failed: %d\n", ret);
goto cleanup;
}
printf("Enrollment complete.\n");
/* Save helper data. In production this is written to flash/NVM
* for use across reboots; here it stays in RAM so the same run
* can also exercise the reconstruction path below. */
memcpy(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
print_hex("Helper data (store to NVM)", helperData,
WC_PUF_HELPER_BYTES);
ret = wc_PufGetIdentity(&ctx, identity, sizeof(identity));
if (ret != 0) {
printf("ERROR: wc_PufGetIdentity failed: %d\n", ret);
goto cleanup;
}
print_hex("Identity (enrollment)", identity, WC_PUF_ID_SZ);
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
if (ret != 0) {
printf("ERROR: wc_PufDeriveKey failed: %d\n", ret);
goto cleanup;
}
print_hex("Derived key (enrollment)", key, WC_PUF_KEY_SZ);
/* ---- Phase 2: Reconstruction ---- */
/* On real hardware the same .puf_sram contents are still in
* RAM, so re-reading them yields the same bytes and BCH runs
* with zero errors. Under m33mu, run the example a second time
* with a different --puf-cold-boot or with --puf-noise to feed
* a noisy SRAM image through wc_PufReconstruct using the
* helper data captured above. */
printf("\n--- Reconstructing from saved helper data ---\n\n");
ret = wc_PufInit(&ctx);
if (ret != 0) {
printf("ERROR: wc_PufInit (reconstruct) failed: %d\n", ret);
goto cleanup;
}
ret = wc_PufReadSram(&ctx, (const uint8_t*)puf_sram_region,
sizeof(puf_sram_region));
if (ret != 0) {
printf("ERROR: wc_PufReadSram (reconstruct) failed: %d\n", ret);
goto cleanup;
}
ret = wc_PufReconstruct(&ctx, helperData, WC_PUF_HELPER_BYTES);
if (ret != 0) {
printf("ERROR: wc_PufReconstruct failed: %d\n", ret);
goto cleanup;
}
printf("Reconstruction complete (BCH error correction ran).\n");
ret = wc_PufGetIdentity(&ctx, identity2, sizeof(identity2));
if (ret != 0) {
printf("ERROR: wc_PufGetIdentity (reconstruct) failed: %d\n", ret);
goto cleanup;
}
print_hex("Identity (reconstructed)", identity2, WC_PUF_ID_SZ);
if (memcmp(identity, identity2, WC_PUF_ID_SZ) == 0) {
printf("PASS: Identity matches after reconstruction.\n");
}
else {
printf("FAIL: Identity mismatch after reconstruction!\n");
ret = -1;
goto cleanup;
}
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2));
if (ret != 0) {
printf("ERROR: wc_PufDeriveKey (reconstruct) failed: %d\n", ret);
goto cleanup;
}
print_hex("Derived key (reconstructed)", key2, WC_PUF_KEY_SZ);
if (memcmp(key, key2, WC_PUF_KEY_SZ) == 0) {
printf("PASS: Derived key matches after reconstruction.\n");
}
else {
printf("FAIL: Derived key mismatch after reconstruction!\n");
ret = -1;
goto cleanup;
}
}
printf("SRAM read complete (%d bytes).\n", (int)sizeof(puf_sram_region));
/* Step 3: Enroll (first boot only - save helper data to flash/NVM) */
ret = wc_PufEnroll(&ctx);
if (ret != 0) {
printf("ERROR: wc_PufEnroll failed: %d\n", ret);
goto cleanup;
}
printf("Enrollment complete.\n");
/* Save helper data for future reconstructions */
memcpy(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
print_hex("Helper data (store to NVM)", helperData, WC_PUF_HELPER_BYTES);
/* Get device identity */
ret = wc_PufGetIdentity(&ctx, identity, sizeof(identity));
if (ret != 0) {
printf("ERROR: wc_PufGetIdentity failed: %d\n", ret);
goto cleanup;
}
print_hex("Device identity", identity, WC_PUF_ID_SZ);
/* Derive a key */
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
if (ret != 0) {
printf("ERROR: wc_PufDeriveKey failed: %d\n", ret);
goto cleanup;
}
print_hex("Derived key", key, WC_PUF_KEY_SZ);
/* Note: On subsequent boots, use wc_PufReconstruct() with the stored
* helper data instead of wc_PufEnroll(). The BCH error correction
* (t=10, corrects up to 10 bit flips per 127-bit codeword) will
* recover the same stable bits even with noisy SRAM. */
#endif /* WOLFSSL_PUF_TEST */
printf("\n--- PUF example complete ---\n");

View File

@ -45,7 +45,10 @@ void SystemInit(void)
{
/* Set VTOR to flash base */
*(volatile uint32_t *)0xE000ED08 = 0x08000000;
/* Default HSI clock (64 MHz) is sufficient for this example */
/* No clock-tree programming. After reset on STM32H563, HSI runs at
* 64 MHz with HSIDIV = /2, so SYSCLK = HCLK = PCLK1 = 32 MHz, which
* is sufficient for the UART/RNG used in this example. The USART3
* baud-rate divisor in stm32.c assumes this 32 MHz PCLK1. */
}
/* -------------------------------------------------------------------------- */
@ -117,7 +120,13 @@ void __attribute__((weak, alias("Default_Handler"))) SysTick_Handler(void);
typedef void (*vector_fn)(void);
const vector_fn __isr_vector[] __attribute__((section(".isr_vector"), used)) = {
/* STM32H563 has 131 external IRQs (0..130, LPTIM6_IRQn).
* Plus 16 entries for the Cortex-M core (SP + 15 system handlers). */
#define STM32H563_EXT_IRQ_COUNT 131u
#define VECTOR_TABLE_ENTRIES (16u + STM32H563_EXT_IRQ_COUNT)
const vector_fn __isr_vector[VECTOR_TABLE_ENTRIES]
__attribute__((section(".isr_vector"), used)) = {
(vector_fn)(uintptr_t)&_estack, /* Initial SP */
Reset_Handler, /* Reset */
NMI_Handler, /* NMI */
@ -132,5 +141,7 @@ const vector_fn __isr_vector[] __attribute__((section(".isr_vector"), used)) = {
0, /* Reserved */
PendSV_Handler, /* PendSV */
SysTick_Handler, /* SysTick */
/* Peripheral IRQs default to Default_Handler via unused slots */
/* All STM32H563 peripheral IRQs default to Default_Handler. The
* GCC range designator below fills entries 16..(16+131-1). */
[16 ... (VECTOR_TABLE_ENTRIES - 1)] = Default_Handler
};

View File

@ -4,9 +4,11 @@
* Provides USART3 init/output, printf retarget, RNG stub, and time stub.
*
* To port to a different MCU, replace this file with your platform's
* UART and RNG implementation. The interface is:
* UART and RNG implementation. The integration points are:
* void hal_init(void) - called once at startup before printf
* int my_rng_seed_gen(uint8_t* output, uint32_t sz) - RNG seed
* int custom_rand_gen_block(unsigned char* output, unsigned int sz) -
* wolfCrypt RNG callback wired via CUSTOM_RAND_GENERATE_BLOCK in
* user_settings.h. Implement using your MCU's hardware TRNG.
* unsigned long my_time(unsigned long* timer) - monotonic time
*
* Copyright (C) 2006-2026 wolfSSL Inc.
@ -64,6 +66,17 @@
#define USART3_TDR (*(volatile uint32_t *)(USART3_BASE + 0x28u))
#define USART3_PRESC (*(volatile uint32_t *)(USART3_BASE + 0x2Cu))
/* After reset on STM32H563:
* HSI = 64 MHz, HSIDIV = /2 (reset value) -> SYSCLK = 32 MHz
* HPRE = /1 -> HCLK = 32 MHz
* PPRE1 = /1 -> PCLK1 = 32 MHz
* The example never reprograms RCC, so PCLK1 stays at 32 MHz and the
* USART3 BRR below is correct. If this code is ported into a project
* that brings up the PLL, recompute UART_PCLK_HZ from the actual
* RCC settings. */
#define UART_PCLK_HZ 32000000u
#define UART_BAUD_HZ 115200u
static void delay(volatile uint32_t n)
{
while (n--) { }
@ -90,12 +103,13 @@ static void uart_init(void)
afr |= (7u << 0); /* AF7 = USART3 */
GPIO_AFRH(GPIOD_BASE) = afr;
/* Configure USART3: 115200 baud at default 32 MHz PCLK1 */
/* Configure USART3 for UART_BAUD_HZ at the post-reset PCLK1 (see
* UART_PCLK_HZ comment above). 32 MHz / 115200 ~= 278. */
USART3_CR1 = 0;
USART3_CR2 = 0;
USART3_CR3 = 0;
USART3_PRESC = 0;
USART3_BRR = 32000000u / 115200u; /* ~278 */
USART3_BRR = UART_PCLK_HZ / UART_BAUD_HZ;
USART3_CR1 = (1u << 3); /* TE */
delay(10);
USART3_CR1 |= (1u << 0); /* UE */
@ -167,17 +181,27 @@ static void rng_init(void)
RCC_AHB2ENR |= (1u << 18); /* RNG clock enable */
delay(100);
/* Configure and enable RNG with conditioning reset */
/* Build the desired CR value (config bits, RNGEN cleared). The
* NIST-SP800-90B compliant config recommended by ST RM0481 for
* HSI48 is CONFIG1=0x0F, CONFIG3=0x0D, CLKDIV/CONFIG2 = 0. */
rng_cr = RNG_CR;
rng_cr &= ~(0x1Fu << RNG_CR_CONFIG1_SHIFT);
rng_cr &= ~(0x7u << RNG_CR_CLKDIV_SHIFT);
rng_cr &= ~(0x3u << RNG_CR_CONFIG2_SHIFT);
rng_cr &= ~(0x7u << RNG_CR_CONFIG3_SHIFT);
rng_cr |= 0x0Fu << RNG_CR_CONFIG1_SHIFT;
rng_cr |= 0x0Du << RNG_CR_CONFIG3_SHIFT;
rng_cr &= ~(0x7u << RNG_CR_CLKDIV_SHIFT);
rng_cr &= ~(0x3u << RNG_CR_CONFIG2_SHIFT);
rng_cr &= ~(0x7u << RNG_CR_CONFIG3_SHIFT);
rng_cr &= ~RNG_CR_RNGEN;
rng_cr |= (0x0Fu << RNG_CR_CONFIG1_SHIFT);
rng_cr |= (0x0Du << RNG_CR_CONFIG3_SHIFT);
/* STM32H5 RNG init sequence (RM0481 28.6.2):
* 1. Write CR with CONDRST=1 and the new config bits in the same
* access. CONDRST holds the conditioning logic in reset and
* latches the config.
* 2. Write CR again with CONDRST=0 and RNGEN=1 to release the
* reset and start generation. The bit does not auto-clear -
* software must drive it back to 0.
* 3. Wait for the first random word: SR.DRDY=1. */
RNG_CR = RNG_CR_CONDRST | rng_cr;
while ((RNG_CR & RNG_CR_CONDRST) == 0u) { }
RNG_CR = rng_cr | RNG_CR_RNGEN;
while ((RNG_SR & RNG_SR_DRDY) == 0u) { }
}