Fixes from peer review. Thank you Daniele
parent
6a036ffe6e
commit
cab3cb7ff6
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
148
puf/main.c
148
puf/main.c
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
46
puf/stm32.c
46
puf/stm32.c
|
|
@ -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) { }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue