Domain-separated signatures + standalone stage0-sign signer#8
Merged
Conversation
Every ed25519 signature stage0 admits was over raw payload bytes with no role tag, so a signature minted for one context was structurally valid in another wherever the bytes were accepted (a signed-args blob replayed as a payload signature, a _stage1 manifest as anything else). Bind each signature to its exact role by signing a fixed 64-byte preimage sha256(domain_tag) || sha256(message). sig.rs gains a Domain enum (stage1.uki / stage1.args / stage1.manifest, the three roles stage0 verifies) with a tag() namespace of the form lockboot.v1.stage1.<kind>; verify() now takes a Domain and checks the preimage. The three admission sites pass their role. Stop signing with openssl in the test Makefile and dogfood a new standalone host-side signer, crates/stage0-sign (keygen + domain-separated sign), so the repo builds and tests without stage1's deploy tool. Its framing is duplicated from sig.rs and pinned byte-for-byte to stage1's signer by a shared golden known-answer test (make sign-test). Randomness for keygen is /dev/urandom via std (no getrandom/libc), and it builds for the musl target so the image's global static RUSTFLAGS do not break proc-macro compilation. The signer var is STAGE0_SIGN, not SIGN, to avoid clobbering by the boot matrix's SIGN=1 flag. make smoke-boot-x86_64 passes 5/5; the full chain (stage0 admits the UKI that stage1's deploy signs with stage1.uki) verifies against the rebuilt boot.disk. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…USTFLAGS The Dockerfile baked a global ENV RUSTFLAGS applying the static-link flags to every target uniformly, including the host, where +crt-static makes proc-macro dylibs impossible to build (a standalone host-gnu build fails "cannot produce proc-macro"). Express the flags per target in .cargo/config.toml instead, so the host is rust-lld only and never force-static. stage0 had no .cargo/config.toml at all and relied entirely on the global env (and, locally, the shared workspace config). Since CI checks this repo out alone with CARGO_HOME redirected to an empty dir, it must be self-sufficient: add a config with the musl targets (stage0-sign), the uefi targets (the measured bootloader + payloads), and a gnu host section (rust-lld, no crt-static). The uefi flags reproduce the measured binaries byte-for-byte: stage0.efi, ena.efi and payload.efi hash identically before and after this change, so PCR4 and PCR14 are unchanged. sign-test (stage0-sign KAT) and smoke-boot 5/5 pass, and the full chain still verifies the UKI (also byte-identical). Pairs with the shared workspace config dropping its now-redundant musl rustflags (cargo concatenates rustflags across config files, so keeping them in both the workspace and this repo would double them and change the compiled bytes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Release-prep hygiene over the domain-separation + rustflags changes, no logic: - Makefile: the stage0-sign musl-target comment still explained itself by the global RUSTFLAGS this branch removed (now inaccurate); state the real reason (a fully static host binary). - Dockerfile.build: collapse the 3-line comment narrating the removed ENV RUSTFLAGS to a one-line statement of the rule. - .cargo/config.toml: drop the "rather than a global RUSTFLAGS" migration framing. - README: drop the "not openssl" aside. - sig.rs / stage0-sign: the role names are stage1.* (the domain tags), not _stage1.* (which is the JSON metadata key) -- fix the two doc comments. Kept the openssl-genpkey PEM-format note: it documents a live interop property (the key is openssl-compatible), not a removed dependency. stage0.efi hashes identically and sign-test passes (comment-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR
added a commit
to lockboot/stage1
that referenced
this pull request
Jul 9, 2026
## What
Bind every ed25519 signature in the chain to its exact role. Signatures
are now over a fixed 64-byte preimage `sha256(domain_tag) ||
sha256(message)`, where the tag names one of the six contexts (2 hops x
{payload, args, manifest}): `lockboot.v1.stage1.uki` / `.stage1.args` /
`.stage1.manifest` / `.stage2.payload` / `.stage2.args` /
`.stage2.manifest`. A signature minted for one context is structurally
invalid in every other.
Alongside: stop signing with `openssl` in the test Makefile and dogfood
the real `deploy` signer, folding in key generation.
## Changes
- **`crates/ed25519-sign`**: `Domain` enum (all six roles) + `tag()`;
`sign()`/`verify()` take a `Domain` and operate on the preimage (callers
still pass the raw message). `pem_from_seed` / `pubkey_b64_from_seed`
promoted to `pub`. Tests: domain-separation rejection + a **golden
known-answer vector** stage0's verifier pins against.
- **`crates/stage1`, `crates/mkuki`**: verify/sign sites pass their
role.
- **`crates/deploy`**: new `keygen` (random ed25519 key via
`/dev/urandom`, no `getrandom`/`libc`) and low-level `sign --domain`;
`create` threads the right `Domain` into each artifact.
- **`Makefile`**: release key + every signature now via
`lockboot-deploy` (no `openssl`).
## Verification
- `make test-chain-x86_64 SIGN=1` / `SIGN=1 MANIFEST=1` / `SIGN=1
SIGN_ARGS=1` all pass against the domain-aware stage0 harness: both hops
verify and all six domains round-trip across the repo boundary.
Pairs with lockboot/stage0#8; the shared golden vector guarantees both
repos' framing agree.
🤖 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.
What
Bind every ed25519 signature stage0 admits to its exact role. Signatures are now over a fixed 64-byte preimage
sha256(domain_tag) || sha256(message), where the tag is one oflockboot.v1.stage1.uki/.stage1.args/.stage1.manifest. A signature minted for one role is structurally invalid in any other, closing the last of the mix-and-match malleability (the deferred tail of #2).Alongside: stop signing with
opensslin the test Makefile and dogfood a new standalone host-side signer.Changes
crates/stage0/src/sig.rs:Domainenum +tag()namespace;verify()takes aDomainand checks the preimage.crates/stage0/src/main.rs: the three admission sites pass their role (stage1.ukipayload,stage1.args,stage1.manifest).crates/stage0-sign(new): standalone host CLI,keygen+sign --domain. Randomness is/dev/urandomviastd(nogetrandom/libc/C toolchain); built for musl so the image's global static RUSTFLAGS don't break proc-macro compilation. Framing duplicated fromsig.rsand pinned byte-for-byte to stage1's signer by a golden known-answer test.Makefile:keygen+ all three sign sites now callstage0-sign(noopenssl). Signer var isSTAGE0_SIGN, notSIGN, to avoid clobbering by the boot matrix'sSIGN=1flag. Newmake sign-bin/make sign-test.Verification
make sign-test(golden KAT) passes.make smoke-boot-x86_64passes 5/5 (sha256, sign, sign_args, manifest, fallback).deploysigns withstage1.uki, verified against the rebuilt boot.disk.Pairs with lockboot/stage1's domain-separation PR; the shared golden vector guarantees both repos' framing agree.
🤖 Generated with Claude Code