`[profile.release]` now uses lto, codegen-units=1, strip, and
panic=abort (last one matches the build-std=panic_abort already in
.cargo/config.toml). Combined: firmware shrinks from 1.71 MB to
1.52 MB (~9% smaller).
partitions.csv: grow each OTA slot from 1.75 MB to 1.9375 MB by
absorbing the previously-unused 384 KB at the end of flash. Now no
unused space at the end. Repartitioning is a USB-only migration
(`make flash-all`); device migrated locally before pushing.
Net headroom per slot: 126 KB -> 463 KB (3.7x). Plenty for the
upcoming e-ink driver + embedded-graphics stack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each OTA fetch is now gated on a Sigstore Bundle signature whose Fulcio
cert identifies a member of an allowlist hardcoded in src/trust.rs
(currently imjasonh@gmail.com / accounts.google.com). The allowlist
cannot be changed via OTA -- only by editing source and reflashing
over USB.
Publisher (Makefile): make publish now runs cosign sign --yes after
the publisher push. Cosign keyless OIDC pops a browser the first time;
subsequent signs reuse the cached token within ~10min.
Firmware:
- src/trust.rs: TRUSTED_IDENTITIES list + bundled Sigstore root and
intermediate CA PEMs (trust/fulcio_root.pem, fulcio_intermediate.pem).
- src/sig.rs: parse Sigstore Bundle v0.3 (DSSE envelope), verify (a)
Fulcio cert SAN email + OID 1.3.6.1.4.1.57264.1.1 issuer match the
allowlist, (b) leaf cert chains to bundled Sigstore root via
P-384 ECDSA-SHA384, (c) DSSE signature verifies via P-256 ECDSA-SHA256
over the PAE, (d) in-toto Statement subject digest binds to our
manifest digest.
- src/ota.rs: fetch_manifest now returns the manifest's own SHA256 (the
digest cosign signed), not just the parsed body. New
fetch_signature_bundle walks the OCI 1.1 referrers layout cosign uses
(image index -> inner manifest -> bundle blob). Sig is fetched and
verified before the firmware download starts.
Crates: p256, p384, x509-cert (with pem feature), base64. Adds
~350 KB to the firmware -- repartitioned ota slots from 1.5MB to
1.75MB to fit (USB-only migration). Bumped OTA thread stack to
48KB for cert-parsing headroom.
Verified end-to-end: Jason signed :latest with cosign keyless,
device polled, all four verification steps passed (identity, chain,
DSSE sig, in-toto binding), then downloaded and applied as before.
Phase 4b (Rekor SET / transparency log inclusion proof) and 4c
(operational hardening) remain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2-slot OTA partition table (ota_0/ota_1, 1.5MB each) on 4MB flash.
sdkconfig.defaults is now generated by the Makefile from a template
so we can substitute the absolute path to partitions.csv -- IDF's
CMake resolves CONFIG_PARTITION_TABLE_CUSTOM_FILENAME relative to
embuild's synthetic project under target/, not our repo root.
Adds make flash-all for full erase + bootloader + partition table +
app reflash; doubles as USB recovery if both OTA slots end up bad.
Includes plans for e-ink display work (eink-plan.md) and OCI-based
OTA updates via GHCR (ota-plan.md). Phase 0 of OTA shipped: device
boots into ota_0, verified via esp_ota_get_running_partition() log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>