$ cat ~/work/signed-firmware-updates.md
project · security · 2026
Fail-Safe Signed Firmware Updates
A secure bootloader and OTA path for an STM32 nobody can reach
A secure bootloader and fail-safe update system for an STM32G474: ECDSA-signed images, A/B flash banks executed in place, trial boot with automatic rollback, anti-rollback, and power-cut testing on a simulated flash. Boot, verification and updates over UART are working and verified on hardware; the CAN link and the gateway are next.
- kind
- project
- category
- security
- period
- Sep 2026 - Present
- status
- in progress
head -n 20 README
Problem
Once the node is in the field you cannot patch it by hand. Power can die mid-flash, a signed image can still be broken, and the gateway or link can be hostile.
What's working
Bootloader verifies bounds, hash, key id, ECDSA P-256, and anti-rollback before every jump. A/B slots, trial boot with rollback, UART updates with resume. 13/13 bad images rejected on target. Host flash simulator plus power-fail sweeps. Fixed a flash read-while-write erratum; 150 clean updates after.
What's next
CAN at 500 kbit/s with ISO-TP, then the ESP32-S3 gateway (Wi-Fi fetch, staging, per-node state). Out of scope for now: debug lockdown, fault injection, encryption.
grep -n highlights
- 01Bootloader that verifies bounds, hash, key id, ECDSA P-256 signature and anti-rollback floor before every jump; 13 of 13 broken images rejected on target with the expected reason.
- 02A/B slots executed in place across the two flash banks, so a version switch is one boot-record write and the running image is never touched.
- 03Append-only, CRC-protected boot state designed around the ECC double-word limits of the G474 flash, with an exhaustive clean and torn power-cut sweep over every flash operation of an update.
- 04Trial boot with three recorded attempts, watchdog supervision of every task, confirmation only after a healthy status exchange, and a 60-second deadline; both rollback paths verified on the board.
- 05Updates over UART in 1 KB CRC-checked chunks with resume across a reset: a 30.5 KB image transfers in 6.0 s, verifies on the node in 2.0 s, and is confirmed about 16 s after the transfer starts.
- 06Found and fixed a one-in-twenty update failure by logging to CCM SRAM across resets: a documented silicon erratum in flash read-while-write, confirmed by 150 consecutive clean updates after the driver change.
- 07CI on GCC and Clang with and without sanitizers, a host verifier cross-checked against the signing tool on every image, and a job that fails the build if a private key is ever committed.
cat spec.toml
[threats]
- -Power loss at any instant, including in the middle of an erase or a program operation
- -Firmware that is correctly signed but does not work
- -A compromised gateway, link, or both
[flash]
- -60 KB bootloader region; 4 KB boot state as a ping-pong pair of pages
- -Slot A and Slot B, 192 KB each, one per flash bank, executed in place
- -512-byte image header, program linked at slot + 0x200, TLV trailer with SHA-256, key id and raw r||s ECDSA P-256 signature
[node]
- -Bootloader: start the independent watchdog, verify, select a slot, jump; no transport code, no RTOS
- -Boot record: 32-byte snapshot, CRC over the state, commit word programmed last, newest valid record wins
- -Slot states EMPTY, PENDING, TRIAL, CONFIRMED and REJECTED; distinct rejection reasons for corruption, forgery, wrong slot and downgrade
- -FreeRTOS application with static allocation; watchdog fed only by a supervisor task once every registered task has reported a heartbeat
[verification]
- -Flash simulator: erase to 0xFF, 64-bit double-word programming, no reprogramming, torn writes fault on read
- -Power cuts enumerated by operation number, clean and torn, with the state checked after every simulated reboot
- -Negative tests: bad magic, four header variants, bad hash, four signature variants, wrong-slot image, downgrade; host and on-target
- -On-target update tests: both directions with confirmation, refused downgrade, refused update during a trial, both rollback paths
[measured]
- -ECDSA P-256 verify at the 16 MHz reset clock: 1.7 s; worst-case verification of a full 192 KB slot: 2.6 s
- -Update over UART at 115200 baud: 30.5 KB image in 6.0 s, 2.0 s verification, about 16 s to a confirmed image
- -150 consecutive updates, about 1.2 million flash operations, with no fault after the read-while-write fix
[status]
- -Done and verified on hardware: host tooling and simulator, boot core, on-device verification, updates over UART with resume and anti-rollback
- -Next: classic CAN at 500 kbit/s with ISO-TP on both ends, then the ESP32-S3 gateway with Wi-Fi fetch, staging and per-node state
- -Out of scope by design: debug-port lockdown (a provisioning step), fault injection (deferred), firmware encryption (a stretch goal with known prerequisites)