Skip to content
Draft
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
2 changes: 1 addition & 1 deletion doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ The CI pipeline's workspace and cache behavior is documented in

- **No network at boot** — all verification is local; no certificate authorities
- **Hardware root of trust** — the coreboot bootblock (IBB) is the Static Core Root of Trust for Measurement (S-CRTM): the first code executed by the CPU, directly from SPI flash. Coreboot implements a transitive measurement chain: the CRTM measures FMAP and the bootblock image into the preram log, then each subsequent stage measures the next before executing it — bootblock measures romstage, romstage measures ramstage, ramstage measures the Heads payload. Measurements are taken during CBFS file loading, before decompression, and are recorded in TPM PCR 2 (SRTM) once the TPM hardware is initialized (`tpm_setup()`). Measurements taken before TPM init are cached in the preram log and flushed to PCR 2 by `tspi_measure_cache_to_pcr()` during `tpm_setup()`. The full chain — bootblock → romstage → ramstage → Heads Linux kernel + initrd — is recorded into PCR 2. PCRs 0, 1, and 3 remain zero as policy anchors. See [tpm.md](tpm.md#srtm-in-coreboot) for TPM init timing per board. See [wp-notes.md](wp-notes.md#pr0-chipset-locking) for SPI write-protection and PR0 chipset locking details.
- **Fail-closed** — failed integrity verification drops to a recovery shell. Recovery shell authentication via GPG smartcard is enforced when GPG key backup has been configured (`CONFIG_HAVE_GPG_KEY_BACKUP=y`), which is set by answering "y" to `"Would you like to format an encrypted USB Thumb drive to store GPG key material? (Required to enable GPG authentication)"` during OEM Factory Reset / Re-Ownership. Otherwise the recovery shell is unauthenticated. An "Ignore tampering and force a boot (Unsafe!)" option is available to override this.
- **Fail-closed** — failed integrity verification drops to a recovery shell. Recovery shell authentication via GPG smartcard is enforced when GPG key backup has been configured (`CONFIG_HAVE_GPG_KEY_BACKUP=y`), which is set by answering "y" to `"Would you like to format an encrypted USB Thumb drive to store GPG key material? (Required to enable GPG authentication)"` during OEM Factory Reset / Re-Ownership, or by running "Reprovision smartcard from GPG key backup" from the GPG Management Menu. Otherwise the recovery shell is unauthenticated. An "Ignore tampering and force a boot (Unsafe!)" option is available to override this. See [recovery-shell.md](recovery-shell.md#authentication) for details.
- **Separation of duties** — the public key that verifies `/boot` signatures is stored in CBFS (ROM). The private key that signs `/boot` stays on a USB security dongle and never leaves it.
- **Auditability** — all source is open, builds are reproducible, ROM images are verifiable

Expand Down
20 changes: 20 additions & 0 deletions doc/configuring-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ If you already have a provisioned USB Security dongle:
3. Follow the steps. After reflashing, reboot.
4. Generate a new TOTP/HOTP secret when prompted.

## Restoring Keys from Backup

If you chose the in-memory backup path during OEM factory reset and your
dongle is lost, broken, or wiped:

1. Insert the backup USB thumb drive and your (new) dongle.
2. Go to `Options -> GPG Management -> Reprovision smartcard from GPG key backup`.
3. Enter the backup passphrase when prompted.
4. Heads detects the key type from the backup, factory-resets the dongle,
restores the subkeys, and sets the card identity.
5. After success, flash the public key to the running BIOS for persistence.
6. Re-sign /boot via `Options -> Update checksums and sign all files in /boot`.

This requires the LUKS-encrypted backup USB drive created during OEM factory
reset (answer Y to "format an encrypted USB Thumb drive"). Without it, run
a new OEM Factory Reset / Re-Ownership to rekey the device.

After reprovisioning, the recovery shell and USB boot will require GPG
smartcard authentication; see [recovery-shell.md](recovery-shell.md#authentication).

## Forgotten GPG User PIN

From Recovery Shell with the dongle inserted:
Expand Down
12 changes: 12 additions & 0 deletions doc/gpg.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ public key into the Heads firmware:
3. From Heads: `Options -> GPG Management -> Add a GPG key to the running BIOS + reflash`.
4. Reboot. Generate a new TOTP/HOTP secret when prompted.

## Restoring Keys from Backup

See [configuring-keys.md](configuring-keys.md#restoring-keys-from-backup) for
the full recovery flow. In short:

1. Insert the backup USB thumb drive and the replacement dongle.
2. From Heads: `Options -> GPG Management -> Reprovision smartcard from GPG key backup`.
3. Enter the backup passphrase.

The backup drive must have been created by the OEM factory reset flow with
the "format an encrypted USB Thumb drive" option set to Y.

## Nitrokey 3 Specifics

- Supports NIST P-256 ECC keys in addition to RSA — significantly faster key
Expand Down
25 changes: 25 additions & 0 deletions doc/qemu.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,31 @@ How I tested these wrappers (smoke checks)
- Minimal: `source docker/common.sh && build_docker_opts` — should print a short description and show flags such as `--device=/dev/kvm` when KVM is available and `-v /tmp/heads-docker-xauth-XXXXXX:...` (or `-v /tmp/.docker.xauth-<uid>:...` as fallback) when Xauthority was created.
- Functional (examples tested by PR author): see the tests in the PR body (Ubuntu, Debian, Fedora installer flows). Consider testing `./docker_repro.sh make BOARD=qemu-coreboot-fbwhiptail-tpm2 run` locally to verify KVM+GTK behavior.

Resetting state
---

QEMU boards using the default virtual token persist canokey and TPM state
between runs. To simulate a fresh dongle and TPM for testing:

```bash
# Wipe the virtual Canokey (new dongle, no keys on card).
sudo rm -f build/x86/<BOARD>/.canokey-file

# Wipe the virtual TPM (new TPM, no sealed secrets or counters).
sudo rm -rf build/x86/<BOARD>/vtpm/
```

The next `make run` will create fresh `.canokey-file` and `vtpm/`
directories automatically. The Heads setup wizard will then offer OEM
factory reset (F) or reprovision from backup (K).

To preserve canokey state for reuse:

```bash
cp build/x86/<BOARD>/.canokey-file ~/Qemu_img/.canokey-file.bak
cp ~/Qemu_img/.canokey-file.bak build/x86/<BOARD>/.canokey-file
```

Troubleshooting
---

Expand Down
105 changes: 105 additions & 0 deletions doc/recovery-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,111 @@ The Recovery Shell boots with PCR 4 set to `recovery` instead of
- TOTP/HOTP sealing and TPM Disk Unlock Key creation/unsealing do not work.
- To perform seal/unseal operations return to the normal GUI boot.

## Authentication

`gpg_auth()` in `initrd/etc/functions.sh` guards the recovery shell and
external media boot entry. On boards with `CONFIG_HAVE_GPG_KEY_BACKUP=y`,
recovery calls `gpg_auth()` before opening the bash prompt, and
`media-scan.sh` (called from `usb-init.sh`) also invokes `gpg_auth` before
scanning USB devices. TPM operations, flash/update, GPG management, and
all other GUI menu functions are NOT gated by this check — they remain
accessible from the main menu.

After OEM Factory Reset / Re-Ownership or the reprovision flow,
`CONFIG_HAVE_GPG_KEY_BACKUP=y` is persisted in the user config stored in
CBFS, so that recovery shell and USB boot authentication is enforced
even on boards where the compile-time default differs.

To enable `gpg_auth` during OEM Factory Reset / Re-Ownership, answer Y to
"format an encrypted USB Thumb drive to store GPG key material? (Required
to enable GPG authentication)". Choosing N skips the backup drive creation
and leaves `gpg_auth` disabled (recovery shell and USB boot remain
unauthenticated).

**What `gpg_auth` guards — and what it does not:**

`gpg_auth()` is called from exactly two places: the recovery shell entry
point (`recovery()` in `functions.sh`) and the USB/external media boot
scanner (`media-scan.sh`, called from `usb-init.sh`). The normal GUI boot
flow (`gui-init.sh`) does NOT invoke `gpg_auth()` — it handles GPG key
presence through its own `check_gpg_key` mechanism, which is separate from
`gpg_auth`.

Many destructive operations are already available from the GUI without
`gpg_auth`: the user can reflash firmware (`Options -> Flash/Update`),
wipe GPG keys and config (`Options -> Clear GPG key(s) and reset all user
settings`), reset the TPM, re-sign boot hash manifests
(`Options -> Update checksums and sign all files in /boot`),
and generate new GPG keys. All of these are tamper-evident — the TPM
PCR measurements and HOTP/TOTP codes will detect changes.

What `gpg_auth` prevents:

- **Recovery shell entry without authentication.** The recovery shell
gives unrestricted root access to raw block devices, SPI flash, TPM
commands, and GPG key operations. The GUI does not expose
`flash.sh -r` (dump running firmware). An attacker with recovery
shell access could dump the full SPI flash for offline analysis, forge
a malicious firmware image, and flash it back undetected. `gpg_auth`
blocks this path. (Note: an attacker with physical access can still
extract the SPI flash via an external programmer after disassembly —
`gpg_auth` only protects the in-software path through the running
system.)

- **USB/external media boot from an untrusted drive.** `media-scan.sh`
(called from `usb-init.sh`) invokes `gpg_auth` before scanning USB
devices. When `CONFIG_HAVE_GPG_KEY_BACKUP` is not set, `gpg_auth`
is a no-op and USB boot proceeds without restriction. When the flag
is set (after OEM factory reset or reprovision), the user must
authenticate with their GPG smartcard before external media boot is
allowed. (Note: the boot process locks the SPI flash controller,
making Heads the only internal flasher. An attacker booting from
USB cannot reflash the firmware even with root access. External
SPI flashing via hardware programmer after disassembly is always
possible regardless — see [wp-notes.md](wp-notes.md).)

**What `gpg_auth` does NOT prevent:**

- Reflashing firmware through the GUI
- Wiping GPG keys, config, or TPM state through the GUI
- Re-signing `/boot` through the GUI
- Any operation the user can perform from the main menu

**How it works:** `gpg_auth()` generates a random nonce, the user signs it
with their GPG key (smartcard or backup USB drive) within 3 attempts, and the
signature is verified against the ROM-fused public keyring. On failure,
`DIE` exits the session. With `CONFIG_RESTRICTED_BOOT=y`, the shell is
blocked entirely and the system reboots after 5 seconds.

**Without authentication:** If the board does not have
`CONFIG_HAVE_GPG_KEY_BACKUP` set, `gpg_auth()` is a no-op and the recovery
shell opens without prompting.

## Resetting Configuration

`Options -> Change configuration settings -> 'r'` (`Clear GPG key(s) and reset
all user settings`) wipes the running system configuration:

- Clears `~/.gnupg` (GPG keyring, trustdb)
- Deletes `/boot/kexec*.txt` and `/boot/kexec.sig` (boot hash manifests,
checksums, rollback counter, signatures)
- Removes all `heads/` files from CBFS (keyring, trustdb, config.user)
- Reflashes the cleaned firmware
- Resets the TPM if present

**Attestation impact:** Removing `heads/` files from CBFS changes the SPI flash
contents measured into PCR 7 by `cbfs-init` at next boot. Combined with the
TPM reset, TOTP/HOTP unseal will fail on the next boot — Heads shows the
standard red-menu TOTP error prompt.

**Recovery after wipe:** Run OEM Factory Reset / Re-Ownership (Options -> 'F')
to fully reprovision ([configuring-keys.md](configuring-keys.md)). If you have a
GPG key backup USB drive from a previous in-memory OEM reset, use
`GPG Options -> 'k' Reprovision USB Security dongle from GPG key backup`
to restore subkeys from the backup ([configuring-keys.md#restoring-keys-from-backup](configuring-keys.md#restoring-keys-from-backup)),
then flash the public key to ROM, re-sign /boot, and generate new TOTP/HOTP
secrets.

## Common Operations

### Manual boot
Expand Down
6 changes: 5 additions & 1 deletion initrd/bin/gpg-gui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ TRACE_FUNC
while true; do
unset menu_choice
whiptail_type $BG_COLOR_MAIN_MENU --title "GPG Management Menu" \
--menu 'Select the GPG function to perform' 0 80 10 \
--menu 'Select the GPG function to perform' 0 80 11 \
'r' ' Add GPG key to running BIOS and reflash' \
'a' ' Add GPG key to standalone BIOS image and flash' \
'e' ' Replace GPG key(s) in the current ROM and reflash' \
'l' ' List GPG keys in your keyring' \
'p' ' Export public GPG key to USB drive' \
'g' ' Generate GPG keys manually on a USB security dongle' \
'k' ' Reprovision USB Security dongle from GPG key backup' \
'x' ' Exit' \
2>/tmp/whiptail || recovery "GUI menu failed"

Expand Down Expand Up @@ -64,6 +65,9 @@ while true; do
gpg_post_gen_mgmt
fi
;;
"k")
reprovision_smartcard_from_backup
;;
esac

done
Expand Down
6 changes: 5 additions & 1 deletion initrd/bin/gui-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,10 @@ prompt_missing_gpg_key_action() {
retry_msg="Cannot sign /boot because no private GPG signing key is available ($DONGLE_BRAND not inserted, wiped, or key not set up).\n\nInsert your $DONGLE_BRAND and retry.\n\nHow would you like to proceed?"
fi
whiptail_error --title "ERROR: GPG signing key unavailable" \
--menu "$retry_msg" 0 80 4 \
--menu "$retry_msg" 0 80 5 \
'r' "$retry_label" \
'F' ' OEM Factory Reset / Re-Ownership' \
'K' ' Reprovision USB Security dongle from GPG key backup' \
'm' ' Return to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
Expand All @@ -257,6 +258,9 @@ prompt_missing_gpg_key_action() {
F)
oem-factory-reset.sh
;;
K)
reprovision_smartcard_from_backup
;;
x)
recovery "User requested recovery shell"
;;
Expand Down
12 changes: 10 additions & 2 deletions initrd/bin/mount-usb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ TRACE_FUNC

function usage() {
cat <<USAGE_END
usage: $0 [options...] <--mode [ro|rw]> <--device device> <--mountpoint mountpoint> <--pass passphrase>
usage: $0 [options...] <--mode [ro|rw]> <--device device> <--mountpoint mountpoint> [--pass passphrase|--pass-file /path/to/file]
$0 --help

parameters:
--mode: ro or rw (default ro)
--device: device to mount (default: first USB device found)
--mountpoint: where to mount the device (default: /media)
--pass: passphrase for LUKS device (default: none)
--pass-file: path to file containing passphrase
--help: Show this help
USAGE_END
}
Expand Down Expand Up @@ -56,6 +57,13 @@ while [ $# -gt 0 ]; do
shift
fi
;;
--pass-file)
if [ -n "$2" ] && [ -r "$2" ]; then
PASS="$(cat "$2")"
shift
shift
fi
;;
*)
usage
exit 1
Expand Down
Loading