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
18 changes: 9 additions & 9 deletions doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ execution. Source lives in `initrd/`.

| Subsystem | Key files | Purpose |
| --- | --- | --- |
| Init / boot flow | `initrd/init`, `initrd/bin/gui-init` | System initialization and main GUI loop |
| TPM abstraction | `initrd/bin/tpmr` | Unified TPM 1.2 / TPM 2.0 wrapper |
| Boot signing | `initrd/bin/kexec-sign-config` | GPG-sign /boot files, create checksums |
| Boot verification | `initrd/bin/kexec-select-boot` | Verify checksums, select and kexec the OS |
| LUKS key sealing | `initrd/bin/kexec-seal-key` | Seal disk encryption key to TPM |
| TOTP/HOTP | `initrd/bin/seal-totp`, `seal-hotpkey` | Seal attestation secrets to TPM |
| OEM reset | `initrd/bin/oem-factory-reset` | Full re-ownership: GPG, TPM, TOTP, checksums |
| Init / boot flow | `initrd/init`, `initrd/bin/gui-init.sh` | System initialization and main GUI loop |
| TPM abstraction | `initrd/bin/tpmr.sh` | Unified TPM 1.2 / TPM 2.0 wrapper |
| Boot signing | `initrd/bin/kexec-sign-config.sh` | GPG-sign /boot files, create checksums |
| Boot verification | `initrd/bin/kexec-select-boot.sh` | Verify checksums, select and kexec the OS |
| LUKS key sealing | `initrd/bin/kexec-seal-key.sh` | Seal disk encryption key to TPM |
| TOTP/HOTP | `initrd/bin/seal-totp.sh`, `initrd/bin/seal-hotpkey.sh` | Seal attestation secrets to TPM |
| OEM reset | `initrd/bin/oem-factory-reset.sh` | Full re-ownership: GPG, TPM, TOTP, checksums |
| Config GUI | `initrd/bin/config-gui.sh` | Runtime configuration menus |
| Functions lib | `initrd/etc/functions` | Shared utilities: logging, INPUT, TPM helpers |
| Functions lib | `initrd/etc/functions.sh` | Shared utilities: logging, INPUT, TPM helpers |
| GUI lib | `initrd/etc/gui_functions` | Whiptail wrappers, integrity report |

---
Expand All @@ -74,7 +74,7 @@ Three-layer hierarchy:
2. **`/etc/config.user`** — User overrides extracted from CBFS at runtime
3. **`/tmp/config`** — Combined result, sourced during boot

`combine_configs()` in `initrd/etc/functions` merges these by concatenating
`combine_configs()` in `initrd/etc/functions.sh` merges these by concatenating
`/etc/config*` into `/tmp/config`. User settings in CBFS take precedence
because they appear last in the concatenation.

Expand Down
2 changes: 1 addition & 1 deletion doc/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ These are not intended to be changed in user config.

| Variable | Purpose |
|---|---|
| CONFIG_BOARD | Internal name of the board being built. Avoid testing this for specific boards in initrd/, instead add a customization point and override it with boards/<name>/initrd/bin/<file>. (For example, boards/librem_mini_v2/initrd/bin/board-init.sh.) |
| CONFIG_BOARD | Internal name of the board being built. Avoid testing this for specific boards in initrd/, instead add a customization point and override it with boards/<name>/initrd/bin/<file>.sh. (For example, boards/librem_mini_v2/initrd/bin/board-init.sh.) |
| CONFIG_BOARD_NAME | Display name of the board being built. Use this to show the board name to the user. |
| CONFIG_BRAND_NAME | Brand name to use to refer to the firmware itself. Upstream, this is "Heads". For example, "Heads main menu", "Enable Heads debug tracing", etc. Distributions can override this to their specific brand name (usually in site-local/config). |

Expand Down
38 changes: 19 additions & 19 deletions doc/tpm.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ See also: [architecture.md](architecture.md), [boot-process.md](boot-process.md)

## tpmr — unified TPM abstraction

`initrd/bin/tpmr` is a shell script wrapper that presents a single interface
`initrd/bin/tpmr.sh` is a shell script wrapper that presents a single interface
over both TPM 1.2 (`tpm` / `trousers`) and TPM 2.0 (`tpm2-tools`). All Heads
scripts call `tpmr` rather than invoking `tpm` or `tpm2` directly.
scripts call `tpmr.sh` rather than invoking `tpm` or `tpm2` directly.

### PCR sizes

Expand Down Expand Up @@ -155,11 +155,11 @@ unchanged; the TXT mechanism adds the DRTM capability on top of it.
| 1 | unused | Zero; anchored in sealing policies |
| 2 | coreboot SRTM | Boot block, ROM stage, RAM stage, Heads Linux kernel + initrd |
| 3 | unused | Zero; anchored in sealing policies |
| 4 | Heads (`usb-init`, `kexec-insert-key`, `functions`) | Boot mode tracking: `"usb"` during USB init, `"generic"` after DUK unsealed, `"recovery"` when recovery shell entered |
| 4 | Heads (`usb-init.sh`, `kexec-insert-key.sh`, `initrd/etc/functions.sh`) | Boot mode tracking: `"usb"` during USB init, `"generic"` after DUK unsealed, `"recovery"` when recovery shell entered |
| 5 | Heads `insmod` wrapper | Each loaded kernel module: parameters + binary content (default `MODULE_PCR=5`) |
| 6 | Heads `qubes-measure-luks` | LUKS header dump for each encrypted drive |
| 7 | Heads `cbfs-init`, `uefi-init` | Each CBFS/UEFI file: filename then content (default `CONFIG_PCR=7`) — covers `config.user`, GPG keyring, user CBFS files |
| 16 | `tpmr calcfuturepcr` (scratch use only) | Resettable debug PCR used as scratch pad during pre-computation of future PCR values; not part of any sealing policy |
| 6 | Heads `qubes-measure-luks.sh` | LUKS header dump for each encrypted drive |
| 7 | Heads `cbfs-init.sh`, `uefi-init.sh` | Each CBFS/UEFI file: filename then content (default `CONFIG_PCR=7`) — covers `config.user`, GPG keyring, user CBFS files |
| 16 | `tpmr.sh calcfuturepcr` (scratch use only) | Resettable debug PCR used as scratch pad during pre-computation of future PCR values; not part of any sealing policy |

PCRs 0-3 are read at seal time and included in sealing policies. The zero
state of PCRs 0, 1, and 3 is intentional — any unexpected extension of those
Expand Down Expand Up @@ -214,7 +214,7 @@ ROM configuration integrity, not disk state.

## PCR extension

`tpmr extend -ix <pcr_num> -ic <string>` extends a PCR with the hash of a
`tpmr.sh extend -ix <pcr_num> -ic <string>` extends a PCR with the hash of a
string. `-if <file>` extends with the hash of a file.

`calcfuturepcr` replays the expected extend sequence to compute what a PCR
Expand All @@ -224,7 +224,7 @@ after normal init, before any recovery shell entry).

### Recovery PCR extension

When a recovery shell is entered, `initrd/etc/functions` extends PCR 4 with
When a recovery shell is entered, `initrd/etc/functions.sh` extends PCR 4 with
the string `"recovery"`. This permanently invalidates TOTP and LUKS DUK
unsealing for the rest of the boot session — the TPM will refuse to unseal
secrets that were sealed against the normal-boot PCR 4 value.
Expand Down Expand Up @@ -283,11 +283,11 @@ validates the counter is readable from TPM, ensuring secrets can actually be
unsealed.

The counter is created during OEM Factory Reset by `check_tpm_counter` in
`initrd/etc/functions`.
`initrd/etc/functions.sh`.

### Counter state file

`read_tpm_counter` in `initrd/etc/functions` reads the counter from the TPM
`read_tpm_counter` in `initrd/etc/functions.sh` reads the counter from the TPM
and writes the result to `/tmp/counter-<index>`. The format is
`<hex_index>: <hex_value>`.

Expand Down Expand Up @@ -324,9 +324,9 @@ counter passes preflight or the user chooses to continue.

### Pipeline safety

`tpmr counter_read` must be called with a direct redirect, not piped through
`tee`. Piping through `tee` hides `tpmr` failures because `||` checks the
exit status of `tee` (always 0), not `tpmr`. See
`tpmr.sh counter_read` must be called with a direct redirect, not piped through
`tee`. Piping through `tee` hides `tpmr.sh` failures because `||` checks the
exit status of `tee` (always 0), not `tpmr.sh`. See
[ux-patterns.md](ux-patterns.md#tpm-counter-patterns) for the correct pattern.

---
Expand Down Expand Up @@ -359,13 +359,13 @@ policy, or investigating why a seal/unseal operation fails.
| --- | --- | --- |
| Which coreboot PCRs are active on a board | `config/coreboot-<board>.config` | `CONFIG_PCR_SRTM`, `CONFIG_TPM_INIT_RAMSTAGE`, `CONFIG_TPM_MEASURED_BOOT_INIT_BOOTBLOCK`, `CONFIG_INTEL_TXT` |
| Which coreboot version / fork a board uses | `modules/coreboot` + `boards/<board>/` | `CONFIG_COREBOOT_VERSION` in board config selects the coreboot source defined in `modules/coreboot` |
| LUKS DUK sealing policy (which PCRs) | `initrd/bin/kexec-seal-key` | `tpmr seal` call and surrounding `pcrread` / `calcfuturepcr` calls; DEBUG comments explain each PCR |
| TOTP/HOTP sealing policy (which PCRs) | `initrd/bin/seal-totp` | `tpmr seal` call; DEBUG messages explain why PCR 5 and PCR 6 are excluded |
| PCR 4 (boot mode) tracking | `initrd/bin/usb-init`, `initrd/bin/kexec-insert-key`, `initrd/etc/functions` | `tpmr extend` calls with `"usb"`, `"generic"`, `"recovery"` |
| LUKS DUK sealing policy (which PCRs) | `initrd/bin/kexec-seal-key.sh` | `tpmr.sh seal` call and surrounding `pcrread` / `calcfuturepcr` calls; DEBUG comments explain each PCR |
| TOTP/HOTP sealing policy (which PCRs) | `initrd/bin/seal-totp.sh` | `tpmr.sh seal` call; DEBUG messages explain why PCR 5 and PCR 6 are excluded |
| PCR 4 (boot mode) tracking | `initrd/bin/usb-init.sh`, `initrd/bin/kexec-insert-key.sh`, `initrd/etc/functions.sh` | `tpmr.sh extend` calls with `"usb"`, `"generic"`, `"recovery"` |
| PCR 5 (kernel modules) | `initrd/sbin/insmod` | `MODULE_PCR` variable; default `MODULE_PCR=5`; each `insmod` extends PCR 5 |
| PCR 6 (LUKS header) | `initrd/bin/qubes-measure-luks` | `tpmr extend` call against `/tmp/luksDump.txt` |
| PCR 7 (CBFS / ROM files) | `initrd/bin/cbfs-init`, `initrd/bin/uefi-init` | `CONFIG_PCR` variable; default `CONFIG_PCR=7`; each extracted file extends PCR 7 |
| Rollback counter logic | `initrd/etc/functions` | `check_tpm_counter`, `read_tpm_counter`, `counter_increment` |
| PCR 6 (LUKS header) | `initrd/bin/qubes-measure-luks.sh` | `tpmr.sh extend` call against `/tmp/luksDump.txt` |
| PCR 7 (CBFS / ROM files) | `initrd/bin/cbfs-init.sh`, `initrd/bin/uefi-init.sh` | `CONFIG_PCR` variable; default `CONFIG_PCR=7`; each extracted file extends PCR 7 |
| Rollback counter logic | `initrd/etc/functions.sh` | `check_tpm_counter`, `read_tpm_counter`, `counter_increment` |

### Adding a new board

Expand Down
44 changes: 36 additions & 8 deletions initrd/bin/gui-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,19 @@ prompt_update_checksums() {
--yesno "You have chosen to update the checksums and sign all of the files in /boot.\n\nThis means that you trust that these files have not been tampered with.\n\nYou will need your GPG key available, and this change will modify your disk.\n\nDo you want to continue?" 0 80); then
if update_checksums; then
return 0
fi
# update_checksums may have set the TPM-reset-required marker
# during its execution (e.g. check_tpm_counter hit "out of
# resources"). Show the targeted TPM message instead of the
# generic failure so the user knows exactly what to do.
if tpm_reset_required; then
whiptail_error --title 'TPM Reset Required' \
--msgbox "Cannot sign /boot: TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM), then update checksums." 0 80
else
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
return 1
fi
return 1
fi
return 1
}
Expand Down Expand Up @@ -411,8 +419,13 @@ EOF
skip_to_menu="true"
return 1
;;
# "Reset the TPM" from the TOTP failure whiptail menu.
# The gate runs first to verify /boot integrity. If the gate
# fails *because* TPM reset is required (e.g. stale counters),
# the || tpm_reset_required bypass lets reset_tpm() proceed —
# it clears counters and creates a fresh one.
p)
if gate_reseal_with_integrity_report && reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
if { gate_reseal_with_integrity_report || tpm_reset_required; } && reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
;;
Expand Down Expand Up @@ -806,8 +819,11 @@ show_tpm_totp_hotp_options_menu() {
update_totp && update_hotp || true
fi
;;
# "Reset the TPM" from the TPM/TOTP/HOTP options whiptail menu.
# Same gate-bypass pattern: if the gate fails because TPM
# reset is required, proceed to reset_tpm() anyway.
r)
if gate_reseal_with_integrity_report && reset_tpm; then
if { gate_reseal_with_integrity_report || tpm_reset_required; } && reset_tpm; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
;;
Expand Down Expand Up @@ -837,7 +853,20 @@ reset_tpm() {
return 1
fi

tpmr.sh reset "$tpm_owner_passphrase"
# Verify TPM reset succeeded before proceeding to counter
# creation, signing, TOTP generation, and DUK resealing.
# A failed reset would leave the TPM in an inconsistent state
# (old passphrase with unknown PCRs), causing confusing errors
# downstream. Show the actual error to the user and return
# to the menu.
local reset_err_file=$(mktemp)
if ! tpmr.sh reset "$tpm_owner_passphrase" >"$reset_err_file" 2>&1; then
ERROR=$(tail -n 1 "$reset_err_file" | fold -s)
rm -f "$reset_err_file"
whiptail_error --title 'ERROR' \
--msgbox "Error resetting TPM:\n\n${ERROR}" 0 80
return 1
fi

# now that the TPM is reset, remove invalid TPM counter files
mount_boot
Expand Down Expand Up @@ -952,12 +981,11 @@ TRACE_FUNC

if [ -x /bin/hotp_verification ]; then
enable_usb
# Detect dongle branding from USB VID:PID -- must run AFTER enable_usb so lsusb
# can see the dongle (NK3 enumerates ~1 second after USB module load).
detect_usb_security_dongle_branding
fi

# Detect dongle branding from USB VID:PID -- must run AFTER enable_usb so lsusb
# can see the dongle (NK3 enumerates ~1 second after USB module load).
detect_usb_security_dongle_branding

if detect_boot_device; then
# /boot device with installed OS found
clean_boot_check
Expand Down
Loading
Loading