diff --git a/docs/security/keys.md b/docs/security/keys.md index 3ff19a0c0..9e9505d97 100644 --- a/docs/security/keys.md +++ b/docs/security/keys.md @@ -104,6 +104,27 @@ next cut the Counter ASIC 2.0 coordinator assembles, not into 0.3.10 or 0.3.11). | 4 | Ship it in the next cut (the plan said 0.3.9; 0.3.9 and 0.3.10 went out without it, so it is 0.3.12 or whatever the coordinator numbers it), signed with K1, so every 0.3.5+ machine takes it on its hourly check. The publisher keeps signing with K1; K2 stays in its image. The engine logs a third header line, `ota keys: trusted []; revoked none|...`, and `tools/logs.mjs --rotation` has the `ota_keys` column (K1 and K2 named from the two `.pub` files; "REVOKED" when a machine reports one) and the line "N machine(s) embed K2, M embed K1 only, P log no ota keys line" | code done; the ship is the coordinator's | | 5 | When every machine reports a build with K2: K1 lost = `tools/keys/with-k2.sh -- packaging/ota/publish-manifest.sh ...` (the image attached read-only for the minutes of the publish, `IGNEUM_OTA_KEY_FILE`/`IGNEUM_OTA_PUB_FILE` set; `publish-jobs.sh` and `push-inputs.sh` read the same two); K1 leaked = the same with `--revoke 8f186e37b48cfddf52d2b37478c562d78a74236fc87f0a675393ed6a35baac4e` (K1's fingerprint). Known limit, accepted with the plan: the two keys are peers, so whoever holds a leaked K1 can sign a manifest that revokes K2 just as well; whichever manifest a machine fetches first wins, and the mitigation table below (take the files off the folder first) is what makes ours arrive first | when the fleet has moved | +Every path that trusts K1, and where each stands (consequences review C21, 5 October 2026). "K2 + revocation" means +the path verifies with the `[K1, K2]` list and honours `revoked_keys` as `manifest.rs check()` does. + +| Path | What it verifies | Key(s) today | State | Who | +|---|---|---|---|---| +| the app (Mac, Windows), `ota.rs fetch_manifest` | the update manifest | `OTA_PUBLIC_KEYS` minus `updates/revoked.json` | K2 + revocation, this branch | done | +| the app's jobs runner, `jobrun.rs fetch_jobs` | `igneum-jobs.json` / `.signed.json` (`kind: run` executes on every machine) | the same trusted list, read at every poll | K2 + revocation, this branch (the jobs file itself carries no `revoked_keys`; the manifest does) | done | +| the Windows build, `windows.yml:172` (`igneum-ota-sign verify-inputs embedded`) | `payload-inputs.json` on GitHub's runner | `OTA_PUBLIC_KEYS` (no revocation record on a runner) | K2, this branch; a revoked key still verifies in CI, which only gates what the Mac then signs | done | +| the rig installer and its hourly updater, branch `rig-install` (`packaging/linux/bin/igneum-rig-lib.sh:16,79,92`, `install-rig.sh:163-174`, `igneum-update.sh:2-5`) | the update manifest: the consensus override and the Linux package | `OTA_PUBLIC_KEY_HEX` alone, openssl or python3-cryptography; no revocation | OPEN: K1 lost = every rig refuses a K2-signed manifest, takes no override and is isolated at the next height switch; K1 leaked = it keeps signing for rigs. Needs the same list and rule (the form sent to the rig agent, below) | rig-install | +| the HiveOS package, `packaging/hive` | nothing: the override reaches a Hive rig only by a package republish; nothing checks who published the archive | none | OPEN: unsigned; the README now says so and names the check (the archive's sha256 from `make-hive-package.sh` against the file the Flight Sheet URL serves) | packaging/hive | +| the wallet, branch `wallet-v1` (`app/igneum-common/src/manifest.rs`, `updater.rs:729`) | the wallet manifest | `OTA_PUBLIC_KEY_HEX` alone; no revocation | OPEN: the follow-up at the end of this section | wallet-v1 | + +The form the rig installer takes, so one pair of files serves the app and the rigs: `OTA_PUBLIC_KEYS` as two hex +constants (`b3c9c5bd144e9d246dc0edf897387d4f3f7ca494cd47457123d1c2f892cabddd` = K1, and K2's 64 hex once +`keygen-k2.sh` has made it; empty = skipped), the signature tried against each in turn, the key that verified +remembered; the fingerprint = sha256 of the 32 RAW key bytes as hex (`printf '%s' "$hex" | xxd -r -p | sha256sum`), +K1 = `8f186e37b48cfddf52d2b37478c562d78a74236fc87f0a675393ed6a35baac4e`; a verified manifest's `revoked_keys` +(an array of such fingerprints) is recorded in `/var/lib/igneum/updates/revoked.json` except the signer's own +fingerprint (a key never revokes itself), and a recorded key is never tried again, before the openssl or python +verify. Same words in the log: "manifest signature is by a revoked key (sha256:<8 hex>)". + What the project lead runs (step 1), in the main checkout once this branch is merged, in this order: ``` diff --git a/packaging/hive/README.md b/packaging/hive/README.md index 6676e1a61..ed37d52ee 100644 --- a/packaging/hive/README.md +++ b/packaging/hive/README.md @@ -6,6 +6,10 @@ the two GPU workers (`igneum-worker-cuda` for NVIDIA, `igneum-worker-opencl` for scripts were self-tested with stub binaries (`selftest.sh`) and the binaries were cross-compiled on a Mac; the first real run on a Hive rig is still to come. Report what breaks. +The archive is UNSIGNED (unlike the app's update manifest, docs/security/keys.md section 4): before a Flight Sheet +points at a new `igneum-hive-.tar.gz`, check the file the URL serves against the sha256 that +`make-hive-package.sh` printed for that build (`curl -fsSL | sha256sum`), and republish only from this Mac. + ## Flight Sheet | Field | Value |