stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2) - #5

Closed
HarryR wants to merge 1 commit into
mainfrom
manifest-admission
Closed

stage0: admit _stage1 via a signed manifest (fix mix-and-match, #2)#5
HarryR wants to merge 1 commit into
mainfrom
manifest-admission

Conversation

@HarryR

Copy link
Copy Markdown
Contributor

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_url for the payload, args_url/args_sig_url for 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 exact sha256. 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: drop sig_url/args_url/args_sig_url; add manifest_url/manifest_sig_url + a Manifest type. 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. fetch_signed_args removed.
  • Makefile: the test payload is no longer self-signed; a signed manifest.json is built + served, and boot-% 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.
  • Negative (direct harness run with a byte-flipped manifest sig) → manifest signature invalidSECURITY_VIOLATION → fail-closed power-off, payload never ran.

Deferred (tracked on #2)

  • Anti-rollback enforcement (a version field is carried now; TPM-NV floor later).
  • Domain-separated (namespaced) signatures.

🤖 Generated with Claude Code

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>
@HarryRHarryR closed this 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)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR