diff --git a/puf/README.md b/puf/README.md index 510e7115..97af4e44 100644 --- a/puf/README.md +++ b/puf/README.md @@ -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). diff --git a/puf/linker.ld b/puf/linker.ld index c335659b..3f2b0f58 100644 --- a/puf/linker.ld +++ b/puf/linker.ld @@ -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") } diff --git a/puf/main.c b/puf/main.c index 2f9b430f..f8105623 100644 --- a/puf/main.c +++ b/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"); diff --git a/puf/startup.c b/puf/startup.c index 27aa0521..f70ce7d0 100644 --- a/puf/startup.c +++ b/puf/startup.c @@ -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 }; diff --git a/puf/stm32.c b/puf/stm32.c index 8ab17b39..0879b281 100644 --- a/puf/stm32.c +++ b/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) { } }