stage1: admit _stage2 via a signed manifest; merge it into stdin (#2)#15
Closed
HarryR wants to merge 1 commit into
Closed
stage1: admit _stage2 via a signed manifest; merge it into stdin (#2)#15HarryR wants to merge 1 commit into
HarryR wants to merge 1 commit into
Conversation
…doc (#2) Companion to the stage0 change. ed25519 signed mode now pins { ed25519 pubkey, manifest_url } in the (trusted) user-data instead of signing the payload + args separately. stage1 fetches a signed manifest ({ url, sha256, args, version }), verifies it against the pinned key, admits the payload by the manifest's exact sha256, and takes argv from the manifest's args. Binding payload + args under one signature closes the mix-and-match / downgrade hole a hostile mirror could exploit; the release still rolls forward by re-signing a new manifest. The verified manifest is merged additively into _stage2.<arch> of the received user-data, and THAT merged doc is what the payload gets on stdin (sha256 mode passes the doc through unchanged); top-level operator keys pass through. So stage2 sees resolved, release-signed pins/args alongside operator config. - metadata: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url plus a Manifest type; url/sha256 are the sha256-mode fields. validate(profile) enforces the per-mode shape. 14 unit tests. - stage1/main.rs: admit_payload dispatches by mode; fetch_manifest (manifest_sig_url {sha256} -> manifest hash, else <manifest_url>.sig); build_stdin_config merges the manifest for stdin. Drop fetch_signed_args. - deploy: ed25519 mode emits <payload>.manifest.json (+ .sig) and pins { ed25519, manifest_url }; args fold into the manifest; --version added; --sign-args removed. New ed25519 round-trip test. - Makefile: build+sign manifests in the chain recipe; smoke-args runs in signed mode. Verified end-to-end (QEMU): full chain boots in sha256 and ed25519 modes; smoke-args shows the manifest's args reaching argv; the merged doc appears on stage2's stdin; a corrupted stage2 manifest signature is rejected (fail-closed, payload never runs). Workspace tests pass. 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>
HarryR
added a commit
that referenced
this pull request
Jul 8, 2026
… resolution (#16) ## What Reworks `_stage2` admission so each per-arch entry is a **discriminated union**, exactly one of: - **`payload`** -- admit a binary now: `sha256` pin or `ed25519`-signed (`sig_url`), with inline `args` or signed remote `args_url`. All the flexible fields from `main` are kept, just nested under `payload`. - **`manifest`** -- fetch a signed manifest (itself a `_stage2` fragment) from `url`, verify its detached signature (`sig_url`, else `<url>.sig`) against the pinned `ed25519` key, **deep-merge the whole document into the received user-data at the top level** (manifest wins on conflict), and re-evaluate the entry. Resolution is a **loop**: a manifest resolves to a `payload`, or delegates to a fresh `manifest` (per-hop key delegation), until a payload is reached. A repeated `(url, sha256)` is a cycle and fails closed. Every hop is recorded in the arch entry's `resolved_manifests` (verifier-authoritative, carrying the resolved hash), and the merged doc is what stage2 receives on stdin. Binding the payload and its args under **one** manifest signature closes the mix-and-match / rollback malleability in ed25519 mode (lockboot/stage0#2), while keeping the existing direct `url`/`sha256`/`ed25519`/`sig_url`/`args_url` paths intact. ## Changes - **`metadata`**: `Entry` / `Payload` / `ManifestRef` types (externally-tagged union via `#[serde(flatten)]`), `Payload::admission` / `ManifestRef::validate`. `ManifestRef.sha256` optionally pins the manifest's own bytes. - **`stage1`**: the resolution loop (replacing the single admit in `stage2()`), `deep_merge`, `fetch_manifest`; the merged doc is serialized to stage2's stdin (sha256-only passes the received bytes through unchanged). - **`deploy`**: `--manifest` emits a signed manifest fragment per payload and pins `{"manifest":{url,ed25519}}`; direct sha256 / ed25519 / `--sign-args` modes unchanged. - **`Makefile`**: `MANIFEST=1` chain-test mode. **README** + **JSON schema** updated. ## Verification - `cargo test --workspace`: 25 tests (union round-trip incl. the serde-flatten case, `deep_merge` top-level union + manifest-wins, cycle detection, `resolved_manifests` provenance, deploy manifest round-trip). - `make test-chain-x86_64` across all modes under QEMU: sha256, ed25519 detached-sig, **signed manifest (`SIGN=1 MANIFEST=1`)**, signed remote args, inline args, mirror fallback -- all boot `stage0 -> UKI -> stage1 -> stage2`; the manifest run shows the merged `_stage2` + `resolved_manifests` on stage2's stdin. - `lockboot-deploy` exercised end-to-end (create sha256/ed25519/manifest, validate, modify). Rebuilt from `main` as a purely additive change; **supersedes #15** (which had deleted the flexible ed25519 paths). Pairs with lockboot/stage0 `manifest-resolve`. 🤖 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 2 of the signed-mode malleability fix (lockboot/stage0#2); pairs with lockboot/stage0#5 (which must land first — the chain test boots against the updated stage0).
What
ed25519 signed mode used to pin a release key inline and sign the payload and args separately (
sig_url/args_url), so a hostile mirror (metadata trusted; mirrors not) could mix-and-match or roll back validly-signed pieces. Now_stage2.<arch>pins{ ed25519 pubkey, manifest_url }; stage1 fetches a signed manifest{ url, sha256, args, version }, verifies it against the pinned key, admits the payload bymanifest.sha256, and takes argv frommanifest.args. One signature binds payload + args; the release still rolls forward by re-signing a manifest.Merge → stdin. The verified manifest is merged additively into
_stage2.<arch>of the received user-data, and that merged doc is what the payload gets on stdin (sha256 mode passes the doc through unchanged). Top-level operator keys pass through, so stage2 sees resolved release-signed pins/args and operator config.metadata: dropsig_url/args_url/args_sig_url; addmanifest_url/manifest_sig_url+ aManifesttype;url/sha256are the sha256-mode fields.validate(profile)enforces the per-mode shape. 14 unit tests.stage1/main.rs:admit_payloaddispatches by mode;fetch_manifest(manifest_sig_url{sha256}→ manifest hash, else<manifest_url>.sig);build_stdin_configdoes the merge.fetch_signed_argsremoved.deploy: ed25519 mode emits<payload>.manifest.json(+.sig) and pins{ ed25519, manifest_url };--argsfold into the manifest;--versionadded;--sign-argsremoved. New ed25519 round-trip test.Makefile: manifests built + signed in the chain recipe;smoke-argsnow runs in signed mode.Verification (QEMU, full chain)
make test-chain-x86_64 SIGN=1→ stage0 + stage1 both admit via manifest; stage2 runs; stdin shows the merged_stage2.<arch>(pointer + splicedurl/sha256/args/version),_stage1left as a pointer.make test-chain-x86_64(sha256) → boots; stdin is the raw doc (no merge).make smoke-args-x86_64→arg[1]: --smoke,arg[2]: hello world(space preserved) from the signed manifest.manifest verification failed→ fail-closed, payload never runs (UKI still admitted, proving isolation).cargo testpasses; no warnings.Deferred (tracked on #2)
versionfield is carried now; TPM-NV floor later).🤖 Generated with Claude Code