/dev/ has Debug Builds and the Debug Console (builds and the token, the console and its protocol, files and screenshots, driving the UI, crashes and Safe Mode, the command reference), Build, test and release (including how an update works), the architecture decisions and the milestone plans. Generated from the repository by site/tools/gen_dev_docs.py: the ADRs, the milestones, the README's sections, and the command reference, read from the firmware's own `help` text. The pages are committed (Zola cannot read outside its folder); the Site workflow checks they are current, and now also runs when src/main.cpp changes. M0, M1 and CONTEXT.md are not published. README: the gnss commands that the table lacked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2.0 KiB
+++ title = "Signed Update Files checked by the firmware, not ESP32 Secure Boot" description = "Firmware Updates are accepted only when their Update File carries a valid ECDSA P-256 signature over the image's SHA-256. The firmware itself checks it, against a public key compiled into it, before switching the boot partition.…" weight = 3
[extra]
docs = true
source = "docs/adr/0003-own-signature-check-not-secure-boot.md"
tag = "ADR 0003"
+++
Firmware Updates are accepted only when their Update File carries a valid ECDSA P-256 signature over the image's SHA-256. The firmware itself checks it, against a public key compiled into it, before switching the boot partition. The private key lives outside the repository, in ~/.config/roro9stack/ota-key.pem.
We chose this over the ESP32's hardware Secure Boot. Secure Boot is enforced by the chip, but it burns eFuses one-way: a mistake bricks the device, and the device can never run unsigned firmware again, which makes recovery over USB harder. On a single development device, a software check that refuses unsigned pushes is enough, and it stays reversible: a new firmware can carry a new public key.
Consequences
- Someone with physical USB access can still flash anything. Only Wi-Fi and SD card updates are guarded.
- Losing the private key means the next update has to go over USB, carrying a new public key.
- P-256 rather than Ed25519, because the firmware's TLS library (mbedTLS) already verifies it, so it costs no extra code.
- Rollback: the bootloader first, the firmware as a second line. Arduino-ESP32 marks a new image valid before
setup()unless the sketch overridesverifyRollbackLater(), which once made every update look good and hid the bootloader's rollback (it had looked like the prebuilt bootloader ignored it). With the override, an image stays pending until Probation confirms it, and the bootloader reverts one that restarts unconfirmed, however early it crashes. The firmware also counts its own boots on Probation, very first thing insetup(), and reverts itself on the second unconfirmed start.