Decisions · ADR 0003

Signed Update Files checked by the firmware, not ESP32 Secure Boot

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.…

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 overrides verifyRollbackLater(), 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 in setup(), and reverts itself on the second unconfirmed start.

This page is generated from docs/adr/0003-own-signature-check-not-secure-boot.md in the repository. To change it, change that file and run site/tools/gen_dev_docs.py.