Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ __pycache__
old
.docker
user-data.json
user-data.stage0.json
# Ignore stage0 build outputs (x86_64/, aarch64/) and the release key (keys/),
# but keep tools/build-stage0/build.sh tracked.
tools/build-stage0/*/
.bashrc
.lesshst
stage0-trace.*
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[workspace]
members = ["crates/stage1", "crates/example-stage2"]
# stage0 is a UEFI (no_std, *-unknown-uefi) application and cannot be built for
# the musl target this workspace defaults to. It is its own workspace so that
# `cargo build --all` (used to build the Linux stages) does not try to compile
# it for the host/musl target.
exclude = ["crates/stage0", "crates/stage0-test-payload"]
resolver = "2"

[workspace.package]
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ RUN apt-get -qq update && \
gzip \
kmod \
make \
openssl \
ovmf \
python3 \
python3-pip \
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update && \
dnsmasq \
swtpm swtpm-tools \
tpm2-tools xxd \
tcpdump \
gh jq python3-venv

RUN groupadd -g 1000 vscode-dc && useradd -u 1000 -g 1000 -d /src -s /bin/bash vscode-dc
Expand Down
82 changes: 81 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.PRECIOUS: tools/build-uki/keys/% tools/build-uki/%
.PRECIOUS: tools/build-uki/keys/% tools/build-uki/% \
tools/build-stage0/%/stage0.efi tools/build-stage0/%/payload.efi tools/build-stage0/%/boot.disk

all: build

Expand All @@ -23,6 +24,7 @@ tools/build-uki/keys/%:
clean:
rm -rf tools/build-uki/x86_64/boot.disk tools/build-uki/x86_64/stage1 tools/build-uki/x86_64/tmp tools/build-uki/x86_64/*.img tools/build-uki/x86_64/*.efi tools/build-uki/x86_64/config-* tools/build-uki/x86_64/efi-vars.ovmf
rm -rf tools/build-uki/aarch64/boot.disk tools/build-uki/aarch64/stage1 tools/build-uki/aarch64/tmp tools/build-uki/aarch64/*.img tools/build-uki/aarch64/*.efi tools/build-uki/aarch64/config-* tools/build-uki/aarch64/efi-vars.ovmf
rm -rf tools/build-stage0/x86_64 tools/build-stage0/aarch64

distclean: clean
$(MAKE) -C tools/build-uki clean
Expand Down Expand Up @@ -149,6 +151,84 @@ tools/build-uki/%/stage1: docker-build-base
cp target/$*-unknown-linux-musl/release/stage1 $@


#####################################################################
# stage0 (pure-UEFI network bootloader)

STAGE0_DIR = crates/stage0

# Guard the arch-less forms: without these, `make boot-stage0` would match the
# generic `boot-%` pattern (stem "stage0") and try to build a UKI for a bogus
# architecture named "stage0". Require an explicit arch suffix instead.
.PHONY: stage0 boot-stage0 test-stage0
stage0 boot-stage0 test-stage0:
@echo "'$@' needs an architecture suffix, e.g. 'make $@-x86_64' or 'make $@-aarch64'." >&2
@exit 2

# Build the stage0 UEFI binary inside the build container. Same model as stage1:
# cargo runs in the container (never the host) and vaportpm is pulled from git,
# so only this repo is mounted.
tools/build-stage0/%/stage0.efi: docker-build-base
mkdir -p tools/build-stage0/$*
$(DOCKER_RUN) -e ARCH=$* $(DOCKER_SAMEUSER) $(BUILD_IMAGE) \
bash -c "rustup target add $*-unknown-uefi && cargo build --release --manifest-path $(STAGE0_DIR)/Cargo.toml --target $*-unknown-uefi"
cp $(STAGE0_DIR)/target/$*-unknown-uefi/release/stage0.efi $@

# Assemble + sign the stage0 boot disk (losetup/mount -> privileged container).
tools/build-stage0/%/boot.disk: tools/build-stage0/%/stage0.efi tools/build-uki/keys/db.crt
$(DOCKER_RUN) -e ARCH=$* $(BUILD_IMAGE) ./tools/build-stage0/build.sh

stage0-amd64 stage0-x86_64: tools/build-stage0/x86_64/boot.disk
stage0-arm64 stage0-aarch64: tools/build-stage0/aarch64/boot.disk

# Boot stage0 under QEMU. Pass PAYLOAD=path/to/payload.efi (repo-relative) to
# serve a local UEFI payload at http://10.0.2.1:8000/payload.efi; otherwise
# point user-data.stage0.json at any URL reachable from the guest.
# Set TRACE=1 to capture the guest TCP conversation to stage0-trace.txt (needs
# the dev image rebuilt for tcpdump: 'make docker-build-dev').
boot-stage0-%: tools/qemu-test/ec2-metadata-mock-linux-amd64 tools/build-stage0/%/boot.disk user-data.stage0.json
$(DOCKER_RUN) $(DOCKER_OPT_KVM) \
-e YES_INSIDE_DOCKER_DO_DANGEROUS_IPTABLES=1 --cap-add=NET_ADMIN --device=/dev/net/tun \
$(DEV_IMAGE) ./tools/qemu-test/boot.sh --kind stage0 --arch $* $(if $(PAYLOAD),--payload $(PAYLOAD)) $(if $(TRACE),--trace)

# Long-term ed25519 release signing key for stage0 "signed mode". This is the
# vendor key that signs payloads; it never touches a deployed machine — stage0
# only ever sees the *public* key, pinned in the metadata doc. Generated once in
# the build container (gitignored). release.pub.b64 is the raw 32-byte public
# key, base64-encoded, ready to drop straight into the _stage0 `ed25519` field.
tools/build-stage0/keys/release.pem: docker-build-base
mkdir -p tools/build-stage0/keys
$(DOCKER_RUN) $(DOCKER_SAMEUSER) $(BUILD_IMAGE) bash -c "\
openssl genpkey -algorithm ed25519 -out tools/build-stage0/keys/release.pem && \
openssl pkey -in tools/build-stage0/keys/release.pem -pubout -outform DER \
| tail -c 32 | base64 -w0 > tools/build-stage0/keys/release.pub.b64"

# Build the end-to-end test payload (a chain-loaded UEFI app that reads PCRs) and
# attach a detached ed25519 signature (payload.efi.sig) made with the release
# key. The payload is NOT Secure Boot db-signed: stage0 verifies the signature
# against the pinned pubkey and loads it via a FileAuthentication override.
# Hostname (not an IP literal) so the end-to-end test also exercises EFI_DNS4;
# boot.sh maps payload.lockboot.test -> 10.0.2.1 in the QEMU DNS. Override with
# PAYLOAD_URL=http://10.0.2.1:8000/payload.efi to skip DNS.
PAYLOAD_URL ?= http://payload.lockboot.test:8000/payload.efi
tools/build-stage0/%/payload.efi: docker-build-base tools/build-stage0/keys/release.pem
mkdir -p tools/build-stage0/$*
$(DOCKER_RUN) -e ARCH=$* $(DOCKER_SAMEUSER) $(BUILD_IMAGE) \
bash -c "rustup target add $*-unknown-uefi && \
cargo build --release --manifest-path crates/stage0-test-payload/Cargo.toml --target $*-unknown-uefi && \
cp crates/stage0-test-payload/target/$*-unknown-uefi/release/stage0-test-payload.efi $@ && \
openssl pkeyutl -sign -inkey tools/build-stage0/keys/release.pem -rawin -in $@ -out $@.sig"

# One-shot end-to-end test: build + sign the payload, pin the release pubkey into
# a _stage0 user-data doc (signed mode), then boot stage0 serving the payload
# and its detached .sig locally over HTTP.
test-stage0-%: tools/build-stage0/%/payload.efi tools/build-stage0/%/boot.disk tools/qemu-test/ec2-metadata-mock-linux-amd64
@PUB=$$(cat tools/build-stage0/keys/release.pub.b64); \
printf '{\n "_stage0": {\n "%s": { "url": "%s", "ed25519": "%s" }\n }\n}\n' \
"$*" "$(PAYLOAD_URL)" "$$PUB" > user-data.stage0.json; \
echo "Wrote user-data.stage0.json (signed mode, release pubkey $$PUB)"
$(MAKE) boot-stage0-$* PAYLOAD=tools/build-stage0/$*/payload.efi TRACE=$(TRACE)


#####################################################################

# Git tagging helpers
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ You can run any statically linked Linux ELF, but the minimal filesystem only has

## Components

- **[stage0](crates/stage0/README.md)**: Kernel-less UEFI netboot loader (downloads + measures + chain-loads a UEFI payload)
- **[stage1](crates/stage1/README.md)**: Secure bootloader (fetches config, verifies binaries, extends PCRs)
- **[example-stage2](crates/example-stage2/README.md)**: Example user application
- **[vaportpm](https://github.com/lockboot/vaportpm)**: TPM 2.0 attestation library (external dependency)
Expand Down
Loading
Loading