A bare-metal secure bootloader for the STM32F407 (Cortex-M4) that verifies an ECDSA-P256 signature over a SHA-256 image hash before executing application firmware, and refuses to boot tampered or unsigned images.
Status: in development. See Roadmap.
Firmware running without verification means anyone who can write flash can run code with full hardware privilege. Secure boot establishes that only firmware signed by the holder of a private key will execute — the foundation every other embedded security control rests on.
POWER ON / RESET
│
▼
┌─────────────────────────────────────┐
│ ST mask ROM │
│ BOOT0 low → jump to 0x08000000 │
└─────────────────────────────────────┘
│
▼
╔═════════════════════════════════════════════════╗
║ The BOOTLOADER @ 0x08000000 (sectors 0–3) ║
║ 9,648 B flash (14.7% of the 64 KB reservation) ║
╠═════════════════════════════════════════════════╣
║ ║
║ init clocks, GPIO (LEDs blink) ║
║ │ ║
║ ▼ ║
║ read header @ 0x08020000 ║
║ │ ║
║ ▼ ║
║ magic == 0x4E495A41 ? ──── no ──┐ ║
║ │ yes │ ║
║ ▼ │ ║
║ img_len sane ? ────────── no ───┤ ║
║ │ yes │ ║
║ ▼ │ ║
║ ┌──────────────────────────┐ │ ║
║ │ SHA-256 over body │ │ PHASE 2 ║
║ │ 0x08020200 .. +img_len │ │ ║
║ └──────────────────────────┘ │ ║
║ │ │ ║
║ ▼ │ ║
║ hash == header.hash ? ─── no ───┤ ║
║ │ yes │ ║
║ ▼ │ ║
║ ┌──────────────────────────┐ │ ║
║ │ ECDSA-P256 verify │ │ PHASE 3 ║
║ │ sign over hash, pubkey │ │ ║
║ └──────────────────────────┘ │ ║
║ │ │ ║
║ ▼ │ ║
║ signature valid ? ─────── no ───┤ ║
║ │ yes │ ║
║ ▼ │ ║
║ version >= counter ? ──── no ───┤ PHASE 4 ║
║ │ yes │ ║
║ ▼ ▼ ║
║ JUMP TO APP REFUSE ║
║ │ red LED ║
║ │ halt forever ║
╚═══════════│═════════════════════════════════════╝
▼
┌─────────────────────────────────────┐
│ APPLICATION @ 0x08020200 │
│ green LED, runs normally │
└─────────────────────────────────────┘
| Sector | Address | Size | Use |
|---|---|---|---|
| 0-3 | 0x08000000 | 16 KB each | Bootloader |
| 4 | 0x08010000 | 64 KB | Metadata / rollback counter |
| 5-7 | 0x08020000 | 128 KB each | App slot A |
| 8-10 | 0x08080000 | 128 KB each | App slot B (update staging) |
Sector sizes on this part are non-uniform, and erase granularity is one sector — this drives the partitioning.
See docs/THREAT_MODEL.md. Read it before the code: it states
what this design defends against and, more importantly, what it does not.
Key limitation: the F407 has no immutable ROM root of trust and no TrustZone. The bootloader is the trust anchor by assumption, protected by flash write protection and readout protection rather than by hardware. On an STM32H5/U5, ROM would verify the bootloader itself.
| Function | Implementation | Rationale |
|---|---|---|
| SHA-256 | Written from specification, validated against NIST test vectors | Implemented to understand it; hash functions are straightforward to verify exhaustively |
| ECDSA-P256 | micro-ecc | Signature verification has subtle side-channel and input-validation failure modes; a reviewed implementation is the correct choice |
bootloader/ bootloader sources, SHA_256 implementation and linker script and third party uECC verified implementation
app/ demo application, linked at 0x08020200
tools/ host-side image signing (Correct and corrupted) and Python script for signing
tests/ SHA-256 test vectors, host-side verification harness
docs/ threat model, design notes, engineering log
- Phase 0 — threat model, repo, concepts
- Phase 1 — bootloader jumps to application
- Phase 2 — SHA-256 integrity check, tampered image refused
- Phase 3 — ECDSA-P256 signature verification, wrong-key image refused
- Phase 4 — flash write protection (WRP), RDP Level 1, anti-rollback counter
- Phase 5 — signed firmware update over UART with A/B slots
- Phase 6 — ESP32 UART bridge for wireless transport
- Phase 7 — AWS IoT Jobs for fleet update orchestration
- Phase 8 — demo video, writeup
- Build the bootloader and app in STM32CubeIDE (two separate projects).
- Generate the public key header:
python3 tools/gen_pubkey.py - Sign the application image:
python3 tools/sign_image.py app/Debug/Secure_Boot_App.bin build/app_signed.bin 1 - Flash with STM32CubeProgrammer:
- bootloader
.bin→0x08000000 app_signed.bin→0x08020000- Leave "Full chip erase" unchecked between the two writes or else it will clear everything
- bootloader
micro-ecc curve selection is configured via compiler defines — see
uECC Config ReadMe.
The signing private key is never committed to this repository. Private keys gitignored, public key committed.
The F407 has no radio and insufficient flash for a TLS stack, so network connectivity is handled by a separate ESP32 acting as a UART bridge: it terminates TLS and MQTT, then forwards the image to the bootloader over the same framed protocol used in Phase 5.
The bootloader's view is unchanged. This is deliberate — TLS authenticates the channel; ECDSA authenticates the image. They are separate controls and both are required. A compromised cloud account could push a TLS-valid but unsigned image, and the bootloader would still refuse it.