Skip to content
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ GIT_STATUS := $(shell \
fi)
HEADS_GIT_VERSION := $(shell git describe --abbrev=7 --tags --dirty)
GIT_TIMESTAMP := $(shell git log -1 --format=%cd --date=format:'%Y%m%d-%H%M%S')
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | cut -c1-30)
# Keep branch identifier path-safe for artifact filenames (e.g. feature/foo -> feature_foo).
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | tr '/[:space:]' '__' | cut -c1-30)
# Release builds: HEAD is exactly on a tag AND working tree is clean.
# Dev builds: any untagged commit, commits ahead of a tag, or dirty tree.
# Dev filenames include timestamp + branch for traceability without
Expand Down
149 changes: 96 additions & 53 deletions doc/logging.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion doc/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ creating additional hardware binding:

The LUKS Disk Unlock Key (DUK) is a random binary key that:

1. Is generated from `/dev/urandom` by `kexec-seal-key` (128 characters — 1024 bits of entropy).
1. Is generated from `/dev/urandom` by `kexec-seal-key` (128 bytes — 1024 bits of entropy, i.e. a $2^{1024}$ brute-force space).
2. Is sealed to TPM NVRAM with PCR policy `0,1,2,3,4,5,6,7`.
3. Is added as a LUKS key slot alongside the user's Disk Recovery Key (DRK).
4. At boot, `kexec-insert-key` unseals it and injects it into a minimal
Expand Down
7 changes: 4 additions & 3 deletions doc/tpm.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,10 @@ PCRs (e.g. enabling an optional coreboot feature) would break the seal.

#### LUKS Disk Unlock Key (DUK) — kexec-seal-key

The DUK is a 128-character random key (128 bytes from `/dev/urandom`, providing
1024 bits of entropy). It is added to a dedicated LUKS key slot and sealed to
TPM NVRAM with the policy below.
The DUK is a 128-byte random key generated from `/dev/urandom` (1024 bits of
entropy). This brute-force space ($2^{1024}$) is vastly larger than practical
password-style secrets and far beyond normal attacker capabilities. It is added
to a dedicated LUKS key slot and sealed to TPM NVRAM with the policy below.

| PCR | How obtained | Reason |
| --- | --- | --- |
Expand Down
9 changes: 5 additions & 4 deletions initrd/bin/cbfs-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ if [ -z "$CONFIG_PCR" ]; then
fi

if [ "$CONFIG_CBFS_VIA_FLASHPROG" = "y" ]; then
# Use flashrom directly, because we don't have /tmp/config with params for flash.sh yet
STATUS "Reading board keys and configuration from SPI flash"
# Workaround: cbfs cannot read CBFS directly on rom_hole boards
# See: https://github.com/osresearch/flashtools/issues/10
STATUS "Reading SPI flash with flashprog (rom_hole workaround)..."
if /bin/flashprog -p internal --fmap -i COREBOOT -i FMAP -r /tmp/cbfs-init.rom; then
CBFS_ARG=" -o /tmp/cbfs-init.rom"
STATUS_OK "ROM read"
else
WARN "Failed to read board keys and configuration from SPI flash - some features may not be available"
fi
Expand All @@ -47,7 +49,6 @@ for cbfsname in `echo $cbfsfiles`; do
|| DIE "$filename: cbfs file read failed"
if [ "$CONFIG_TPM" = "y" ]; then
TRACE_FUNC
INFO "Measuring $filename into TPM PCR[$CONFIG_PCR]"
# Measure both the filename and its content. This
# ensures that renaming files or pivoting file content
# will still affect the resulting PCR measurement.
Expand All @@ -57,4 +58,4 @@ for cbfsname in `echo $cbfsfiles`; do
fi
fi
done
STATUS_OK "Board keys and configuration loaded from firmware"
STATUS_OK "GPG keyring, trustdb, and board configuration extracted from firmware"
4 changes: 2 additions & 2 deletions initrd/bin/gpg-gui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ while true; do
"g")
confirm_gpg_card
STATUS "INSTRUCTIONS:"
INFO "Type 'admin' then 'generate' and follow the prompts to generate a GPG key"
INFO "Type 'quit' once the key is generated to exit GPG"
NOTE "Type 'admin' then 'generate' and follow the prompts to generate a GPG key"
NOTE "Type 'quit' once the key is generated to exit GPG"
gpg --card-edit >/tmp/gpg_card_edit_output
if [ $? -eq 0 ]; then
gpg_post_gen_mgmt
Expand Down
24 changes: 11 additions & 13 deletions initrd/bin/gui-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,11 @@ gate_reseal_with_integrity_report() {
# starting the NK3 CCID teardown. This safety call covers the
# case where scdaemon was restarted between then and now.
release_scdaemon
STATUS "Checking $DONGLE_BRAND presence before sealing"
DEBUG "gate_reseal_with_integrity_report: checking HOTP token presence"
STATUS "Checking $DONGLE_BRAND presence before sealing"
if hotp_verification info >/dev/null 2>&1; then
token_ok="y"
STATUS_OK "$DONGLE_BRAND present and accessible"
token_ok="y"
break
fi
DEBUG "gate_reseal_with_integrity_report: HOTP token not accessible"
Expand Down Expand Up @@ -293,10 +293,9 @@ generate_totp_hotp() {
if [ "$CONFIG_TPM" != "y" ] && [ -x /bin/hotp_verification ]; then
# If we don't have a TPM, but we have a HOTP USB Security dongle
TRACE_FUNC
STATUS "Generating new HOTP secret"
/bin/seal-hotpkey.sh ||
DIE "Failed to generate HOTP secret"
elif STATUS "Generating new TOTP secret" && /bin/seal-totp.sh "$BOARD_NAME" "$tpm_owner_passphrase"; then
elif /bin/seal-totp.sh "$BOARD_NAME" "$tpm_owner_passphrase"; then
if [ -x /bin/hotp_verification ]; then
# If we have a TPM and a HOTP USB Security dongle
if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then
Expand Down Expand Up @@ -370,6 +369,7 @@ update_totp() {
return 1 # Already asked to skip to menu from a prior error
fi

DEBUG "TPM state at TOTP failure:"
DEBUG "$(pcrs)"

totp_menu_text=$(
Expand Down Expand Up @@ -450,6 +450,7 @@ update_hotp() {
local hotp_token_info hotp_exit attempt

# Ensure dongle is present; capture info for PIN counter display
STATUS "Checking $DONGLE_BRAND presence"
if ! hotp_token_info="$(hotp_verification info)"; then
if [ "$skip_to_menu" = "true" ]; then
return 1 # Already asked to skip to menu from a prior error
Expand Down Expand Up @@ -487,12 +488,14 @@ update_hotp() {
# PIN retry count is shown only before a retry so normal boots stay silent.
for attempt in 1 2 3; do
# Don't output HOTP codes to screen, so as to make replay attacks harder
STATUS "Verifying HOTP code"
hotp_verification check "$HOTP"
hotp_exit=$?
case "$hotp_exit" in
0)
HOTP="Success"
BG_COLOR_MAIN_MENU="normal"
STATUS_OK "HOTP code verified"
return
;;
4 | 7) # 4: code incorrect, 7: not a valid HOTP code — no point retrying same code
Expand Down Expand Up @@ -667,7 +670,6 @@ check_gpg_key() {

prompt_auto_default_boot() {
TRACE_FUNC
STATUS_OK "HOTP verification success"
if pause_automatic_boot; then
STATUS "Attempting default boot"
attempt_default_boot
Expand Down Expand Up @@ -896,16 +898,17 @@ reset_tpm() {
DIE "Unable to create rollback file"

TRACE_FUNC
# As a countermeasure for existing primary handle hash, we will now force sign /boot without it
# USB is already initialized at startup; run gpg --card-status to populate key stub.
# As a countermeasure for existing primary handle hash, we will now force sign /boot without it.
# NOTE: At seal time, PCR5 is IGNORED (not measured) - only used on HOTP board variants. So USB
# modules loading here don't affect DUK seal. GPG card needs USB to be enabled first.
enable_usb
wait_for_gpg_card || true
while true; do
GPG_KEY_COUNT=$(gpg -K 2>/dev/null | wc -l)
if [ "$GPG_KEY_COUNT" -eq 0 ]; then
prompt_missing_gpg_key_action || return 1
wait_for_gpg_card || true
else
STATUS_OK "TPM reset successful - updating /boot checksums and signatures"
if ! update_checksums; then
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
Expand All @@ -925,14 +928,9 @@ reset_tpm() {
fi

if [ -s /boot/kexec_key_devices.txt ] || [ -s /boot/kexec_key_lvm.txt ]; then
STATUS_OK "TPM reset successful - resealing TPM Disk Unlock Key (DUK)"
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
else
INFO "Returning to the main menu"
fi
else
whiptail_error --title 'ERROR: No TPM Detected' --msgbox "This device does not have a TPM.\n\nPress OK to return to the Main Menu" 0 80
fi
}

Expand Down
4 changes: 2 additions & 2 deletions initrd/bin/kexec-insert-key.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ if [ -r "$TMP_KEY_LVM" ]; then
fi

# Measure the LUKS headers before we unseal the LUKS Disk Unlock Key from TPM
STATUS "Measuring LUKS headers"
STATUS "Measuring TPM Disk Unlock Key (DUK) into PCR[6])"
cat "$TMP_KEY_DEVICES" | cut -d\ -f1 | xargs /bin/qubes-measure-luks.sh ||
DIE "LUKS measure failed"

Expand Down Expand Up @@ -65,7 +65,7 @@ fi

# Override PCR 4 so that user can't read the key
TRACE_FUNC
INFO "TPM: Extending PCR[4] to prevent any future secret unsealing"
INFO "TPM: Extending PCR[4] with content of string 'generic' to prevent secret unsealing"
tpmr.sh extend -ix 4 -ic generic ||
DIE 'Unable to scramble PCR'

Expand Down
16 changes: 12 additions & 4 deletions initrd/bin/kexec-seal-key.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,20 @@ if [ $attempts -ge 3 ]; then
DIE "Failed to set a valid Disk Unlock Key (DUK) passphrase after 3 attempts. Exiting..."
fi

# Generate key file
STATUS "Generating new randomized key of 128 characters for LUKS TPM Disk Unlock Key"
# Generate key file: 128 bytes from /dev/urandom = 1024 bits of entropy.
# This provides a brute-force space of 2^1024 possible values. An attacker
# would need to try ~2^1023 guesses on average. Even at an absurd 10^12 guesses/second,
# this would require ~2^1023/10^12 seconds — vastly longer than the age of the universe.
# See doc/tpm.md and doc/security-model.md for full entropy analysis.
STATUS "Generating new 128-byte random key for LUKS TPM Disk Unlock Key"
dd \
if=/dev/urandom \
of="$DUK_KEY_FILE" \
bs=1 \
count=128 \
2>/dev/null ||
DIE "Unable to generate random key of 128 characters"
DIE "Unable to generate random key of 128 bytes"
STATUS_OK "LUKS TPM Disk Unlock Key generated"

previous_luks_header_version=0
for dev in $key_devices; do
Expand Down Expand Up @@ -261,15 +266,17 @@ for dev in $key_devices; do
--new-key-slot "$duk_keyslot" \
"$dev" "$DUK_KEY_FILE" ||
DIE "$dev: Unable to add LUKS TPM Disk Unlock Key to LUKS key slot $duk_keyslot"
STATUS_OK "$dev: LUKS TPM Disk Unlock Key added to slot $duk_keyslot"
done

# Now that we have setup the new keys, measure the PCRs
# We don't care what ends up in PCR 6; we just want
# to get the /tmp/luksDump.txt file. We use PCR16
# since it should still be zero
STATUS "Measuring LUKS headers for TPM sealing policy"
STATUS "Measuring TPM Disk Unlock Key (DUK) for sealing policy (PCR[6])"
echo "$key_devices" | xargs /bin/qubes-measure-luks.sh ||
DIE "Unable to measure the LUKS headers"
STATUS_OK "TPM Disk Unlock Key (DUK) measured for sealing policy (PCR[6])"

STATUS "Reading current PCR values for TPM sealing policy"
pcrf="/tmp/secret/pcrf.bin"
Expand All @@ -293,6 +300,7 @@ DEBUG "Precomputing TPM future value for PCR6 sealing/unsealing of LUKS TPM Disk
tpmr.sh calcfuturepcr 6 "/tmp/luksDump.txt" >>"$pcrf"
# We take into consideration user files in cbfs
tpmr.sh pcrread -a 7 "$pcrf"
STATUS_OK "PCR values read for TPM sealing policy"

# tpmr.sh seal may prompt for TPM owner password; avoid DO_WITH_DEBUG here so the
# prompt remains visible on console. tpmr.sh logs command details internally.
Expand Down
4 changes: 2 additions & 2 deletions initrd/bin/kexec-select-boot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ parse_option() {
}

scan_options() {
STATUS "Scanning for unsigned boot options"
STATUS "Scanning for boot options"
option_file="/tmp/kexec_options.txt"
scan_boot_options "$bootdir" "$config" "$option_file"
if [ ! -s $option_file ]; then
Expand Down Expand Up @@ -373,7 +373,7 @@ while true; do
if [ ! -r "$TMP_KEY_DEVICES" ]; then
# Extend PCR4 as soon as possible
TRACE_FUNC
INFO "TPM: Extending PCR[4] to prevent further secret unsealing"
INFO "TPM: Extending PCR[4] with content of string 'generic' to prevent secret unsealing"
tpmr.sh extend -ix 4 -ic generic ||
DIE "Failed to extend TPM PCR[4]"
fi
Expand Down
1 change: 1 addition & 0 deletions initrd/bin/lock_chip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ if [ -n "$APM_CNT" -a -n "$FIN_CODE" ]; then
# until the next system reset.
STATUS "Finalizing chipset write protection via SMI PR0 lockdown"
io386 -o b -b x $APM_CNT $FIN_CODE
STATUS_OK "Chipset write protection locked"
else
NOTE "NOT finalizing chipset - lock_chip.sh called without valid APM_CNT and FIN_CODE"
fi
10 changes: 6 additions & 4 deletions initrd/bin/network-init-recovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ ethernet_activation()
insmod.sh /lib/modules/$module.ko
fi
done
STATUS_OK "Ethernet network modules loaded"
}

# bring up the ethernet interface
Expand Down Expand Up @@ -113,10 +114,10 @@ if [ -n "$dev" ]; then
STATUS_OK "NTP time sync successful"
fi
fi
STATUS "Syncing hardware clock with system time (UTC)"
hwclock -w
date=$(date "+%Y-%m-%d %H:%M:%S %Z")
STATUS "Time: $date"
STATUS "Syncing hardware clock with system time (UTC)"
hwclock -w
date=$(date "+%Y-%m-%d %H:%M:%S %Z")
STATUS_OK "Hardware clock synced: $date"
fi
fi
fi
Expand All @@ -133,6 +134,7 @@ if [ -n "$dev" ]; then
# -B background
# -R create host keys
dropbear -B -R
STATUS_OK "Dropbear SSH server started"
fi
STATUS_OK "Network setup complete"
ifconfig $dev
Expand Down
Loading