stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
Closed
HarryR wants to merge 1 commit into
Closed
stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5HarryR wants to merge 1 commit into
HarryR wants to merge 1 commit into
Conversation
ed25519 signed mode now pins { ed25519 pubkey, manifest_url } in the (trusted)
user-data instead of signing each artifact separately. stage0 fetches a signed
manifest ({ url, sha256, args, version }), verifies its detached signature against
the pinned key, then admits the payload by the manifest's exact sha256. Binding the
payload + args under one signature closes the mix-and-match / downgrade hole (#2): a
hostile mirror can neither recombine independently-signed pieces nor swap the
payload, while the release still rolls forward by re-signing a new manifest.
- config.rs: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url
plus a Manifest type; url/sha256 are now the sha256-mode fields. validate()
enforces the per-mode shape.
- main.rs: admit_payload dispatches by mode -- sha256 downloads from the inline url;
ed25519 fetches+verifies the manifest (manifest_sig_url, {sha256} -> manifest hash,
else <manifest_url>.sig) then admits by manifest.sha256. Drop fetch_signed_args.
- Makefile: the test payload is no longer self-signed; a signed manifest.json is
built + served, and boot-% serves a directory.
Verified: make boot-x86_64 admits via the manifest and runs the payload; a corrupted
manifest signature is rejected (SECURITY_VIOLATION) and fails closed with the payload
never running.
Anti-rollback (version enforcement via TPM NV) and domain-separated signatures are
deferred follow-ups on #2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 8, 2026
HarryR
added a commit
that referenced
this pull request
Jul 8, 2026
…ible boot-test matrix (#6) ## What Mirrors the stage1 rework on stage0's `_stage1` admission. Each per-arch entry is a **discriminated union**, exactly one of: - **`payload`** -- admit a UEFI binary now: `sha256` pin or `ed25519`-signed (`sig_url`), with inline or signed LoadOptions (`args` / `args_url`). - **`manifest`** -- fetch a signed manifest (a `_stage1` fragment), verify its detached signature against the pinned `ed25519` key, deep-merge it, and re-evaluate. Resolution loops through a chain of signed manifests (per-hop key delegation) until a payload is reached; a repeated `(url, sha256)` is a cycle and fails closed. stage0 forwards no document (the UKI re-fetches its own metadata), so the merged doc drives only re-evaluation. Closes the ed25519 mix-and-match malleability (#2) without dropping the flexible `url`/`sha256`/`ed25519`/`sig_url`/`args_url` paths. `config.rs` mirrors stage1's `metadata` types (http-only, Deserialize-only). ## Reproducible boot-test suite (Makefile-driven, no manual docker) - **`boot-%`** -- mode-aware (`SIGN` / `SIGN_ARGS` / `MANIFEST` / `FALLBACK` / `ARGS`), serves a staging dir so payload, signed args, and a signed manifest are all served uniformly at `http://SERVE_HOST/<file>`. - **`smoke-boot-%`** -- boots every admission mode `stage0 -> test-payload` and asserts the payload actually chain-loaded (fetch -> admit -> PCR-measure -> chain-load), with a per-mode PASS/FAIL summary. - **Fast by default**: the test payload powers off at the end instead of returning to stage0 (so stage0's ~90s fail-closed drain is never hit), and skips its EC2-only ~60s serial-flush hold when its LoadOptions carry `--nosleep` (a runtime flag the harness passes -- no separate build; a real EC2 deploy keeps the full hold). The whole matrix runs in ~2 min. ## Verification - Compiles clean for `x86_64-unknown-uefi` via `make`. - `make smoke-boot-x86_64`: **all 5 modes PASS** (sha256, ed25519 sig, signed args, **signed manifest**, mirror fallback) in ~2 min. Rebuilt from `main` as a purely additive change; **supersedes #5**. Pairs with lockboot/stage1#16. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of the fix for signed-mode malleability (#2). stage1 side follows in lockboot/stage1.
What
ed25519 signed mode used to pin a release key inline and sign each artifact separately (
sig_urlfor the payload,args_url/args_sig_urlfor args). A hostile mirror (metadata is trusted; the payload/args/sig URLs are not) could mix-and-match validly-signed payload-from-A with args-from-B, or roll back to an old signed payload.Now the arch entry pins
{ ed25519 pubkey, manifest_url }. stage0 fetches a signed manifest{ url, sha256, args, version }, verifies its detached signature against the pinned key, then admits the payload by the manifest's exactsha256. Binding the payload + args under one signature closes mix-and-match; the release still rolls forward by re-signing a new manifest (user-data unchanged).config.rs: dropsig_url/args_url/args_sig_url; addmanifest_url/manifest_sig_url+ aManifesttype.validate()enforces the per-mode shape.main.rs:admit_payloaddispatches by mode — sha256 downloads from the inlineurl; ed25519 fetches+verifies the manifest (manifest_sig_url,{sha256}→ manifest hash, else<manifest_url>.sig) then admits bymanifest.sha256.fetch_signed_argsremoved.Makefile: the test payload is no longer self-signed; a signedmanifest.jsonis built + served, andboot-%serves a directory.Verification
make boot-x86_64→ manifest fetched + signature verified, payload downloaded from the manifest's URL, hash matched, PCR 14 extended, payload ran.manifest signature invalid→SECURITY_VIOLATION→ fail-closed power-off, payload never ran.Deferred (tracked on #2)
versionfield is carried now; TPM-NV floor later).🤖 Generated with Claude Code