puf: fix transmit-only UART HAL and add an interactive INTERACTIVE=1 demo mode
parent
d421168ad8
commit
ef8181959e
|
|
@ -1359,6 +1359,14 @@ examples:
|
|||
mode: skip
|
||||
reason: "shared helper (btle-sim.c) linked by btle/ecies and btle/tls; not an example"
|
||||
|
||||
- id: puf-host-test
|
||||
path: puf/host_test
|
||||
mode: skip
|
||||
reason: >-
|
||||
Host behavioral test harness for puf's interactive demo, not an example:
|
||||
puf.yml builds it and runs driver.py against wolfSSL master (the demo
|
||||
needs post-v5.9.2 PUF APIs, so the stable ref is excluded there)
|
||||
|
||||
- id: hsm-dtls-client
|
||||
path: hsm/dtls_client
|
||||
mode: skip
|
||||
|
|
|
|||
|
|
@ -62,6 +62,30 @@ 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
|
||||
make WOLFSSL_ROOT=/tmp/wolfssl INTERACTIVE=1 SHOW_KEYS=1
|
||||
|
||||
# Host build of the interactive demo with a stdio HAL, driven end to
|
||||
# end: enrollment, the sweep gate and correction cliff, blob dump and
|
||||
# recovery, checksum and identity-mismatch rejection, paste abort, and
|
||||
# the fail-closed unhealthy-readout path.
|
||||
- name: Run interactive demo behavioral test (wolfSSL master only)
|
||||
if: matrix.wolfssl_ref == 'master'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd puf/host_test
|
||||
make WOLFSSL_ROOT=/tmp/wolfssl run
|
||||
|
||||
- name: Assert it really cross-compiled
|
||||
run: |
|
||||
|
|
|
|||
42
puf/Makefile
42
puf/Makefile
|
|
@ -20,14 +20,31 @@ 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
|
||||
|
||||
# The interactive demo never writes derived keys to the UART by default (the
|
||||
# console is unauthenticated). SHOW_KEYS=1 opts in for lab use.
|
||||
SHOW_KEYS ?= 0
|
||||
|
||||
# Architecture
|
||||
ARCHFLAGS = -mcpu=cortex-m33 -mthumb -mabi=aapcs
|
||||
|
||||
|
|
@ -41,6 +58,19 @@ ifeq ($(PUF_TEST),1)
|
|||
CFLAGS += -DWOLFSSL_PUF_TEST
|
||||
endif
|
||||
|
||||
BUILD_SUFFIX =
|
||||
ifeq ($(INTERACTIVE),1)
|
||||
BUILD_SUFFIX := $(BUILD_SUFFIX)-interactive
|
||||
ifeq ($(SHOW_KEYS),1)
|
||||
CFLAGS += -DPUF_DEMO_SHOW_KEYS
|
||||
BUILD_SUFFIX := $(BUILD_SUFFIX)-showkeys
|
||||
endif
|
||||
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 +82,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 +136,5 @@ $(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-interactive-showkeys ./Build-hw
|
||||
$(MAKE) -C host_test clean
|
||||
|
|
|
|||
|
|
@ -46,6 +46,97 @@ 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
|
||||
|
||||
The interactive demo has a host-side behavioral test: `host_test/` builds
|
||||
`main_interactive.c` against a stdio HAL and `driver.py` drives the menu end
|
||||
to end (enrollment, the sweep gate and correction cliff, blob dump and
|
||||
recovery, checksum and identity-mismatch rejection, paste abort, and the
|
||||
fail-closed unhealthy-readout path). CI runs it on every change; locally:
|
||||
`make -C host_test WOLFSSL_ROOT=/path/to/wolfssl run`.
|
||||
|
||||
```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.
|
||||
|
||||
Derived keys are never written to the UART by default - the console is an
|
||||
unauthenticated physical interface, so the demo prints a "derived OK (not
|
||||
shown)" status instead. For lab work where seeing the key bytes matters,
|
||||
`make INTERACTIVE=1 SHOW_KEYS=1` opts in explicitly.
|
||||
|
||||
If the power-on readout fails the Hamming-weight health band (which is what a
|
||||
warm reset looks like, since SRAM keeps the previous image's data), the demo
|
||||
fails closed: enrollment, reconstruction, and key derivation are disabled
|
||||
until a genuine power cycle provides a real readout. Nothing is ever derived
|
||||
from a substitute pattern.
|
||||
|
||||
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`.
|
||||
|
||||
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/`:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
# Host behavioral test for the interactive PUF demo: builds
|
||||
# main_interactive.c with a stdio HAL (harness.c) and drives the menu with
|
||||
# driver.py, covering enrollment, the noise sweep, blob dump/recovery,
|
||||
# checksum and identity-mismatch rejection, abort, and the fail-closed
|
||||
# unhealthy-readout path. Requires wolfSSL master (same as INTERACTIVE=1).
|
||||
WOLFSSL_ROOT ?= ../../../wolfssl
|
||||
CC ?= gcc
|
||||
CFLAGS = -Wall -Og -g -DWOLFSSL_USER_SETTINGS -DWOLFSSL_PUF_TEST
|
||||
CFLAGS += -I.. -I$(WOLFSSL_ROOT)
|
||||
|
||||
SRC = ../main_interactive.c harness.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/puf.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/sha256.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/kdf.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/hmac.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/hash.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/memory.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/wc_port.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/error.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/misc.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/logging.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/random.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/sp_int.c
|
||||
SRC += $(WOLFSSL_ROOT)/wolfcrypt/src/sha3.c
|
||||
|
||||
puf_host_test: $(SRC) ../user_settings.h
|
||||
$(CC) $(CFLAGS) -o $@ $(filter %.c,$^)
|
||||
|
||||
run: puf_host_test
|
||||
python3 driver.py ./puf_host_test
|
||||
|
||||
clean:
|
||||
rm -f puf_host_test
|
||||
|
||||
.PHONY: run clean
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Behavioral test driver for the interactive PUF demo (host build).
|
||||
|
||||
Drives the menu over stdin/stdout and asserts on the demo's output markers:
|
||||
enrollment, the sweep gate and correction cliff, blob dump and recovery,
|
||||
checksum rejection, identity-mismatch rejection, paste abort, and the
|
||||
fail-closed unhealthy-readout path. Exits nonzero on the first failure.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
BIN = sys.argv[1] if len(sys.argv) > 1 else "./puf_host_test"
|
||||
TIMEOUT = 15
|
||||
|
||||
|
||||
class Demo:
|
||||
def __init__(self, env=None):
|
||||
e = dict(os.environ)
|
||||
if env:
|
||||
e.update(env)
|
||||
self.p = subprocess.Popen([BIN], stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, env=e)
|
||||
self.buf = b""
|
||||
|
||||
def send(self, text):
|
||||
self.p.stdin.write(text.encode())
|
||||
self.p.stdin.flush()
|
||||
|
||||
def expect(self, *patterns):
|
||||
"""Read until every pattern has appeared (in the stream so far)."""
|
||||
deadline = time.time() + TIMEOUT
|
||||
remaining = list(patterns)
|
||||
while remaining:
|
||||
remaining = [p for p in remaining
|
||||
if not re.search(p.encode(), self.buf)]
|
||||
if not remaining:
|
||||
break
|
||||
if time.time() > deadline:
|
||||
raise AssertionError(
|
||||
"timeout waiting for %r; got:\n%s" %
|
||||
(remaining, self.buf.decode(errors="replace")[-2000:]))
|
||||
r, _, _ = select.select([self.p.stdout], [], [], 0.2)
|
||||
if r:
|
||||
chunk = os.read(self.p.stdout.fileno(), 65536)
|
||||
if not chunk:
|
||||
raise AssertionError(
|
||||
"EOF waiting for %r; got:\n%s" %
|
||||
(remaining, self.buf.decode(errors="replace")[-2000:]))
|
||||
self.buf += chunk
|
||||
|
||||
def absent(self, pattern):
|
||||
if re.search(pattern.encode(), self.buf):
|
||||
raise AssertionError("unexpected %r in:\n%s" %
|
||||
(pattern, self.buf.decode(errors="replace")))
|
||||
|
||||
def clear(self):
|
||||
self.buf = b""
|
||||
|
||||
def close(self):
|
||||
self.p.stdin.close()
|
||||
try:
|
||||
self.p.wait(timeout=TIMEOUT)
|
||||
finally:
|
||||
if self.p.poll() is None:
|
||||
self.p.kill()
|
||||
|
||||
|
||||
def checksum(data):
|
||||
s = 0xFFFF
|
||||
for b in data:
|
||||
s = ((s << 5) ^ (s >> 11) ^ b) & 0xFFFF
|
||||
return s
|
||||
|
||||
|
||||
def healthy_run():
|
||||
d = Demo()
|
||||
d.expect(r"interactive demo", r"inside the health band")
|
||||
d.absent(r"synthetic")
|
||||
|
||||
# sweep is gated before an enrollment from this boot
|
||||
d.clear()
|
||||
d.send("2")
|
||||
d.expect(r"run \[1\] enroll first")
|
||||
|
||||
# enroll: identity shown, key NOT shown by default
|
||||
d.clear()
|
||||
d.send("1")
|
||||
d.expect(r"enrolled from this boot", r"identity : [0-9a-f]{32}",
|
||||
r"derived OK \(not shown")
|
||||
d.absent(r"derived key : [0-9a-f]{32}")
|
||||
|
||||
# sweep: full correction cliff, never the wrong key
|
||||
d.clear()
|
||||
d.send("2")
|
||||
d.expect(r"<= t, the limit", r"rejected \(-\d+\) - fails closed")
|
||||
d.absent(r"WRONG KEY")
|
||||
|
||||
# two keys: derivation succeeds, no key material on the wire
|
||||
d.clear()
|
||||
d.send("3")
|
||||
d.expect(r"two HKDF contexts", r"derived OK \(not shown")
|
||||
d.absent(r"[0-9a-f]{32}\r")
|
||||
|
||||
# dump the recovery blob (id + helper + 2-byte checksum, one hex line)
|
||||
d.clear()
|
||||
d.send("4")
|
||||
d.expect(r"\r\n[0-9a-f]{300,}\r\n")
|
||||
blob = re.search(rb"\r\n([0-9a-f]{300,})\r\n", d.buf).group(1).decode()
|
||||
raw = bytes.fromhex(blob)
|
||||
assert checksum(raw[:-2]) == int.from_bytes(raw[-2:], "big"), \
|
||||
"dumped blob checksum does not verify"
|
||||
|
||||
# paste it back: checksum OK, same key
|
||||
d.clear()
|
||||
d.send("5")
|
||||
d.expect(r"paste the recovery blob")
|
||||
d.send(blob)
|
||||
d.expect(r"checksum [0-9a-f]{4} OK", r"SAME KEY")
|
||||
|
||||
# sweep gated again after a loaded blob
|
||||
d.clear()
|
||||
d.send("2")
|
||||
d.expect(r"run \[1\] enroll first")
|
||||
|
||||
# mangled checksum: rejected, nothing changed
|
||||
d.clear()
|
||||
d.send("5")
|
||||
d.expect(r"paste the recovery blob")
|
||||
bad = blob[:-1] + ("0" if blob[-1] != "0" else "1")
|
||||
d.send(bad)
|
||||
d.expect(r"checksum mismatch", r"nothing was changed")
|
||||
|
||||
# corrupted identity with a recomputed valid checksum: MISMATCH, no commit
|
||||
body = bytearray(raw[:-2])
|
||||
body[0] ^= 0x01
|
||||
wrong = body.hex() + format(checksum(body), "04x")
|
||||
d.clear()
|
||||
d.send("5")
|
||||
d.expect(r"paste the recovery blob")
|
||||
d.send(wrong)
|
||||
d.expect(r"MISMATCH - this blob does not belong",
|
||||
r"nothing was changed")
|
||||
|
||||
# truncated paste + q: aborted
|
||||
d.clear()
|
||||
d.send("5")
|
||||
d.expect(r"paste the recovery blob")
|
||||
d.send(blob[:40] + "q")
|
||||
d.expect(r"aborted")
|
||||
|
||||
d.close()
|
||||
print("healthy-path scenarios: PASS")
|
||||
|
||||
|
||||
def unhealthy_run():
|
||||
d = Demo(env={"PUF_HOST_UNHEALTHY": "1"})
|
||||
d.expect(r"REJECTED by the health band",
|
||||
r"enrollment and key derivation are disabled")
|
||||
d.absent(r"synthetic")
|
||||
for opt in "135":
|
||||
d.clear()
|
||||
d.send(opt)
|
||||
# option 3 is additionally gated on enrollment; either refusal is a
|
||||
# correct fail-closed response
|
||||
d.expect(r"(failed the health check|run \[1\] enroll first)")
|
||||
d.absent(r"[0-9a-f]{32}")
|
||||
d.close()
|
||||
print("unhealthy fail-closed scenarios: PASS")
|
||||
|
||||
|
||||
def main():
|
||||
healthy_run()
|
||||
unhealthy_run()
|
||||
print("ALL PASS")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/* harness.c - host-side stand-ins for the interactive PUF demo's HAL so the
|
||||
* menu logic, blob parsing, and fail-closed paths can be exercised without
|
||||
* hardware. The UART becomes stdio; the PUF region is seeded with a balanced
|
||||
* deterministic pattern before main() runs, or left all-zero (which fails the
|
||||
* Hamming-weight health band) when PUF_HOST_UNHEALTHY is set.
|
||||
*
|
||||
* Copyright (C) 2006-2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfSSL.
|
||||
*
|
||||
* wolfSSL is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfSSL is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/wolfcrypt/puf.h>
|
||||
|
||||
extern volatile uint8_t puf_sram_region[WC_PUF_RAW_BYTES];
|
||||
|
||||
__attribute__((constructor))
|
||||
static void seed_region(void)
|
||||
{
|
||||
uint32_t x = 0x12345678u;
|
||||
unsigned int i;
|
||||
|
||||
if (getenv("PUF_HOST_UNHEALTHY") != NULL) {
|
||||
return; /* all-zero: rejected by the health band */
|
||||
}
|
||||
for (i = 0; i < (unsigned int)WC_PUF_RAW_BYTES; i++) {
|
||||
x ^= x << 13; x ^= x >> 17; x ^= x << 5;
|
||||
puf_sram_region[i] = (uint8_t)x;
|
||||
}
|
||||
}
|
||||
|
||||
void hal_init(void)
|
||||
{
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
}
|
||||
|
||||
int uart_getc(void)
|
||||
{
|
||||
int c = getchar();
|
||||
if (c == EOF) {
|
||||
exit(0);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void uart_drain(void)
|
||||
{
|
||||
}
|
||||
|
||||
int custom_rand_gen_block(unsigned char* output, unsigned int sz)
|
||||
{
|
||||
static uint32_t x = 0xA5A5A5A5u;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < sz; i++) {
|
||||
x ^= x << 13; x ^= x >> 17; x ^= x << 5;
|
||||
output[i] = (unsigned char)x;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,527 @@
|
|||
/* main_interactive.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.
|
||||
*
|
||||
* Lines are terminated with an explicit \r\n: the host behavioral test
|
||||
* (host_test/driver.py) matches literal CRLF, and on the hardware UART the
|
||||
* extra CR added by _write() is harmless.
|
||||
*
|
||||
* Copyright (C) 2006-2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfSSL.
|
||||
*
|
||||
* wolfSSL is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfSSL is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#include <wolfssl/wolfcrypt/puf.h>
|
||||
#include <wolfssl/wolfcrypt/error-crypt.h>
|
||||
|
||||
/* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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. Non-static
|
||||
* so the host behavioral test harness can seed it before main() runs. */
|
||||
__attribute__((section(".puf_sram")))
|
||||
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;
|
||||
|
||||
/* Derived keys are never written to the UART by default: the console is an
|
||||
* unauthenticated physical interface. make SHOW_KEYS=1 opts in for lab use. */
|
||||
static void print_key(const char* label, const uint8_t* key, uint32_t len)
|
||||
{
|
||||
#ifdef PUF_DEMO_SHOW_KEYS
|
||||
uint32_t i;
|
||||
printf("%s", label);
|
||||
for (i = 0; i < 16u && i < len; i++)
|
||||
printf("%02x", key[i]);
|
||||
printf("\r\n");
|
||||
#else
|
||||
printf("%s%u bytes derived OK (not shown; build SHOW_KEYS=1 to "
|
||||
"display)\r\n", label, (unsigned int)len);
|
||||
(void)key;
|
||||
#endif
|
||||
}
|
||||
|
||||
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 int require_healthy(void)
|
||||
{
|
||||
if (!g_rawHealthy) {
|
||||
printf(" the power-on readout failed the health check, so this is\r\n"
|
||||
" disabled - deriving from anything else would produce a\r\n"
|
||||
" device-independent key. Power-cycle the board (a warm\r\n"
|
||||
" reset leaves old data in SRAM) and try again.\r\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void do_enroll(void)
|
||||
{
|
||||
wc_PufCtx ctx;
|
||||
uint8_t key[WC_PUF_KEY_SZ];
|
||||
int ret;
|
||||
|
||||
if (!require_healthy())
|
||||
return;
|
||||
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 this boot's power-on SRAM readout\r\n");
|
||||
print_hex(" identity : ", g_id, 16);
|
||||
print_key(" derived key : ", key, sizeof(key));
|
||||
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;
|
||||
|
||||
if (!g_enrolled) {
|
||||
printf(" run [1] enroll first\r\n");
|
||||
return;
|
||||
}
|
||||
if (!require_healthy())
|
||||
return;
|
||||
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_key(" \"nv-integrity\" : ", k1, sizeof(k1));
|
||||
print_key(" \"device-identity\" : ", k2, sizeof(k2));
|
||||
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;
|
||||
|
||||
if (!require_healthy())
|
||||
return;
|
||||
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_key(" \"nv-integrity\" : ", k1, sizeof(k1));
|
||||
print_key(" \"device-identity\" : ", k2, sizeof(k2));
|
||||
/* 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();
|
||||
c = wolfCrypt_Init();
|
||||
if (c != 0) {
|
||||
printf("ERROR: wolfCrypt_Init failed: %d\r\n", c);
|
||||
for (;;) { }
|
||||
}
|
||||
|
||||
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"
|
||||
" so enrollment and key derivation are disabled - deriving\r\n"
|
||||
" from anything else would produce a device-independent\r\n"
|
||||
" key. Power-cycle the board (a warm reset leaves old data\r\n"
|
||||
" in SRAM) and try again.\r\n");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
puf/stm32.c
43
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue