-
Notifications
You must be signed in to change notification settings - Fork 18
docs(events): define signed event firmware OTA contract #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,103 @@ The manifest is first-party but **remote**, and it drives rendered text, opened | |
| - **`fonts` are family-name identifiers, not URLs.** Resolve them only against the platform's own font provider. Never treat the value as a fetchable location. | ||
| - **Colors are `#RRGGBB`.** Parse strictly and drop malformed entries rather than substituting a default that misrepresents the brand. | ||
|
|
||
| ### Executable firmware is authorized separately | ||
|
|
||
| The display manifest does not authorize installation. In particular, | ||
| `firmware.zipUrl` is informational and must never be promoted directly into an | ||
| in-app OTA action. A client that offers event firmware OTA fetches a separately | ||
| signed contract for the selected edition: | ||
|
|
||
| ```text | ||
| GET https://api.meshtastic.org/resource/eventFirmware/{EDITION}/ota | ||
| ``` | ||
|
|
||
| Unknown editions return `404`. If the API signing key is unavailable, the | ||
| endpoint fails closed with `503`; it must not return an unsigned contract. | ||
|
|
||
| The response is an Ed25519 envelope. `payload` is the exact UTF-8 JSON bytes, | ||
| base64 encoded, that were signed: | ||
|
|
||
| ```jsonc | ||
| { | ||
| "keyId": "event-release-2026", | ||
| "payload": "<base64 contract JSON>", | ||
| "signature": "<base64 Ed25519 signature over decoded payload bytes>" | ||
| } | ||
| ``` | ||
|
|
||
| Clients ship the trusted public key keyed by `keyId`. A key delivered by this | ||
| endpoint, the display manifest, or an artifact host is not a trust root. | ||
| Unknown keys, malformed base64, invalid signatures, expired contracts, and | ||
| unsupported schema versions make in-app installation unavailable. | ||
|
|
||
| The schema-1 contract is scoped to one event release: | ||
|
|
||
| ```jsonc | ||
| { | ||
| "schemaVersion": 1, | ||
| "releaseId": "defcon34-2.8.0.b00d76f", | ||
| "edition": "DEFCON", | ||
| "version": "2.8.0.b00d76f", | ||
| "issuedAt": "2026-07-29T00:00:00Z", | ||
| "expiresAt": "2026-10-01T00:00:00Z", | ||
| "artifacts": [ | ||
| { | ||
| "pioEnv": "tbeam-s3-core", | ||
| "hwModel": 12, | ||
| "architecture": "esp32-s3", | ||
| "version": "2.8.0.b00d76f", | ||
| "format": "bin", | ||
| "url": "https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/<40-hex-commit>/event/...", | ||
| "sha256": "<64 lowercase hex characters>", | ||
| "byteCount": 2383792, | ||
| "minimumSourceVersion": "2.7.26", | ||
| "partitionRole": "app0", | ||
| "partitionScheme": "8MB", | ||
| "dfuProtocol": null, | ||
| "minimumBootloaderVersion": null | ||
| } | ||
| ], | ||
| "standardArtifacts": [] | ||
| } | ||
| ``` | ||
|
|
||
| `issuedAt` and `expiresAt` use UTC ISO-8601 timestamps with whole-second | ||
| precision (`yyyy-MM-dd'T'HH:mm:ss'Z'`). `releaseId` and `keyId` are stable, | ||
| bounded identifiers; they are not URLs or user-facing labels. | ||
|
|
||
| `artifacts` installs the event edition. `standardArtifacts` provides the | ||
| explicit return path to ordinary Meshtastic firmware. A production contract | ||
| must contain both sets; a client must not infer the return artifact by editing | ||
| an event URL or selecting whatever release happens to be latest. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| Selection is exact on `pioEnv`, `hwModel`, and `architecture`, with exactly one | ||
| matching artifact. The client also enforces `minimumSourceVersion` and | ||
| architecture-specific compatibility: | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| - ESP32 artifacts are bare application `.bin` payloads for `app0`, never | ||
| factory, merged, filesystem, or OTA-loader images. Their partition scheme | ||
| must exactly match the connected target. | ||
| - nRF52 artifacts are target-specific Nordic DFU OTA ZIPs. The contract names | ||
| the DFU protocol and minimum compatible bootloader. A client that cannot | ||
| establish the installed bootloader's compatibility must not offer in-app | ||
| DFU. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Make architecture-specific fields explicitly required. For nRF52 artifacts, 🤖 Prompt for AI Agents |
||
| - Unsupported architectures and devices without a proven app OTA path use the | ||
| web flasher. | ||
|
|
||
| Artifact URLs are HTTPS, use an approved project origin, and identify immutable | ||
| content. For `raw.githubusercontent.com`, the revision path component is a | ||
| 40-hex commit SHA. Clients reject redirects, enforce the signed byte count while | ||
| downloading, then verify both byte count and SHA-256 before opening the existing | ||
| OTA/DFU flow. Any failure falls back to the web flasher without writing bytes to | ||
| the device. | ||
|
|
||
| Signing keys are release infrastructure. Private keys live only in deployment | ||
| secrets; public keys ship in clients. Rotation adds a new `keyId` and public key | ||
| to clients before the API begins signing with it. Revocation requires removing | ||
| the affected contract and shipping a client update when the trust key itself is | ||
| compromised. | ||
|
|
||
| ### Manifest shape | ||
|
|
||
| ```jsonc | ||
|
|
@@ -226,12 +323,35 @@ Fall back to the device zone only when `timeZone` is absent or unparseable, and | |
|
|
||
| Drive this from the metadata date, not from a stored "event is over" bit: the prompt then appears whenever an ended-event device connects and disappears on its own once the device is re-flashed. | ||
|
|
||
| ## Behavior 5: Event Firmware Installation | ||
|
|
||
| An event info or firmware-updates screen may offer a convenient install action, | ||
| but only when the signed contract above authorizes one exact artifact for the | ||
| connected device. Display metadata alone never enables the action. | ||
|
|
||
| Before installation the client: | ||
|
|
||
| 1. Confirms the same device is still connected. | ||
| 2. Fetches and verifies the edition's signed contract. | ||
| 3. Selects one exact target and validates source-version and OTA compatibility. | ||
| 4. Downloads with a signed size ceiling and verifies SHA-256. | ||
| 5. Hands the local verified file to the platform's existing OTA/DFU flow. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| If any step is unavailable or fails, the primary action opens | ||
| `https://flasher.meshtastic.org`. After the event, a compatible device running | ||
| the event edition uses `standardArtifacts` through the same process. Clients | ||
| must label this as a firmware rewrite, keep their existing OTA safety UI, and | ||
| must not imply that background execution can keep a BLE/Wi-Fi transfer alive. | ||
|
|
||
| ## Adding a New Event | ||
|
|
||
| For an event whose enum value already exists, no client code changes are needed: | ||
|
|
||
| 1. Add an entry to `data/eventFirmware.json` in `meshtastic/api`. | ||
| 2. Add `static/eventFirmware/<slug>.png` and point `iconUrl` at it. | ||
| 3. To enable in-app OTA, add a reviewed entry to | ||
| `data/eventFirmwareOTA.json` with immutable per-target event and standard | ||
| artifacts, then configure the API's release signing key. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| Land both **well before the event** so online clients have refreshed their cache by the time attendees arrive. Per the caching table above, users who have been offline since install will not see the event until their client next reaches the network — bundling the icon and shipping the manifest snapshot in a client release is what covers them. | ||
|
|
||
|
|
@@ -242,7 +362,7 @@ A new enum value requires a proto change coordinated via the `meshtastic/protobu | |
| | Platform | Branding | Theme | Notifications | Post-event | Notes | | ||
| |---|---|---|---|---|---| | ||
| | Android | ✅ | ✅ | ✅ | ✅ | Fonts Google-flavor only; no URL scheme allowlist; edition lookup is a one-shot, so a cache refresh needs a reconnect | | ||
| | Apple | — | — | — | — | Not yet implemented | | ||
| | Apple | PR | PR | PR | PR | Signed event OTA contract and installer are in draft; hardware event install remains untested | | ||
| | Web | — | — | — | — | Not yet implemented | | ||
|
|
||
| ## Sub-tasks | ||
|
|
@@ -253,3 +373,5 @@ A new enum value requires a proto change coordinated via the `meshtastic/protobu | |
| - [ ] Resolve the two notification divergences above and align all platforms. | ||
| - [ ] Enforce the URL scheme allowlist on link and icon fetches (Android does not today). | ||
| - [ ] Make the edition lookup observable so a manifest refresh re-evaluates active branding without a reconnect (Android). | ||
| - [ ] Provision and document the production event-firmware signing key and rotation process. | ||
| - [ ] Hardware-validate each artifact target before adding it to a signed OTA contract. | ||
Uh oh!
There was an error while loading. Please reload this page.