diff --git a/.github/workflows/puf.yml b/.github/workflows/puf.yml index 37b4d0b8d..01493cef2 100644 --- a/.github/workflows/puf.yml +++ b/.github/workflows/puf.yml @@ -62,6 +62,18 @@ jobs: bash "$GITHUB_WORKSPACE/.github/scripts/git-clone-retry.sh" -q --depth 1 --branch '${{ matrix.wolfssl_ref }}' https://github.com/wolfSSL/wolfssl /tmp/wolfssl cd puf make WOLFSSL_ROOT=/tmp/wolfssl + make WOLFSSL_ROOT=/tmp/wolfssl PUF_TEST=0 + + # The interactive demo uses PUF APIs added after v5.9.2, so it only + # builds against master (older trees stop at a #error in the source). + # PUF_TEST=0 on the same line proves the override forces test mode on. + - name: Build puf interactive demo (wolfSSL master only) + if: matrix.wolfssl_ref == 'master' + run: | + set -euo pipefail + cd puf + make WOLFSSL_ROOT=/tmp/wolfssl INTERACTIVE=1 + make WOLFSSL_ROOT=/tmp/wolfssl INTERACTIVE=1 PUF_TEST=0 - name: Assert it really cross-compiled run: | diff --git a/puf/Makefile b/puf/Makefile index 1c4a0d42d..ed87cdbdb 100644 --- a/puf/Makefile +++ b/puf/Makefile @@ -20,14 +20,27 @@ NM = $(TOOLCHAIN)nm # wolfSSL root (relative to this directory) WOLFSSL_ROOT ?= ../../wolfssl -# Build output -BUILD_DIR = ./Build +# Build output. Each configuration gets its own directory so switching +# INTERACTIVE / PUF_TEST between invocations can never relink stale objects +# from the previous configuration (the default build stays in ./Build). BIN = puf_example # PUF test mode (default on): synthetic SRAM data for testing without hardware. # Set PUF_TEST=0 to build for real hardware SRAM. PUF_TEST ?= 1 +# Set INTERACTIVE=1 to build the UART menu demo (main_interactive.c) instead of +# the one-shot example. It captures the real power-on SRAM and then replays it +# through wc_PufSetTestData so a known number of bit flips can be injected, so +# it needs the test hooks compiled in: PUF_TEST is forced on (override beats a +# contradictory PUF_TEST=0 on the command line). Requires wolfSSL master (the +# demo uses PUF APIs added after v5.9.2; the build stops with a clear #error +# on older trees). +INTERACTIVE ?= 0 +ifeq ($(INTERACTIVE),1) +override PUF_TEST := 1 +endif + # Architecture ARCHFLAGS = -mcpu=cortex-m33 -mthumb -mabi=aapcs @@ -41,6 +54,15 @@ ifeq ($(PUF_TEST),1) CFLAGS += -DWOLFSSL_PUF_TEST endif +BUILD_SUFFIX = +ifeq ($(INTERACTIVE),1) +BUILD_SUFFIX := $(BUILD_SUFFIX)-interactive +endif +ifeq ($(PUF_TEST),0) +BUILD_SUFFIX := $(BUILD_SUFFIX)-hw +endif +BUILD_DIR ?= ./Build$(BUILD_SUFFIX) + # Linker flags LDFLAGS = $(ARCHFLAGS) LDFLAGS += --specs=nosys.specs --specs=nano.specs @@ -52,7 +74,11 @@ LDFLAGS += -T./linker.ld LIBS = -lm # Source files +ifeq ($(INTERACTIVE),1) +SRC_C = main_interactive.c +else SRC_C = main.c +endif SRC_C += startup.c SRC_C += stm32.c @@ -102,5 +128,4 @@ $(BUILD_DIR)/$(BIN).hex: $(BUILD_DIR)/$(BIN).elf $(OBJCOPY) -O ihex $< $@ clean: - rm -f $(BUILD_DIR)/*.elf $(BUILD_DIR)/*.hex $(BUILD_DIR)/*.map - rm -f $(BUILD_DIR)/*.o $(BUILD_DIR)/*.sym $(BUILD_DIR)/*.disasm + rm -rf ./Build ./Build-interactive ./Build-hw diff --git a/puf/README.md b/puf/README.md index 97af4e44f..11b20317b 100644 --- a/puf/README.md +++ b/puf/README.md @@ -46,6 +46,82 @@ 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. +**Only a real power cycle gives a real readout.** A warm reset - the reset +button, a debugger reset, or `-rst` after flashing - leaves SRAM holding +whatever the previous image left there. That stale content can still pass the +Hamming-weight health band, so the example will happily enroll from it and +report a plausible-looking identity that has nothing to do with the silicon. +Pull power (or unplug USB) between enrollment and reconstruction when you want +to exercise the PUF itself. + +Measured on a NUCLEO-H563ZI: a cold-boot readout is about 51-52% ones, well +inside the default 35-65% band, and reconstruction recovers the enrolled +identity unchanged across a physical power cycle - so this part's SRAM noise +stays within the BCH t=10 correction budget. Immediately after a warm reset the +same board reported 20% ones and was correctly rejected with `PUF_READ_E`. + +### Interactive Mode + +```bash +make INTERACTIVE=1 +``` + +Requires wolfSSL master (the demo uses PUF APIs added after v5.9.2; the build +stops with a clear `#error` on older trees). Output goes to +`Build-interactive/` so switching modes never reuses stale objects. + +Builds `main_interactive.c` instead of the one-shot example: a UART menu that +captures the real power-on SRAM at reset, reports whether it passed the readout +health band, and then lets you drive the extractor a step at a time. + +``` +=== wolfCrypt PUF - interactive demo === + profile : BCH(127,64,t=10) over GF(2^7), 16 codewords, id 0x38500010 + power-on SRAM readout: 256 bytes, 44% ones -> inside the health band + + [1] enroll and show identity / key / helper + [2] noise sweep - the correction cliff + [3] two keys from one PUF + [4] dump the public recovery blob (identity + helper) + [5] paste the blob back after a power cycle, and verify + [r] reboot (soft reset - SRAM is NOT re-randomised) +``` + +Option 2 is the interesting one: it injects a known number of bit flips per +codeword and shows exactly where BCH stops correcting. + +``` + flips/codeword result + 9 identity matches + 10 identity matches <= t, the limit + 11 rejected (-1012) - fails closed +``` + +Controlled error counts are not something real SRAM can provide, so the captured +power-on pattern is replayed through `wc_PufSetTestData()` with the flips +applied - the bits are real silicon, only the extra noise is synthetic. That is +why `INTERACTIVE=1` implies `PUF_TEST=1`. If the power-on readout is rejected by +the health band (which is what a warm reset looks like, since SRAM keeps the +previous image's data), the demo falls back to a synthetic pattern and says so, +so the extractor demos still run. + +Options 4 and 5 show what helper data is for, across a real power cycle and with +no non-volatile storage involved. `4` prints one line holding the device +identity, the helper data, and a trailing checksum over both. Copy it, power-cycle the +board, then paste it back with `5`: it verifies the checksum (a mangled +paste is reported as such and changes nothing), reconstructs from freshly +re-read silicon, and compares against the identity carried in the blob, so the +board reports the result itself rather than leaving you to compare hex by eye. Nothing secret +leaves the part - the helper is public, which is why it can travel out over the +wire and back in again. + +The reader ignores whitespace, needs no trailing newline, and discards its +accumulation if it sees any non-hex text, so a selection that catches the +surrounding prose still loads correctly. `q` aborts. + +Note that a soft reset does **not** re-randomise SRAM. Only a real power cycle +produces a fresh power-on readout. + ### Output Build output is placed in `./Build/`: diff --git a/puf/main_interactive.c b/puf/main_interactive.c new file mode 100644 index 000000000..acba34f84 --- /dev/null +++ b/puf/main_interactive.c @@ -0,0 +1,479 @@ +/* main.c - interactive wolfCrypt PUF demo over UART + * + * Captures the real power-on SRAM at reset, reports whether it is a usable + * PUF source, and then offers an interactive menu over the UART: + * + * 1 enroll and show identity / derived key / helper size + * 2 noise sweep - inject a known number of bit flips per codeword and + * show where BCH stops correcting (the "correction cliff") + * 3 derive two unrelated keys from the same silicon (HKDF context) + * 4 dump the helper data, which is public + * 5 reconstruct from the stored helper and compare to enrollment + * r soft reboot + * + * The noise sweep needs controllable error counts, which real SRAM cannot + * provide, so the captured power-on pattern is replayed through + * wc_PufSetTestData with a known number of flips applied. The bits are real + * silicon; only the extra noise is synthetic. + */ + +#include +#include +#include + +/* This demo drives PUF APIs added after the v5.9.2 stable release + * (WC_PUF_RAW_STRIDE_BITS, wc_PufCheckSram, wc_PufGetParams, + * wc_PufGetProfileId, wc_PufGetHelperData), so INTERACTIVE=1 needs wolfSSL + * master. The one-shot example still builds against the stable release. */ +#ifndef WC_PUF_RAW_STRIDE_BITS +#error "INTERACTIVE=1 requires wolfSSL master (post-v5.9.2 PUF API)" +#endif +#include +#include +#include + +extern void hal_init(void); +extern int uart_getc(void); +extern void uart_drain(void); + +static unsigned int helper_sum(const uint8_t* d, uint32_t len); +static unsigned int helper_sum_cont(unsigned int sum, const uint8_t* d, + uint32_t len); + +/* Raw power-on SRAM. NOLOAD section: startup must not zero it. */ +__attribute__((section(".puf_sram"))) +static volatile uint8_t puf_sram_region[WC_PUF_RAW_BYTES]; + +/* Snapshot taken before anything else can disturb the region. */ +static uint8_t g_raw[WC_PUF_RAW_BYTES]; +static uint8_t g_work[WC_PUF_RAW_BYTES]; +static uint8_t g_helper[WC_PUF_HELPER_BYTES]; +static uint8_t g_id[WC_PUF_ID_SZ]; +static int g_enrolled = 0; +/* The noise sweep injects exact flip counts against the readout the helper + * was enrolled from, so it needs an enrollment taken from THIS boot's g_raw - + * a blob loaded from a previous boot has an unknown natural flip baseline. */ +static int g_freshEnroll = 0; +static int g_rawHealthy = 0; +static int g_onesPct = 0; +static int g_synthetic = 0; + +/* Deterministic stand-in used only when the real readout is unusable, so the + * extractor demos still run. xorshift32 gives a well-balanced pattern. */ +static void synth_pattern(uint8_t* d, uint32_t len, uint32_t seed) +{ + uint32_t i; + for (i = 0; i < len; i++) { + seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5; + d[i] = (uint8_t)(seed & 0xFFu); + } +} + +static void print_hex(const char* label, const uint8_t* d, uint32_t len) +{ + uint32_t i; + printf("%s", label); + for (i = 0; i < len; i++) + printf("%02x", d[i]); + printf("\r\n"); +} + +static int ones_percent(const uint8_t* d, uint32_t len) +{ + uint32_t i; + int b, ones = 0; + for (i = 0; i < len; i++) { + for (b = 0; b < 8; b++) { + if (d[i] & (1u << b)) + ones++; + } + } + return (int)((ones * 100u) / (len * 8u)); +} + +/* Flip 'flips' bits inside each codeword-sized stride of the pattern. */ +static void add_noise(uint8_t* d, int flips) +{ + int cw, f, bit; + int stride = WC_PUF_RAW_STRIDE_BITS; + for (cw = 0; cw < WC_PUF_NUM_CODEWORDS; cw++) { + for (f = 0; f < flips; f++) { + bit = cw * stride + (f * 7) + 3; + if ((bit / 8) < (int)WC_PUF_RAW_BYTES) + d[bit / 8] ^= (uint8_t)(1u << (bit % 8)); + } + } +} + +/* Load a pattern into a fresh context and read it in. */ +static int load_ctx(wc_PufCtx* ctx, const uint8_t* pattern) +{ + int ret = wc_PufInit(ctx); + if (ret != 0) + return ret; + ret = wc_PufSetTestData(ctx, pattern, WC_PUF_RAW_BYTES); + if (ret != 0) + return ret; + return wc_PufReadSram(ctx, pattern, WC_PUF_RAW_BYTES); +} + +static void do_enroll(void) +{ + wc_PufCtx ctx; + uint8_t key[WC_PUF_KEY_SZ]; + int ret; + + ret = load_ctx(&ctx, g_raw); + if (ret != 0) { + printf(" readout rejected: %d\r\n", ret); + wc_PufZeroize(&ctx); + return; + } + ret = wc_PufEnroll(&ctx); + if (ret != 0) { + printf(" enroll failed: %d\r\n", ret); + wc_PufZeroize(&ctx); + return; + } + ret = wc_PufGetHelperData(&ctx, g_helper, sizeof(g_helper)); + if (ret == 0) + ret = wc_PufGetIdentity(&ctx, g_id, sizeof(g_id)); + if (ret == 0) + ret = wc_PufDeriveKey(&ctx, (const byte*)"nv-integrity", 12, + key, sizeof(key)); + if (ret != 0) { + printf(" enroll failed: %d\r\n", ret); + wc_ForceZero(key, sizeof(key)); + wc_PufZeroize(&ctx); + return; + } + + printf(" enrolled from %s\r\n", g_synthetic ? + "a synthetic pattern (real readout was rejected this boot)" : + "this boot's real power-on SRAM"); + print_hex(" identity : ", g_id, 16); + print_hex(" derived key : ", key, 16); + printf(" helper data : %d bytes, stored in the clear\r\n", + (int)sizeof(g_helper)); + g_enrolled = 1; + g_freshEnroll = 1; + + wc_ForceZero(key, sizeof(key)); + wc_PufZeroize(&ctx); +} + +static void do_sweep(void) +{ + wc_PufCtx ctx; + uint8_t id[WC_PUF_ID_SZ]; + int flips, ret, m, n, k, t, cw; + + if (!g_freshEnroll) { + printf(" run [1] enroll first - the sweep needs a helper enrolled\r\n" + " from this boot's readout so the injected flip counts are\r\n" + " exact (a loaded blob has an unknown natural flip baseline)\r\n"); + return; + } + wc_PufGetParams(&m, &n, &k, &t, &cw); + printf(" BCH(%d,%d,t=%d), %d codewords - correcting up to %d flips per " + "%d-bit codeword\r\n", n, k, t, cw, t, n); + printf(" flips/codeword result\r\n"); + + for (flips = 0; flips <= t + 3; flips++) { + XMEMCPY(g_work, g_raw, sizeof(g_work)); + add_noise(g_work, flips); + ret = load_ctx(&ctx, g_work); + if (ret == 0) + ret = wc_PufReconstruct(&ctx, g_helper, sizeof(g_helper)); + if (ret == 0) + ret = wc_PufGetIdentity(&ctx, id, sizeof(id)); + + printf(" %2d ", flips); + if (ret != 0) { + printf("rejected (%d) - fails closed", ret); + } + else if (XMEMCMP(id, g_id, sizeof(id)) == 0) { + printf("identity matches"); + } + else { + printf("WRONG KEY - would be a bug"); + } + if (flips == t) + printf(" <= t, the limit"); + printf("\r\n"); + wc_PufZeroize(&ctx); + } +} + +static void do_two_keys(void) +{ + wc_PufCtx ctx; + uint8_t k1[WC_PUF_KEY_SZ], k2[WC_PUF_KEY_SZ]; + int ret; + + ret = load_ctx(&ctx, g_raw); + if (ret == 0) + ret = wc_PufReconstruct(&ctx, g_helper, sizeof(g_helper)); + if (ret != 0) { + printf(" need an enrollment first ([1]), rc=%d\r\n", ret); + wc_PufZeroize(&ctx); + return; + } + ret = wc_PufDeriveKey(&ctx, (const byte*)"nv-integrity", 12, + k1, sizeof(k1)); + if (ret == 0) + ret = wc_PufDeriveKey(&ctx, (const byte*)"device-identity", 15, + k2, sizeof(k2)); + if (ret != 0) { + printf(" key derivation failed: %d\r\n", ret); + wc_ForceZero(k1, sizeof(k1)); + wc_ForceZero(k2, sizeof(k2)); + wc_PufZeroize(&ctx); + return; + } + printf(" same silicon, same helper data, two HKDF contexts:\r\n"); + print_hex(" \"nv-integrity\" : ", k1, 16); + print_hex(" \"device-identity\" : ", k2, 16); + printf(" unrelated keys - one PUF backs as many as you need\r\n"); + wc_ForceZero(k1, sizeof(k1)); + wc_ForceZero(k2, sizeof(k2)); + wc_PufZeroize(&ctx); +} + +static void do_dump_helper(void) +{ + uint32_t i; + unsigned int sum; + + if (!g_enrolled) { + printf(" run [1] enroll first\r\n"); + return; + } + printf(" Public recovery blob: device identity, %d bytes of helper\r\n" + " data, then a 2-byte checksum over both. Triple-click the\r\n" + " single line below and copy it. After a power cycle, [5]\r\n" + " pastes it back and checks itself, so there is nothing to\r\n" + " write down.\r\n\r\n", + (int)WC_PUF_HELPER_BYTES); + for (i = 0; i < (uint32_t)WC_PUF_ID_SZ; i++) { + printf("%02x", g_id[i]); + } + for (i = 0; i < (uint32_t)WC_PUF_HELPER_BYTES; i++) { + printf("%02x", g_helper[i]); + } + sum = helper_sum(g_id, (uint32_t)WC_PUF_ID_SZ); + sum = helper_sum_cont(sum, g_helper, (uint32_t)WC_PUF_HELPER_BYTES); + printf("%04x\r\n\r\n", sum); + printf(" none of this is secret - it reveals nothing about the key, and\r\n" + " on another die it reconstructs nothing\r\n"); +} + +/* Small checksum so a mangled paste is reported as such rather than surfacing + * as a confusing reconstruct failure. The blob checksum covers the identity + * and the helper data together. */ +static unsigned int helper_sum_cont(unsigned int sum, const uint8_t* d, + uint32_t len) +{ + uint32_t i; + for (i = 0; i < len; i++) { + sum = ((sum << 5) ^ (sum >> 11) ^ d[i]) & 0xFFFFu; + } + return sum; +} + +static unsigned int helper_sum(const uint8_t* d, uint32_t len) +{ + return helper_sum_cont(0xFFFFu, d, len); +} + +static int hexval(int c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +/* Read helper data back in as pasted hex and reconstruct from it. The helper + * is public, so it can be carried out of the device and back in over the wire. + * Pasting it after a power cycle shows the key rebuilt from silicon that has + * just been re-read, with nothing secret ever leaving the part. */ +static uint8_t g_blob[WC_PUF_ID_SZ + WC_PUF_HELPER_BYTES + 2]; + +static void do_load_helper(void) +{ + wc_PufCtx ctx; + uint8_t id[WC_PUF_ID_SZ]; + uint8_t k1[WC_PUF_KEY_SZ], k2[WC_PUF_KEY_SZ]; + unsigned int sum, expect; + int c, v, hi = -1, ret, match; + uint32_t n = 0; + + printf(" paste the recovery blob from [4]; q aborts.\r\n"); + printf(" nothing is echoed while pasting.\r\n"); + + /* Terminator-free: a triple-click selection carries no trailing newline, + * so finish as soon as the blob is complete. Whitespace is ignored; + * anything else non-hex means the selection caught prose, so discard and + * resynchronise rather than shifting the stream by a nibble. + * + * g_blob is only a staging buffer: nothing is committed to the enrolled + * state (g_id / g_helper / g_enrolled) until the checksum verifies, the + * reconstruct succeeds, AND the identity matches. Every failure path + * leaves any previous enrollment untouched. */ + while (n < (uint32_t)sizeof(g_blob)) { + c = uart_getc(); + if (c == 'q' || c == 'Q' || c == 27) { + printf(" aborted\r\n"); + return; + } + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + continue; + v = hexval(c); + if (v < 0) { + n = 0; + hi = -1; + continue; + } + if (hi < 0) { + hi = v; + } + else { + g_blob[n++] = (uint8_t)((hi << 4) | v); + hi = -1; + } + } + + /* Verify the trailing checksum (over identity + helper) before anything + * else, so a mangled paste is reported as exactly that. */ + sum = helper_sum(g_blob, (uint32_t)WC_PUF_ID_SZ); + sum = helper_sum_cont(sum, g_blob + WC_PUF_ID_SZ, + (uint32_t)WC_PUF_HELPER_BYTES); + expect = ((unsigned int)g_blob[WC_PUF_ID_SZ + WC_PUF_HELPER_BYTES] << 8) | + (unsigned int)g_blob[WC_PUF_ID_SZ + WC_PUF_HELPER_BYTES + 1]; + if (sum != expect) { + printf(" checksum mismatch (got %04x, blob says %04x) - the paste\r\n" + " was mangled; nothing was changed, copy the line again\r\n", + sum, expect); + return; + } + printf(" loaded identity + %d bytes of helper data, checksum %04x OK\r\n", + (int)WC_PUF_HELPER_BYTES, sum); + + ret = load_ctx(&ctx, g_raw); + if (ret == 0) + ret = wc_PufReconstruct(&ctx, g_blob + WC_PUF_ID_SZ, + WC_PUF_HELPER_BYTES); + if (ret == 0) + ret = wc_PufGetIdentity(&ctx, id, sizeof(id)); + if (ret == 0) + ret = wc_PufDeriveKey(&ctx, (const byte*)"nv-integrity", 12, + k1, sizeof(k1)); + if (ret == 0) + ret = wc_PufDeriveKey(&ctx, (const byte*)"device-identity", 15, + k2, sizeof(k2)); + if (ret != 0) { + printf(" reconstruct failed: %d\r\n", ret); + printf(" either the blob is from a different part, or the readout\r\n" + " drifted past the correction budget; nothing was changed\r\n"); + } + else { + match = (XMEMCMP(id, g_blob, WC_PUF_ID_SZ) == 0); + print_hex(" identity now : ", id, 16); + print_hex(" identity enrolled : ", g_blob, 16); + printf("\r\n >>> %s <<<\r\n\r\n", match ? + "SAME KEY, REBUILT FROM SILICON AFTER POWER LOSS" : + "MISMATCH - this blob does not belong to this part"); + if (match) { + print_hex(" \"nv-integrity\" : ", k1, 16); + print_hex(" \"device-identity\" : ", k2, 16); + /* Commit only now: verified, reconstructed, and matching. */ + XMEMCPY(g_id, g_blob, WC_PUF_ID_SZ); + XMEMCPY(g_helper, g_blob + WC_PUF_ID_SZ, WC_PUF_HELPER_BYTES); + g_enrolled = 1; + /* Not enrolled from this boot's readout - the sweep stays off. */ + g_freshEnroll = 0; + } + else { + printf(" nothing was changed\r\n"); + } + } + wc_ForceZero(k1, sizeof(k1)); + wc_ForceZero(k2, sizeof(k2)); + wc_PufZeroize(&ctx); +} + +static void menu(void) +{ + printf("\r\n [1] enroll and show identity / key / helper\r\n"); + printf(" [2] noise sweep - the correction cliff\r\n"); + printf(" [3] two keys from one PUF\r\n"); + printf(" [4] dump the public recovery blob (identity + helper)\r\n"); + printf(" [5] paste the blob back after a power cycle, and verify\r\n"); + printf(" [r] reboot (soft reset - SRAM is NOT re-randomised)\r\n"); + printf(" [?] this menu\r\n"); +} + +int main(void) +{ + int m, n, k, t, cw, c; + + /* Snapshot the power-on SRAM before anything else can touch it. */ + XMEMCPY(g_raw, (const void*)puf_sram_region, sizeof(g_raw)); + + hal_init(); + wolfCrypt_Init(); + + g_onesPct = ones_percent(g_raw, sizeof(g_raw)); + g_rawHealthy = (wc_PufCheckSram(g_raw, sizeof(g_raw), NULL) == 0); + + wc_PufGetParams(&m, &n, &k, &t, &cw); + printf("\r\n=== wolfCrypt PUF - interactive demo ===\r\n"); + printf(" profile : BCH(%d,%d,t=%d) over GF(2^%d), %d codewords, " + "id 0x%08lX\r\n", n, k, t, m, cw, + (unsigned long)wc_PufGetProfileId()); + printf(" power-on SRAM readout: %d bytes, %d%% ones -> %s\r\n", + (int)sizeof(g_raw), g_onesPct, + g_rawHealthy ? "inside the health band" : + "REJECTED by the health band"); + if (!g_rawHealthy) { + printf(" this region has no usable power-on entropy on this boot.\r\n"); + printf(" falling back to a synthetic pattern so the extractor demos " + "still run\r\n"); + printf(" (note: a warm reset leaves old data in SRAM - only a real " + "power cycle shows the true readout)\r\n"); + synth_pattern(g_raw, sizeof(g_raw), 0xC0FFEEu); + g_synthetic = 1; + g_onesPct = ones_percent(g_raw, sizeof(g_raw)); + printf(" synthetic readout: %d%% ones\r\n", g_onesPct); + } + else { + printf(" the readout passed the SRAM health checks; only a genuine\r\n" + " power cycle establishes that it is fresh power-on entropy\r\n"); + } + + /* Drop any line noise latched in the receiver before prompting. */ + uart_drain(); + + menu(); + + for (;;) { + printf("\r\n> "); + c = uart_getc(); + printf("%c\r\n", (char)c); + switch (c) { + case '1': do_enroll(); break; + case '2': do_sweep(); break; + case '3': do_two_keys(); break; + case '4': do_dump_helper(); break; + case '5': do_load_helper(); break; + case 'r': + case 'R': + printf(" rebooting...\r\n\r\n"); + /* AIRCR: VECTKEY 0x5FA | SYSRESETREQ */ + *(volatile uint32_t*)0xE000ED0Cu = 0x05FA0004u; + for (;;) { } + default: menu(); break; + } + } +} diff --git a/puf/stm32.c b/puf/stm32.c index 0879b2812..d1919db33 100644 --- a/puf/stm32.c +++ b/puf/stm32.c @@ -59,6 +59,8 @@ /* USART3 registers */ #define USART3_CR1 (*(volatile uint32_t *)(USART3_BASE + 0x00u)) +#define USART3_RDR (*(volatile uint32_t *)(USART3_BASE + 0x24u)) +#define USART3_ICR (*(volatile uint32_t *)(USART3_BASE + 0x20u)) #define USART3_CR2 (*(volatile uint32_t *)(USART3_BASE + 0x04u)) #define USART3_CR3 (*(volatile uint32_t *)(USART3_BASE + 0x08u)) #define USART3_BRR (*(volatile uint32_t *)(USART3_BASE + 0x0Cu)) @@ -100,7 +102,19 @@ static void uart_init(void) GPIO_OSPEEDR(GPIOD_BASE) |= (3u << 16); /* High speed for PD8 */ afr = GPIO_AFRH(GPIOD_BASE); afr &= ~(0xFu << 0); - afr |= (7u << 0); /* AF7 = USART3 */ + afr |= (7u << 0); /* AF7 = USART3 TX on PD8 */ + GPIO_AFRH(GPIOD_BASE) = afr; + + /* Configure PD9 (RX) as AF7 as well. Needed for the interactive menu; + * the original one-shot example was transmit-only. MODER pin 9 is bits + * [19:18]; AFRH pin 9 is bits [7:4]. */ + moder = GPIO_MODER(GPIOD_BASE); + moder &= ~(3u << 18); + moder |= (2u << 18); + GPIO_MODER(GPIOD_BASE) = moder; + afr = GPIO_AFRH(GPIOD_BASE); + afr &= ~(0xFu << 4); + afr |= (7u << 4); GPIO_AFRH(GPIOD_BASE) = afr; /* Configure USART3 for UART_BAUD_HZ at the post-reset PCLK1 (see @@ -110,7 +124,7 @@ static void uart_init(void) USART3_CR3 = 0; USART3_PRESC = 0; USART3_BRR = UART_PCLK_HZ / UART_BAUD_HZ; - USART3_CR1 = (1u << 3); /* TE */ + USART3_CR1 = (1u << 3) | (1u << 2); /* TE | RE */ delay(10); USART3_CR1 |= (1u << 0); /* UE */ delay(100); @@ -266,3 +280,28 @@ unsigned long my_time(unsigned long* timer) *timer = t; return t++; } + +/* Blocking single-character read, used by the interactive demo menu. */ +int uart_getc(void) +{ + /* ISR bit 5 = RXNE (receive register not empty), bit 3 = ORE (overrun). + * A pasted block arrives back-to-back with no flow control, so clear ORE + * (ICR bit 3) rather than let it wedge the receiver. */ + for (;;) { + if ((USART3_ISR & (1u << 3)) != 0u) + USART3_ICR = (1u << 3); + if ((USART3_ISR & (1u << 5)) != 0u) + break; + } + return (int)(USART3_RDR & 0xFFu); +} + +/* Discard anything latched in the receiver (line noise at reset). */ +void uart_drain(void) +{ + volatile uint32_t sink; + while ((USART3_ISR & (1u << 5)) != 0u) { + sink = USART3_RDR; + (void)sink; + } +}