From 1f6a97594057e106118d6e2cb706968be88e7800 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Fri, 7 Feb 2025 13:18:17 -0500 Subject: [PATCH 01/21] Codebase up to TPM DUK: ident, add DEBUG+TRACE_FUNC, TRACE_FUNC now gives call hierarchy, fix HOTP resealing only on OS reinstall, clarify TPM increment workflow Signed-off-by: Thierry Laurion --- initrd/.bash_history | 4 +-- initrd/bin/generic-init | 4 +-- initrd/bin/gui-init | 41 ++++++++++++----------- initrd/bin/gui-init-basic | 6 ++-- initrd/bin/kexec-save-default | 10 +++--- initrd/bin/kexec-save-key | 3 +- initrd/bin/kexec-select-boot | 48 +++++++++++++-------------- initrd/bin/kexec-sign-config | 62 +++++++++++++++++++++++++++++------ initrd/bin/key-init | 18 +++++----- initrd/bin/oem-factory-reset | 20 +++++++---- initrd/bin/reboot | 2 +- initrd/bin/tpmr | 6 ++-- initrd/bin/unseal-hotp | 36 +++++++++++--------- initrd/bin/unseal-totp | 7 ++-- initrd/etc/functions | 62 ++++++++++++++++++++++++++--------- initrd/sbin/insmod | 6 ++-- 16 files changed, 212 insertions(+), 123 deletions(-) diff --git a/initrd/.bash_history b/initrd/.bash_history index 7f03d3ee1..44fd60529 100644 --- a/initrd/.bash_history +++ b/initrd/.bash_history @@ -5,9 +5,9 @@ find /boot/kexec*.txt | gpg --verify /boot/kexec.sig - #remove invalid kexec_* signed files mount /dev/sda1 /boot && mount -o remount,rw /boot && rm /boot/kexec* && mount -o remount,ro /boot #Generate keys on OpenPGP smartcard: -mount-usb && gpg --home=/.gnupg/ --card-edit +mount-usb --mode rw && gpg --home=/.gnupg/ --card-edit #Copy generated public key, private_subkey, trustdb and artifacts to external media for backup: -mount -o remount,rw /media && mkdir -p /media/gpg_keys; gpg --export-secret-keys --armor email@address.com > /media/gpg_keys/private.key && gpg --export --armor email@address.com > /media/gpg_keys/public.key && gpg --export-ownertrust > /media/gpg_keys/otrust.txt && cp -r ./.gnupg/* /media/gpg_keys/ 2> /dev/null +mkdir -p /media/gpg_keys; gpg --export-secret-keys --armor email@address.com > /media/gpg_keys/private.key && gpg --export --armor email@address.com > /media/gpg_keys/public.key && gpg --export-ownertrust > /media/gpg_keys/otrust.txt && cp -r ./.gnupg/* /media/gpg_keys/ 2> /dev/null #Insert public key and trustdb export into reproducible rom: cbfs -o /media/coreboot.rom -a "heads/initrd/.gnupg/keys/public.key" -f /media/gpg_keys/public.key && cbfs -o /media/coreboot.rom -a "heads/initrd/.gnupg/keys/otrust.txt" -f /media/gpg_keys/otrust.txt #Flush changes to external media: diff --git a/initrd/bin/generic-init b/initrd/bin/generic-init index 0a4a17a63..a3b9f34e6 100755 --- a/initrd/bin/generic-init +++ b/initrd/bin/generic-init @@ -48,14 +48,14 @@ while true; do if [ "$totp_confirm" = "m" ]; then # Try to select a kernel from the menu mount_boot - kexec-select-boot -m -b /boot -c "grub.cfg" + DO_WITH_DEBUG kexec-select-boot -m -b /boot -c "grub.cfg" continue fi if [ "$totp_confirm" = "y" -o -n "$totp_confirm" ]; then # Try to boot the default mount_boot - kexec-select-boot -b /boot -c "grub.cfg" \ + DO_WITH_DEBUG kexec-select-boot -b /boot -c "grub.cfg" \ || recovery "Failed default boot" fi diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 7d4bf2eaa..76c42c9a7 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -183,17 +183,6 @@ update_totp() { TOTP="NO TPM" else TOTP=$(unseal-totp) - # On platforms using CONFIG_BOOT_EXTRA_TTYS multiple processes may try to - # access TPM at the same time, failing with EBUSY. The order of execution - # is unpredictable, so the error may appear on main console, secondary one, - # or neither of them if the calls are sufficiently staggered. Try up to - # three times (including previous one) with small delays in case of error, - # instead of immediately scaring users with "you've been pwned" message. - while [ $? -ne 0 ] && [ $tries -lt 2 ]; do - sleep 0.5 - ((tries++)) - TOTP=$(unseal-totp) - done if [ $? -ne 0 ]; then BG_COLOR_MAIN_MENU="error" if [ "$skip_to_menu" = "true" ]; then @@ -280,7 +269,10 @@ update_hotp() { HOTP='N/A' fi - if [[ "$CONFIG_TPM" = n && "$HOTP" = "Invalid code" ]]; then + if [[ "$HOTP" = "Invalid code" ]]; then + #Do not propose to generate a new secret if there is no /boot/kexec_hotp_counter + # tpm unseal succeeded: so the sealed secret is correct: we should propose to reset TPM if not already + # Here: the OS was most probably reinstalled since TPM can still unseal the secret whiptail_error --title "ERROR: HOTP Validation Failed!" \ --menu "ERROR: $CONFIG_BRAND_NAME couldn't validate the HOTP code.\n\nIf you just reflashed your BIOS, you should generate a new TOTP/HOTP secret.\n\nIf you have not just reflashed your BIOS, THIS COULD INDICATE TAMPERING!\n\nHow would you like to proceed?" 0 80 4 \ 'g' ' Generate new TOTP/HOTP secret' \ @@ -553,21 +545,30 @@ reset_tpm() { mount -o rw,remount /boot #TODO: this is really problematic, we should really remove the primary handle hash - INFO "Removing rollback and primary handle hash under /boot" + INFO "Removing rollback and primary handle hashes under /boot" + + DEBUG "Removing /boot/kexec_rollback.txt and /boot/kexec_primhdl_hash.txt" rm -f /boot/kexec_rollback.txt rm -f /boot/kexec_primhdl_hash.txt # create Heads TPM counter before any others check_tpm_counter /boot/kexec_rollback.txt "" "$tpm_owner_password" || die "Unable to find/create tpm counter" - counter="$TPM_COUNTER" - increment_tpm_counter $counter >/dev/null 2>&1 || + TRACE_FUNC + + TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || die "Unable to increment tpm counter" - sha256sum /tmp/counter-$counter >/boot/kexec_rollback.txt || + #TODO: should this be here? + DO_WITH_DEBUG sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt || die "Unable to create rollback file" + TRACE_FUNC # As a countermeasure for existing primary handle hash, we will now force sign /boot without it if (whiptail --title 'TPM Reset Successfully' \ --yesno "Would you like to update the checksums and sign all of the files in /boot?\n\nYou will need your GPG key to continue and this will modify your disk.\n\nOtherwise the system will reboot immediately." 0 80); then @@ -593,7 +594,7 @@ select_os_boot_option() { TRACE_FUNC mount_boot if verify_global_hashes; then - kexec-select-boot -m -b /boot -c "grub.cfg" -g + DO_WITH_DEBUG kexec-select-boot -m -b /boot -c "grub.cfg" -g fi } @@ -606,11 +607,13 @@ attempt_default_boot() { fi DEFAULT_FILE=$(find /boot/kexec_default.*.txt 2>/dev/null | head -1) if [ -r "$DEFAULT_FILE" ]; then - kexec-select-boot -b /boot -c "grub.cfg" -g || + TRACE_FUNC + DO_WITH_DEBUG kexec-select-boot -b /boot -c "grub.cfg" -g || recovery "Failed default boot" elif (whiptail_warning --title 'No Default Boot Option Configured' \ --yesno "There is no default boot option configured yet.\nWould you like to load a menu of boot options?\nOtherwise you will return to the main menu." 0 80); then - kexec-select-boot -m -b /boot -c "grub.cfg" -g + TRACE_FUNC + DO_WITH_DEBUG kexec-select-boot -m -b /boot -c "grub.cfg" -g fi } diff --git a/initrd/bin/gui-init-basic b/initrd/bin/gui-init-basic index 033e561af..af9da581e 100755 --- a/initrd/bin/gui-init-basic +++ b/initrd/bin/gui-init-basic @@ -159,7 +159,7 @@ select_os_boot_option() { TRACE_FUNC mount_boot - kexec-select-boot -m -b /boot -c "grub.cfg" -g -i + DO_WITH_DEBUG kexec-select-boot -m -b /boot -c "grub.cfg" -g -i } attempt_default_boot() @@ -174,11 +174,11 @@ attempt_default_boot() if [ "$CONFIG_BASIC_NO_AUTOMATIC_DEFAULT" != "y" ]; then basic-autoboot.sh elif [ -r "$DEFAULT_FILE" ]; then - kexec-select-boot -b /boot -c "grub.cfg" -g -i -s \ + DO_WITH_DEBUG kexec-select-boot -b /boot -c "grub.cfg" -g -i -s \ || recovery "Failed default boot" elif (whiptail_warning --title 'No Default Boot Option Configured' \ --yesno "There is no default boot option configured yet.\nWould you like to load a menu of boot options?\nOtherwise you will return to the main menu." 0 80) then - kexec-select-boot -m -b /boot -c "grub.cfg" -g -i + DO_WITH_DEBUG kexec-select-boot -m -b /boot -c "grub.cfg" -g -i fi } diff --git a/initrd/bin/kexec-save-default b/initrd/bin/kexec-save-default index 22ed28cfa..79d582913 100755 --- a/initrd/bin/kexec-save-default +++ b/initrd/bin/kexec-save-default @@ -8,10 +8,10 @@ TRACE_FUNC while getopts "b:d:p:i:" arg; do case $arg in - b) bootdir="$OPTARG" ;; - d) paramsdev="$OPTARG" ;; - p) paramsdir="$OPTARG" ;; - i) index="$OPTARG" ;; + b) bootdir="$OPTARG" ;; + d) paramsdev="$OPTARG" ;; + p) paramsdir="$OPTARG" ;; + i) index="$OPTARG" ;; esac done @@ -354,7 +354,7 @@ if [ "$CONFIG_TPM" = "y" ]; then fi fi if [ "$CONFIG_BASIC" != "y" ]; then - kexec-sign-config -p $paramsdir $extparam || + DO_WITH_DEBUG kexec-sign-config -p $paramsdir $extparam || die "Failed to sign default config" fi # switch back to ro mode diff --git a/initrd/bin/kexec-save-key b/initrd/bin/kexec-save-key index a6ceb71ba..0fe2373dc 100755 --- a/initrd/bin/kexec-save-key +++ b/initrd/bin/kexec-save-key @@ -77,10 +77,11 @@ kexec-seal-key $paramsdir || if [ "$skip_sign" != "y" ]; then extparam= if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then + DEBUG "kexec-save-key: CONFIG_IGNORE_ROLLBACK is not set, will sign with -r" extparam=-r fi # sign and auto-roll config counter - kexec-sign-config -p $paramsdir $extparam || + DO_WITH_DEBUG kexec-sign-config -p $paramsdir $extparam || die "Failed to sign updated config" fi diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot index 6a0edc6a7..d6b0d77eb 100755 --- a/initrd/bin/kexec-select-boot +++ b/initrd/bin/kexec-select-boot @@ -20,25 +20,25 @@ force_boot="n" skip_confirm="n" while getopts "b:d:p:a:r:c:uimgfs" arg; do case $arg in - b) bootdir="$OPTARG" ;; - d) paramsdev="$OPTARG" ;; - p) paramsdir="$OPTARG" ;; - a) add="$OPTARG" ;; - r) remove="$OPTARG" ;; - c) config="$OPTARG" ;; - u) unique="y" ;; - m) force_menu="y" ;; - i) - valid_hash="y" - valid_rollback="y" - ;; - g) gui_menu="y" ;; - f) - force_boot="y" - valid_hash="y" - valid_rollback="y" - ;; - s) skip_confirm="y" ;; + b) bootdir="$OPTARG" ;; + d) paramsdev="$OPTARG" ;; + p) paramsdir="$OPTARG" ;; + a) add="$OPTARG" ;; + r) remove="$OPTARG" ;; + c) config="$OPTARG" ;; + u) unique="y" ;; + m) force_menu="y" ;; + i) + valid_hash="y" + valid_rollback="y" + ;; + g) gui_menu="y" ;; + f) + force_boot="y" + valid_hash="y" + valid_rollback="y" + ;; + s) skip_confirm="y" ;; esac done @@ -120,14 +120,14 @@ verify_rollback_counter() { TPM_COUNTER=$(grep counter $TMP_ROLLBACK_FILE | cut -d- -f2) if [ -z "$TPM_COUNTER" ]; then - die "$TMP_ROLLBACK_FILE: TPM counter not found?" + die "$TMP_ROLLBACK_FILE: TPM counter not found. Please reset TPM through the Heads menu: Options -> TPM/TOTP/HOTP Options -> Reset the TPM" fi read_tpm_counter $TPM_COUNTER >/dev/null 2>&1 || - die "Failed to read TPM counter" + die "Failed to read TPM counter. Please reset TPM through the Heads menu: Options -> TPM/TOTP/HOTP Options -> Reset the TPM" sha256sum -c $TMP_ROLLBACK_FILE >/dev/null 2>&1 || - die "Invalid TPM counter state. TPM Reset required" + die "Invalid TPM counter state. Please reset TPM through the Heads menu: Options -> TPM/TOTP/HOTP Options -> Reset the TPM" valid_rollback="y" } @@ -361,9 +361,9 @@ do_boot() { while true; do if [ "$force_boot" = "y" -o "$CONFIG_BASIC" = "y" ]; then - check_config $paramsdir force + DO_WITH_DEBUG check_config $paramsdir force else - check_config $paramsdir + DO_WITH_DEBUG check_config $paramsdir fi TMP_DEFAULT_FILE=$(find /tmp/kexec/kexec_default.*.txt 2>/dev/null | head -1) || true TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt" diff --git a/initrd/bin/kexec-sign-config b/initrd/bin/kexec-sign-config index 52e6add6d..8e53702ed 100755 --- a/initrd/bin/kexec-sign-config +++ b/initrd/bin/kexec-sign-config @@ -27,24 +27,32 @@ fi paramsdir="${paramsdir%%/}" assert_signable +TRACE_FUNC confirm_gpg_card +TRACE_FUNC # remount /boot as rw mount -o remount,rw /boot +DEBUG "Signing kexec parameters in $paramsdir, rollback=$rollback, update=$update, counter=$counter" + # update hashes in /boot before signing if [ "$update" = "y" ]; then ( + TRACE_FUNC + DEBUG "update=y: Updating kexec hashes in /boot" cd /boot find ./ -type f ! -path './kexec*' -print0 | xargs -0 sha256sum >/boot/kexec_hashes.txt if [ -e /boot/kexec_default_hashes.txt ]; then + DEBUG "/boot/kexec_default_hashes.txt exists, updating /boot/kexec_default_hashes.txt" DEFAULT_FILES=$(cat /boot/kexec_default_hashes.txt | cut -f3 -d ' ') echo $DEFAULT_FILES | xargs sha256sum >/boot/kexec_default_hashes.txt fi #also save the file & directory structure to detect added files print_tree >/boot/kexec_tree.txt + TRACE_FUNC ) [ $? -eq 0 ] || die "$paramsdir: Failed to update hashes." @@ -56,31 +64,65 @@ fi if [ "$rollback" = "y" ]; then rollback_file="$paramsdir/kexec_rollback.txt" + DEBUG "rollback=y, counter=$counter, paramsdir=$paramsdir, rollback_file=$rollback_file" + TRACE_FUNC + if [ -n "$counter" ]; then - # use existing counter - read_tpm_counter $counter >/dev/null 2>&1 || + DEBUG "rollback=y: provided counter=$counter, will read tpm counter next" + TRACE_FUNC + + # use existing tpm counter + DO_WITH_DEBUG read_tpm_counter "$counter" >/dev/null 2>&1 || die "$paramsdir: Unable to read tpm counter '$counter'" else - # increment counter - check_tpm_counter $rollback_file >/dev/null 2>&1 || - die "$paramsdir: Unable to find/create tpm counter" - counter="$TPM_COUNTER" + DEBUG "rollback=y: counter was not provided: checking for existing TPM counter from TPM rollback_file=$rollback_file" + TRACE_FUNC + + if [ -e "$rollback_file" ]; then + # Extract TPM_COUNTER from rollback file + TPM_COUNTER=$(grep -o 'counter-[0-9a-f]*' "$rollback_file" | cut -d- -f2) + DEBUG "rollback=y: Found TPM counter $TPM_COUNTER in rollback file $rollback_file" + else + DEBUG "Rollback file $rollback_file does not exist. Creating new TPM counter." + DO_WITH_DEBUG check_tpm_counter $rollback_file || + die "$paramsdir: Unable to find/create tpm counter" + + TRACE_FUNC + TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || + die "$paramsdir: Unable to increment tpm counter" - increment_tpm_counter $counter >/dev/null 2>&1 || - die "$paramsdir: Unable to increment tpm counter" + # Ensure the incremented counter file exists + incremented_counter_file="/tmp/counter-$TPM_COUNTER" + if [ ! -e "$incremented_counter_file" ]; then + DEBUG "TPM counter file '$incremented_counter_file' not found. Attempting to read it again." + DO_WITH_DEBUG read_tpm_counter "$TPM_COUNTER" >/dev/null 2>&1 || + die "$paramsdir: TPM counter file '$incremented_counter_file' not found after incrementing." fi - sha256sum /tmp/counter-$counter >$rollback_file || + DEBUG "TPM counter file '$incremented_counter_file' found." + + # Create the rollback file + sha256sum "$incremented_counter_file" >$rollback_file || die "$paramsdir: Unable to create rollback file" fi +TRACE_FUNC param_files=$(find $paramsdir/kexec*.txt) if [ -z "$param_files" ]; then die "$paramsdir: No kexec parameter files to sign" fi for tries in 1 2 3; do - if sha256sum $param_files | gpg \ + if DO_WITH_DEBUG sha256sum $param_files | gpg \ --detach-sign \ -a \ >$paramsdir/kexec.sig \ diff --git a/initrd/bin/key-init b/initrd/bin/key-init index dcfaf5295..3213a9d43 100755 --- a/initrd/bin/key-init +++ b/initrd/bin/key-init @@ -10,19 +10,19 @@ TRACE_FUNC # Good system clock is required for GPG to work properly. # if system year is less then 2024, prompt user to set correct time if [ "$(date +%Y)" -lt 2024 ]; then - if whiptail_warning --title "System Time Incorrect" \ - --yesno "The system time is incorrect. Please set the correct time." \ - 0 80 --yes-button Continue --no-button Skip --clear; then - change-time.sh - fi + if whiptail_warning --title "System Time Incorrect" \ + --yesno "The system time is incorrect. Please set the correct time." \ + 0 80 --yes-button Continue --no-button Skip --clear; then + change-time.sh + fi fi # Import user's keys if they exist if [ -d /.gnupg/keys ]; then - # This is legacy location for user's keys. cbfs-init takes for granted that keyring and trustdb are in /.gnupg - # oem-factory-reset generates keyring and trustdb which cbfs-init dumps to /.gnupg - # TODO: Remove individual key imports. This is still valid for distro keys only below. - gpg --import /.gnupg/keys/*.key /.gnupg/keys/*.asc 2>/dev/null || warn "Importing user's keys failed" + # This is legacy location for user's keys. cbfs-init takes for granted that keyring and trustdb are in /.gnupg + # oem-factory-reset generates keyring and trustdb which cbfs-init dumps to /.gnupg + # TODO: Remove individual key imports. This is still valid for distro keys only below. + gpg --import /.gnupg/keys/*.key /.gnupg/keys/*.asc 2>/dev/null || warn "Importing user's keys failed" fi # Import trusted distro keys allowed for ISO signing diff --git a/initrd/bin/oem-factory-reset b/initrd/bin/oem-factory-reset index 22f1bba54..917a9acb1 100755 --- a/initrd/bin/oem-factory-reset +++ b/initrd/bin/oem-factory-reset @@ -731,14 +731,20 @@ generate_checksums() { whiptail_error_die "Unable to create TPM counter" TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || - whiptail_error_die "Unable to increment tpm counter" + if [ -s /tmp/counter-$TPM_COUNTER ]; then - # create rollback file - sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt 2>/dev/null || - whiptail_error_die "Unable to create rollback file" - else + # increment TPM counter + increment_tpm_counter $TPM_COUNTER >/dev/null 2>&1 || + whiptail_error_die "Unable to increment tpm counter" + + # create rollback file + sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt 2>/dev/null || + whiptail_error_die "Unable to create rollback file" + fi + fi + + # If HOTP is enabled from board config, create HOTP counter + if [ -x /bin/hotp_verification]; then ## needs to exist for initial call to unseal-hotp echo "0" >/boot/kexec_hotp_counter fi diff --git a/initrd/bin/reboot b/initrd/bin/reboot index ce7e6947d..90a757865 100755 --- a/initrd/bin/reboot +++ b/initrd/bin/reboot @@ -5,7 +5,7 @@ TRACE_FUNC if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then #Generalize user prompt to continue reboot or go to recovery shell - read -r -n 1 -s -p "Press any key to continue reboot or 'r' to go to recovery shell: " REPLY + read -r -n 1 -s -p "Press Enter to continue reboot or 'r' to go to recovery shell: " REPLY echo if [ "$REPLY" = "r" ] || [ "$REPLY" = "R" ]; then recovery "Reboot call bypassed to go into recovery shell to debug" diff --git a/initrd/bin/tpmr b/initrd/bin/tpmr index 756050e77..466184d99 100755 --- a/initrd/bin/tpmr +++ b/initrd/bin/tpmr @@ -589,7 +589,7 @@ tpm2_unseal() { # can't do anything without a primary handle. if [ ! -f "$PRIMARY_HANDLE_FILE" ]; then DEBUG "tpm2_unseal: No primary handle, cannot attempt to unseal" - warn "No TPM primary handle. You must reset TPM to seal secret to TPM NVRAM" + warn "No TPM primary handle. You must reset the TPM to seal secret to TPM NVRAM" exit 1 fi @@ -639,7 +639,7 @@ tpm1_unseal() { rm -f "$sealed_file" - tpm nv_readvalue \ + DO_WITH_DEBUG tpm nv_readvalue \ -in "$index" \ -sz "$sealed_size" \ -of "$sealed_file" || @@ -719,7 +719,7 @@ tpm1_reset() { DO_WITH_DEBUG tpm physicalsetdeactivated -c &>/dev/null DO_WITH_DEBUG tpm forceclear &>/dev/null DO_WITH_DEBUG tpm physicalenable &>/dev/null - DO_WITH_DEBUG tpm takeown -pwdo "$tpm_owner_password" &>/dev/null + DO_WITH_DEBUG --mask-position 3 tpm takeown -pwdo "$tpm_owner_password" &>/dev/null # And now turn it all back on DO_WITH_DEBUG tpm physicalpresence -s &>/dev/null diff --git a/initrd/bin/unseal-hotp b/initrd/bin/unseal-hotp index 8565ac612..5fae80da9 100755 --- a/initrd/bin/unseal-hotp +++ b/initrd/bin/unseal-hotp @@ -7,12 +7,12 @@ HOTP_SECRET="/tmp/secret/hotp.key" HOTP_COUNTER="/boot/kexec_hotp_counter" mount_boot_or_die() { - TRACE_FUNC - # Mount local disk if it is not already mounted - if ! grep -q /boot /proc/mounts; then - mount -o ro /boot || - die "Unable to mount /boot" - fi + TRACE_FUNC + # Mount local disk if it is not already mounted + if ! grep -q /boot /proc/mounts; then + mount -o ro /boot || + die "Unable to mount /boot" + fi } TRACE_FUNC @@ -29,27 +29,33 @@ mount_boot_or_die #counter_value=$(read_tpm_counter $counter | cut -f2 -d ' ' | awk 'gsub("^000e","")') # -counter_value=$(cat $HOTP_COUNTER) +#if HOTP_COUNTER is not present, bail out +if [ ! -f $HOTP_COUNTER ]; then + die "HOTP counter file not found. If you just reinstalled an OS, you need to reseal the HOTP secret" +fi + +# Read the counter from the file +counter_value=$(cat $HOTP_COUNTER 2>/dev/null) if [ "$counter_value" == "" ]; then - die "Unable to read HOTP counter" + die "Unable to read HOTP counter" fi #counter_value=$(printf "%d" 0x${counter_value}) if [ "$CONFIG_TPM" = "y" ]; then - DEBUG "Unsealing HOTP secret reuses TOTP sealed secret..." - tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET" || die "Unable to unseal HOTP secret" + DEBUG "Unsealing HOTP secret reuses TOTP sealed secret..." + tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET" || die "Unable to unseal HOTP secret" else - # without a TPM, generate a secret based on the SHA-256 of the ROM - secret_from_rom_hash >"$HOTP_SECRET" || die "Reading ROM failed" + # without a TPM, generate a secret based on the SHA-256 of the ROM + secret_from_rom_hash >"$HOTP_SECRET" || die "Reading ROM failed" fi # Truncate the secret if it is longer than the maximum HOTP secret truncate_max_bytes 20 "$HOTP_SECRET" if ! hotp $counter_value <"$HOTP_SECRET"; then - shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null - die 'Unable to compute HOTP hash?' + shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null + die 'Unable to compute HOTP hash?' fi shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null @@ -65,7 +71,7 @@ mount -o remount,rw /boot DEBUG "Incrementing HOTP counter under $HOTP_COUNTER" counter_value=$(expr $counter_value + 1) echo $counter_value >$HOTP_COUNTER || - die "Unable to create hotp counter file" + die "Unable to create hotp counter file" mount -o remount,ro /boot exit 0 diff --git a/initrd/bin/unseal-totp b/initrd/bin/unseal-totp index 3ca7cf281..da61deeea 100755 --- a/initrd/bin/unseal-totp +++ b/initrd/bin/unseal-totp @@ -8,11 +8,12 @@ TOTP_SECRET="/tmp/secret/totp.key" TRACE_FUNC if [ "$CONFIG_TPM" = "y" ]; then - tpmr unseal 4d47 0,1,2,3,4,7 312 "$TOTP_SECRET" || - die "Unable to unseal TOTP secret from TPM" + DO_WITH_DEBUG --mask-position 5 \ + tpmr unseal 4d47 0,1,2,3,4,7 312 "$TOTP_SECRET" || + die "Unable to unseal TOTP secret from TPM" fi -if ! totp -q <"$TOTP_SECRET"; then +if ! DO_WITH_DEBUG totp -q <"$TOTP_SECRET"; then shred -n 10 -z -u "$TOTP_SECRET" 2>/dev/null die 'Unable to compute TOTP hash?' fi diff --git a/initrd/etc/functions b/initrd/etc/functions index 61fb34fee..73b94f718 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -8,7 +8,10 @@ die() { else echo -e "!!! ERROR: $* !!!" >&2 fi - sleep 2 + + # ask user to press any key prior to exit + read -n 1 -s -r -p $'Press Enter to continue...\n\n' + exit 1 } @@ -194,7 +197,7 @@ confirm_gpg_card() { echo "GPG User PIN retry attempts left before becoming locked: $user_pin_retries" echo "GPG Admin PIN retry attempts left before becoming locked: $admin_pin_retries" echo "" - warn "Your GPG User PIN, followed by Enter key will be required for input at: 'Please unlock the card' next prompt" + warn "Your GPG User PIN, followed by Enter will be required for input at: 'Please unlock the card' next prompt" echo "" } @@ -512,14 +515,33 @@ DO_WITH_DEBUG() { return "$exit_status" } -# Trace the current script and function. +# TRACE_FUNC outputs the function call stack in a readable format. +# It helps debug the execution path leading to the current function. +# +# The format of the output is: +# main(/path/to/script:line) -> function1(/path/to/file:line) -> function2(/path/to/file:line) +# +# Usage: +# Call TRACE_FUNC within any function to print the call hierarchy. TRACE_FUNC() { # Index [1] for BASH_SOURCE and FUNCNAME give us the caller location. # FUNCNAME is 'main' if called from a script outside any function. # BASH_LINENO is offset by 1, it provides the line that the # corresponding FUNCNAME was _called from_, so BASH_LINENO[0] is the # location of the caller. - TRACE "${BASH_SOURCE[1]}(${BASH_LINENO[0]}): ${FUNCNAME[1]}" + + local i stack_trace="" + + # Traverse the call stack from the earliest caller to the direct caller of TRACE_FUNC + for ((i = ${#FUNCNAME[@]} - 1; i > 1; i--)); do + stack_trace+="${FUNCNAME[i]}(${BASH_SOURCE[i]}:${BASH_LINENO[i - 1]}) -> " + done + + # Append the direct caller (without extra " -> " at the end) + stack_trace+="${FUNCNAME[1]}(${BASH_SOURCE[1]}:${BASH_LINENO[0]})" + + # Print the final trace output + TRACE "${stack_trace}" } # Show the entire current call stack in debug output - useful if a catastrophic @@ -750,7 +772,7 @@ prompt_new_owner_password() { # Cache the password externally to be reused by who needs it DEBUG "Caching TPM Owner Password to /tmp/secret/tpm_owner_password" mkdir -p /tmp/secret || die "Unable to create /tmp/secret" - echo -n "$tpm_owner_password" >/tmp/secret/tpm_owner_password || die "Unable to cache TPM password under /tmp/secret" + echo -n "$tpm_owner_password" >/tmp/secret/tpm_owner_password || die "Unable to cache TPM password under /tmp/secret/tpm_owner_password" } check_tpm_counter() { @@ -780,16 +802,21 @@ check_tpm_counter() { # Read the TPM counter value from the TPM. read_tpm_counter() { TRACE_FUNC - tpmr counter_read -ix "$1" | tee "/tmp/counter-$1" >/dev/null 2>&1 || - die "Counter read failed" + if [ ! -e /tmp/counter-"$1" ]; then + DEBUG "Counter file /tmp/counter-$1 not found. Attempting to read from TPM." + DO_WITH_DEBUG tpmr counter_read -ix "$1" | tee /tmp/counter-"$1" >/dev/null 2>&1 || + die "Counter read failed for index $1" + fi + DEBUG "Counter file /tmp/counter-$1 read successfully." } # Increment the TPM counter value in the TPM. increment_tpm_counter() { TRACE_FUNC - tpmr counter_increment -ix "$1" -pwdc '' | - tee /tmp/counter-$1 >/dev/null 2>&1 || - die "TPM counter increment failed for rollback prevention. Please reset the TPM" + DO_WITH_DEBUG tpmr counter_increment -ix "$1" -pwdc '' | + tee /tmp/counter-"$1" >/dev/null 2>&1 || + die "TPM counter increment failed for rollback prevention. Please reset the TPM." + DEBUG "TPM counter incremented successfully for index $1" } # Check detached signature on kexec boot params @@ -804,14 +831,17 @@ check_config() { fi if [ ! -r $1/kexec.sig -a "$CONFIG_BASIC" != "y" ]; then + DEBUG "No $1/kexec.sig found" return fi if [ $(find $1/kexec*.txt | wc -l) -eq 0 ]; then + DEBUG "No $1/kexec*.txt found" return fi if [ "$2" != "force" ]; then + DEBUG "second param: $2 != force" # Note that kexec.sig detached signature is solely verifying kexec*.txt files here! if ! sha256sum $(find $1/kexec*.txt) | gpgv $1/kexec.sig -; then die 'Invalid signature on kexec boot params' @@ -885,10 +915,11 @@ update_checksums() { extparam= if [ "$CONFIG_TPM" = "y" ]; then if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then + DEBUG "add -r to kexec-sign-config since CONFIG_IGNORE_ROLLBACK is not set" extparam=-r fi fi - if ! kexec-sign-config -p /boot -u $extparam; then + if ! DO_WITH_DEBUG kexec-sign-config -p /boot -u $extparam; then rv=1 else rv=0 @@ -996,12 +1027,12 @@ verify_checksums() { set +e -o pipefail local ret=0 cd "$boot_dir" || ret=1 - sha256sum -c "$TMP_HASH_FILE" >/tmp/hash_output || ret=1 + sha256sum -c "$TMP_HASH_FILE" >/tmp/hash_output 2>/dev/null || ret=1 # also make sure that the file & directory structure didn't change # (sha256sum won't detect added files) print_tree >/tmp/tree_output || ret=1 - if ! cmp -s "$TMP_TREE_FILE" /tmp/tree_output &>/dev/null; then + if ! cmp -s "$TMP_TREE_FILE" /tmp/tree_output 2>/dev/null; then ret=1 [[ "$gui" != "y" ]] && exit "$ret" # produce a diff that can safely be presented to the user @@ -1190,7 +1221,6 @@ scan_boot_options() { fi } - # truncate a file to a size only if it is longer (busybox truncate lacks '<' and # always sets the file size) truncate_max_bytes() { @@ -1220,13 +1250,13 @@ fromhex_plain() { print_battery_charge() { local battery battery="$1" - echo "$((100*$(cat "${battery}/charge_now")/$(cat "${battery}/charge_full")))" + echo "$((100 * $(cat "${battery}/charge_now") / $(cat "${battery}/charge_full")))" } print_battery_health() { local battery battery="$1" - echo "$((100*$(cat "${battery}/charge_full")/$(cat "${battery}/charge_full_design")))" + echo "$((100 * $(cat "${battery}/charge_full") / $(cat "${battery}/charge_full_design")))" } print_battery_name() { diff --git a/initrd/sbin/insmod b/initrd/sbin/insmod index b079fcc0c..7ca6a28fe 100755 --- a/initrd/sbin/insmod +++ b/initrd/sbin/insmod @@ -33,9 +33,9 @@ if lsmod | sed 's/_/-/g' | grep -q "^$module_name\\b"; then fi if [ ! -r /sys/class/tpm/tpm0/pcrs -o ! -x /bin/tpm ]; then - if [ ! -c /dev/tpmrm0 -o ! -x /bin/tpm2 ]; then - tpm_missing=1 - fi + if [ ! -c /dev/tpmrm0 -o ! -x /bin/tpm2 ]; then + tpm_missing=1 + fi fi if [ -z "$tpm_missing" ]; then From f9def6ba32f4e9d4f66187ded355b610d29e5969 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Thu, 27 Mar 2025 11:58:59 -0400 Subject: [PATCH 02/21] gui-init: reboot as told to user if he refused to update+sign /boot checksums. Warn user prior of effectively booting (shows console warning, wait 2s then reboot) Signed-off-by: Thierry Laurion --- initrd/bin/gui-init | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 76c42c9a7..7f11af872 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -577,7 +577,8 @@ reset_tpm() { --msgbox "Failed to update checksums / sign default config" 0 80 fi else - die "TPM reset successful, but user chose not to update checksums" + warn "TPM reset successful, but user chose not to update+sign /boot checksums. Rebooting" + reboot fi mount -o ro,remount /boot From b5cb7aee3619c0d39a28ed361f22cf83dc016099 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Fri, 28 Mar 2025 12:11:47 -0400 Subject: [PATCH 03/21] etc/functions: die+warn+INFO: add TRACE_FUNC and TODO to add colors (red,yellow,green) Signed-off-by: Thierry Laurion --- initrd/etc/functions | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/initrd/etc/functions b/initrd/etc/functions index 73b94f718..1e15d61fa 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -3,6 +3,8 @@ # ------- Start of functions coming from /etc/ash_functions die() { + TRACE_FUNC + #TODO: add colors to output, here red for ERROR? if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then echo -e " !!! ERROR: $* !!!" | tee -a /tmp/debug.log /dev/kmsg >/dev/null else @@ -16,6 +18,8 @@ die() { } warn() { + TRACE_FUNC + #TODO: add colors to output, here yellow for WARNING? if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then echo -e " *** WARNING: $* ***" | tee -a /tmp/debug.log /dev/kmsg >/dev/null else @@ -41,6 +45,7 @@ TRACE() { # Function to manage information output level to the console/debug.log INFO() { + TRACE_FUNC #TODO: add colors to output, here green for INFO? # if not CONFIG_QUIET_MODE=y, output to console. If not, output to debug.log From c4b7fefa45bb8f42f52cd9dd5aabcc64f993b5d3 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Fri, 28 Mar 2025 13:29:16 -0400 Subject: [PATCH 04/21] bin/kexec-select-boot/reboot/recovery: reuse reboot+DEBUG conditional prompt for recovery shell access, state where debug logs are in centralized way Note for https://github.com/linuxboot/heads/pull/1888: warn in code is used mostly to actually warn user of something requiring his attention, and pausing for 2 seconds. Goal is: die: blocking: tell user that something failed, requiring acknowledgement for corrective actions. warn: display "WARNING:" prepended messages which pauses for 2 seconds prior of continuing. This is not an error, nor INFO INFO: gives a trace to the user when in QUIET mode, under /tmp/debug.log related to core components output, typically related to measurements traces. Consequently, putting what is currently under warn->INFO wold be console silenced. We want to get rid of manual "echo +++++" messages. So it seems we lack what is currently named INFO to go into measurement_log, and INFO (green), warn (yellow) and die (red) messages to console. Signed-off-by: Thierry Laurion --- initrd/bin/kexec-select-boot | 12 +----------- initrd/bin/reboot | 3 ++- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot index d6b0d77eb..d533d0d82 100755 --- a/initrd/bin/kexec-select-boot +++ b/initrd/bin/kexec-select-boot @@ -314,17 +314,7 @@ user_select() { # continue below to boot the new default option true else - echo "+++ Rebooting to start the new default option" - sleep 2 - if [ "$CONFIG_DEBUG_OUTPUT" != "y" ]; then - reboot || - die "!!! Failed to reboot system" - else - DEBUG "Rebooting is required prior of booting default boot entry" - # Instead of rebooting, drop to a recovery shell - # for a chance to inspect debug output - recovery "Entering recovery to permit inspection of /tmp/debug.log output, reboot to continue" - fi + warn "Rebooting to start the new default option" fi fi diff --git a/initrd/bin/reboot b/initrd/bin/reboot index 90a757865..f78123e05 100755 --- a/initrd/bin/reboot +++ b/initrd/bin/reboot @@ -7,7 +7,8 @@ if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then #Generalize user prompt to continue reboot or go to recovery shell read -r -n 1 -s -p "Press Enter to continue reboot or 'r' to go to recovery shell: " REPLY echo - if [ "$REPLY" = "r" ] || [ "$REPLY" = "R" ]; then + if [ "$REPLY" = "r" ] || [ "$REPLY" = "R" ]; then + warn "Use 'less /tmp/debug.log' to see the debug log content" recovery "Reboot call bypassed to go into recovery shell to debug" fi fi From 0d3b3b678794da7dafb6ca0f2f10ed7618048435 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 31 Mar 2025 15:53:58 -0400 Subject: [PATCH 05/21] WiP Signed-off-by: Thierry Laurion --- initrd/bin/gui-init | 9 +++++-- initrd/bin/kexec-seal-key | 1 + initrd/bin/kexec-select-boot | 2 +- initrd/bin/kexec-sign-config | 12 +++------ initrd/bin/oem-factory-reset | 3 +-- initrd/bin/reboot | 1 - initrd/bin/seal-hotpkey | 26 +++++++++----------- initrd/etc/DEBUG_LOG_COPY_INSTRUCTIONS | 10 ++++++++ initrd/etc/functions | 34 ++++++++++++++++++++------ 9 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 initrd/etc/DEBUG_LOG_COPY_INSTRUCTIONS diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 7f11af872..6596e805a 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -150,16 +150,21 @@ generate_totp_hotp() { TRACE_FUNC tpm_owner_password="$1" # May be empty, will prompt if needed and empty 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 echo "Generating new HOTP secret" - /bin/seal-hotpkey + /bin/seal-hotpkey || + die "Failed to generate HOTP secret" elif echo -e "Generating new TOTP secret...\n\n" && /bin/seal-totp "$BOARD_NAME" "$tpm_owner_password"; then echo if [ -x /bin/hotp_verification ]; then + # If we have a TPM and a HOTP USB Security dongle if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then echo "Once you have scanned the QR code, hit Enter to configure your HOTP USB Security dongle (e.g. Librem Key or Nitrokey)" read fi - /bin/seal-hotpkey + TRACE_FUNC + /bin/seal-hotpkey || die "Failed to generate HOTP secret" else if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then echo "Once you have scanned the QR code, hit Enter to continue" diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 558c579b5..7def73d70 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -81,6 +81,7 @@ for dev in $key_devices ; do echo "++++++ $dev: LUKS device unlocked successfully with the DRK passphrase" luks_drk_passphrase_valid=1 break + #TODO: when multiple LUKS devices are tested, new DUK passphrase prompt interferes with previous echo. FIXME else attempts=$((attempts + 1)) if [ "$attempts" == "3" ] && [ "$luks_drk_passphrase_valid" == "0" ]; then diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot index d533d0d82..333bfad82 100755 --- a/initrd/bin/kexec-select-boot +++ b/initrd/bin/kexec-select-boot @@ -239,7 +239,7 @@ save_default_option() { -i "$option_index" \ ; then echo "+++ Saved defaults to device" - sleep 2 + default_failed="n" force_menu="n" return diff --git a/initrd/bin/kexec-sign-config b/initrd/bin/kexec-sign-config index 8e53702ed..7f655f737 100755 --- a/initrd/bin/kexec-sign-config +++ b/initrd/bin/kexec-sign-config @@ -29,9 +29,6 @@ paramsdir="${paramsdir%%/}" assert_signable TRACE_FUNC -confirm_gpg_card -TRACE_FUNC - # remount /boot as rw mount -o remount,rw /boot @@ -122,11 +119,10 @@ if [ -z "$param_files" ]; then fi for tries in 1 2 3; do - if DO_WITH_DEBUG sha256sum $param_files | gpg \ - --detach-sign \ - -a \ - >$paramsdir/kexec.sig \ - ; then + confirm_gpg_card + TRACE_FUNC + + if DO_WITH_DEBUG sha256sum $param_files | gpg --detach-sign -a >$paramsdir/kexec.sig; then # successful - update the validated params check_config $paramsdir diff --git a/initrd/bin/oem-factory-reset b/initrd/bin/oem-factory-reset index 917a9acb1..713fee1e4 100755 --- a/initrd/bin/oem-factory-reset +++ b/initrd/bin/oem-factory-reset @@ -778,11 +778,10 @@ generate_checksums() { DEBUG "Detach-signing boot files under kexec.sig: ${param_files}" - if sha256sum $param_files 2>/dev/null | gpg \ + if sha256sum $param_files 2>/dev/null | gpg --detach-sign \ --pinentry-mode loopback \ --passphrase-file <(echo -n "$USER_PIN") \ --digest-algo SHA256 \ - --detach-sign \ -a \ >/boot/kexec.sig 2>/tmp/error; then # successful - update the validated params diff --git a/initrd/bin/reboot b/initrd/bin/reboot index f78123e05..b3c8e6530 100755 --- a/initrd/bin/reboot +++ b/initrd/bin/reboot @@ -8,7 +8,6 @@ if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then read -r -n 1 -s -p "Press Enter to continue reboot or 'r' to go to recovery shell: " REPLY echo if [ "$REPLY" = "r" ] || [ "$REPLY" = "R" ]; then - warn "Use 'less /tmp/debug.log' to see the debug log content" recovery "Reboot call bypassed to go into recovery shell to debug" fi fi diff --git a/initrd/bin/seal-hotpkey b/initrd/bin/seal-hotpkey index 6ef5319d5..d5ba46795 100755 --- a/initrd/bin/seal-hotpkey +++ b/initrd/bin/seal-hotpkey @@ -22,14 +22,6 @@ mount_boot() { TRACE_FUNC -fatal_error() { - echo -e "\nERROR: ${1}; press Enter to continue." - read - # get lsusb output for debugging - DEBUG "lsusb output: $(lsusb)" - die "$1" -} - # Use stored HOTP key branding (this might be useful after OEM reset) if [ -r /boot/kexec_hotp_key ]; then HOTPKEY_BRANDING="$(cat /boot/kexec_hotp_key)" @@ -40,7 +32,7 @@ fi if [ "$CONFIG_TPM" = "y" ]; then DEBUG "Sealing HOTP secret reuses TOTP sealed secret..." tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET" || - fatal_error "Unable to unseal HOTP secret" + die "Unable to unseal HOTP secret" else # without a TPM, generate a secret based on the SHA-256 of the ROM secret_from_rom_hash >"$HOTP_SECRET" || die "Reading ROM failed" @@ -66,8 +58,10 @@ counter_value=1 enable_usb +TRACE_FUNC + # Make sure no conflicting GPG related services are running, gpg-agent will respawn -killall gpg-agent scdaemon >/dev/null 2>&1 +DO_WITH_DEBUG killall gpg-agent scdaemon >/dev/null 2>&1 || true # While making sure the key is inserted, capture the status so we can check how # many PIN attempts remain @@ -77,7 +71,7 @@ if ! hotp_token_info="$(hotp_verification info)"; then if ! hotp_token_info="$(hotp_verification info)"; then # don't leak key on failure shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null - fatal_error "Unable to find $HOTPKEY_BRANDING" + die "Unable to find $HOTPKEY_BRANDING" fi fi @@ -90,9 +84,13 @@ else HOTPKEY_BRANDING="HOTP USB Security dongle" fi +DEBUG "HOTP USB Security dongle branding is $HOTPKEY_BRANDING" + # Truncate the secret if it is longer than the maximum HOTP secret truncate_max_bytes 20 "$HOTP_SECRET" +TRACE_FUNC + # Check when the signing key was created to consider trying the default PIN # (Note: we must avoid using gpg --card-status here as the Nitrokey firmware # locks up, https://github.com/Nitrokey/nitrokey-pro-firmware/issues/54) @@ -159,9 +157,9 @@ if [ "$admin_pin_status" -ne 0 ]; then # don't leak key on failure shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null if [ "$HOTPKEY_BRANDING" == "Nitrokey" ]; then - fatal_error "Setting HOTP secret failed, to reset $prompt_message PIN, redo Re-Ownership procedure, use the Nitrokey App 2 or contact Nitrokey support" + die "Setting HOTP secret failed, to reset $prompt_message PIN, redo Re-Ownership procedure, use the Nitrokey App 2 or contact Nitrokey support" else - fatal_error "Setting HOTP secret failed" + die "Setting HOTP secret failed" fi fi fi @@ -183,7 +181,7 @@ mount -o remount,rw /boot counter_value=$(expr $counter_value + 1) echo $counter_value >$HOTP_COUNTER || - fatal_error "Unable to create hotp counter file" + die "Unable to create hotp counter file" # Store/overwrite HOTP USB Security dongle branding found out beforehand echo $HOTPKEY_BRANDING >$HOTP_KEY || diff --git a/initrd/etc/DEBUG_LOG_COPY_INSTRUCTIONS b/initrd/etc/DEBUG_LOG_COPY_INSTRUCTIONS new file mode 100644 index 000000000..cd523f64d --- /dev/null +++ b/initrd/etc/DEBUG_LOG_COPY_INSTRUCTIONS @@ -0,0 +1,10 @@ +Welcome to the Recovery Shell! + +- /tmp/debug.log: contains corresponding log level (Quiet/Info/Debug) debug traces + - Read them locally through: 'less /tmp/debug/log' +- If you faced a bug: + - Preformat/connect a ext3/ext4/fat32/exfat USB thumb drive, and then: + - 'mount-usb --mode rw' # Mounts a connected USB drive in Read+Write mode + - 'cp /tmp/debug.log /media' # copy the log to mounted Read+Write partition under /media + - 'umount /media' # Makes sure buffered write operations are done and "ejects" the USB drive +- Share the debug.log with the developers. diff --git a/initrd/etc/functions b/initrd/etc/functions index 1e15d61fa..9d86445b6 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -169,6 +169,9 @@ confirm_gpg_card() { # setup the USB so we can reach the USB Security dongle's OpenPGP smartcard enable_usb + #TODO: remove this comment: we moved the sleep to enable_usb here + sleep 2 + echo -e "\nVerifying presence of GPG card...\n" # ensure we don't exit without retrying errexit=$(set -o | grep errexit | awk '{print $2}') @@ -187,13 +190,10 @@ confirm_gpg_card() { gpg_output=$(gpg --card-status 2>&1) || die "gpg card read failed" fi - # restore prev errexit state - if [ "$errexit" = "on" ]; then - set -e - fi # Extract and display GPG PIN retry counters # output excerpt: "PIN retry counter : 3 0 3" + gpg_output=$(gpg --card-status 2>&1) pin_retry_counters=$(echo "$gpg_output" | grep 'PIN retry counter' | awk -F': ' '{print $2}') user_pin_retries=$(echo "$pin_retry_counters" | awk '{print $1}') admin_pin_retries=$(echo "$pin_retry_counters" | awk '{print $3}') @@ -204,6 +204,11 @@ confirm_gpg_card() { echo "" warn "Your GPG User PIN, followed by Enter will be required for input at: 'Please unlock the card' next prompt" echo "" + + # restore prev errexit state + if [ "$errexit" = "on" ]; then + set -e + fi } gpg_auth() { @@ -295,8 +300,17 @@ recovery() { #Going to recovery shell should be authenticated if supported gpg_auth + #if we have DEBUG_OUTPUT=y, we instruct users to use the debug log + if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then + cat /etc/DEBUG_LOG_COPY_INSTRUCTIONS + fi + + #Guide user into enabling debug output in case of a discovered bug + if [ "$CONFIG_DEBUG_OUTPUT" != "y" ]; then + #User can enable DEBUG_OUTPUT=y and TRACE_FUNCTION_TRACING_OUTPUT=y from Configuration Options + warn "If you want to file a bug, please enable Debug mode through 'Options --> Change configuration settings > Configure Heads informational'" + fi echo >&2 "!!!!! Starting recovery shell" - sleep 1 if [ -x /bin/setsid ]; then /bin/setsid -c /bin/bash @@ -381,7 +395,8 @@ enable_usb() { insmod /lib/modules/ehci-pci.ko || die "ehci_pci: module load failed" insmod /lib/modules/xhci-hcd.ko || die "xhci_hcd: module load failed" insmod /lib/modules/xhci-pci.ko || die "xhci_pci: module load failed" - sleep 2 + #TODO: get rid of this sleep of 2 seconds each time we call enable_usb!!! + #sleep 2 # For resiliency, test CONFIG_USB_KEYBOARD_REQUIRED explicitly rather # than having it imply CONFIG_USER_USB_KEYBOARD at build time. @@ -631,8 +646,13 @@ reseal_tpm_disk_decryption_key() { done warn "LUKS header hash changed under /boot/kexec_luks_hdr_hash.txt" echo "Updating checksums and signing all files under /boot/kexec.sig" + attempt=1 while ! update_checksums; do - warn "Checksums were not signed. Preceding errors should explain possible causes" + warn "Attempt $attempt: Checksums were not signed. Preceding errors should explain possible causes" + if [ "$attempt" -ge 3 ]; then + die "Failed to sign checksums after 3 attempts" + fi + attempt=$((attempt + 1)) done warn "Rebooting in 3 seconds to enable booting default boot option" sleep 3 From 68da3227b9df0e8e6f540bb2d8f1c8ab69e395fb Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 31 Mar 2025 17:30:46 -0400 Subject: [PATCH 06/21] WiP: tpm2 increment still fails when tpm reseal is done, and past DUK being set: observed in fbwhiptail-tpm2-hotp-prod_quiet 991 root 3272 S {gui-init} /bin/bash /bin/gui-init 2024 root 2792 S {kexec-select-bo} /bin/bash /bin/kexec-select-boot - 2025 root 1364 S sha256sum -c /tmp/kexec/kexec_default_hashes.txt 2105 root 2068 S /bin/bash Signed-off-by: Thierry Laurion --- initrd/bin/kexec-seal-key | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 7def73d70..996747349 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -70,10 +70,9 @@ for dev in $key_devices ; do while [ $attempts -lt 3 ]; do if [ "$luks_drk_passphrase_valid" == "0" ]; then # Ask for the passphrase only once - read -s -p "Enter LUKS Disk Recovery Key (DRK) passphrase that can unlock: $key_devices: " disk_recovery_key_passphrase - #Using he provided passphrase as the DRK "keyfile" for unattended operations + read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices: "$'\n' disk_recovery_key_passphrase + # Using the provided passphrase as the DRK "keyfile" for unattended operations echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" - echo fi DEBUG "Testing $DISK_RECOVERY_KEY_FILE keyfile created from provided passphrase against $dev individual key slots" @@ -98,10 +97,8 @@ done attempts=0 while [ $attempts -lt 3 ]; do - read -s -p "New LUKS TPM Disk Unlock Key (DUK) passphrase for booting: " key_password - echo - read -s -p "Repeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: " key_password2 - echo + read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting:\n' key_password + read -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting:\n' key_password2 if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) if [ "$attempts" == "3" ]; then @@ -182,8 +179,7 @@ for dev in $key_devices; do # Heads expects key slot LUKSv1:7 or LUKSv2:31 to be used for TPM DUK setup. # Ask user to confirm otherwise warn "LUKS key slot $keyslot is not typical ($duk_keyslot expected) for TPM Disk Unlock Key setup" - read -p "Are you sure you want to wipe it? [y/N] " -n 1 -r - echo + read -p $'\nAre you sure you want to wipe it? [y/N]\n' -n 1 -r # If user does not confirm, skip this slot if [[ $REPLY =~ ^[Yy]$ ]]; then wipe_desired="yes" From 03c5d390f6aedf5a83a315f608ef3a46135a9934 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 31 Mar 2025 18:43:50 -0400 Subject: [PATCH 07/21] WiP: so tpm reset+reboot+TPM DUK works. HOTP reseal fails at post DUK. Logs for first under usb.raw to check against HOTP reseal Signed-off-by: Thierry Laurion --- initrd/bin/kexec-select-boot | 1 + 1 file changed, 1 insertion(+) diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot index 333bfad82..456d951d1 100755 --- a/initrd/bin/kexec-select-boot +++ b/initrd/bin/kexec-select-boot @@ -315,6 +315,7 @@ user_select() { true else warn "Rebooting to start the new default option" + reboot fi fi From 6d904803ceda39ba482039eafcf975cfe10a8282 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 28 Apr 2025 14:09:19 -0400 Subject: [PATCH 08/21] kexec-seal-key: force minimal DUK to 12 chars (2 words DICEWARE passphrase equiv) + easthetic fixes Signed-off-by: Thierry Laurion --- initrd/bin/kexec-seal-key | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 996747349..eb972979d 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -65,12 +65,12 @@ DEBUG "$(pcrs)" luks_drk_passphrase_valid=0 -for dev in $key_devices ; do +for dev in $key_devices; do attempts=0 while [ $attempts -lt 3 ]; do if [ "$luks_drk_passphrase_valid" == "0" ]; then # Ask for the passphrase only once - read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices: "$'\n' disk_recovery_key_passphrase + read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"':\n' disk_recovery_key_passphrase # Using the provided passphrase as the DRK "keyfile" for unattended operations echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" fi @@ -80,37 +80,46 @@ for dev in $key_devices ; do echo "++++++ $dev: LUKS device unlocked successfully with the DRK passphrase" luks_drk_passphrase_valid=1 break - #TODO: when multiple LUKS devices are tested, new DUK passphrase prompt interferes with previous echo. FIXME else attempts=$((attempts + 1)) - if [ "$attempts" == "3" ] && [ "$luks_drk_passphrase_valid" == "0" ]; then + if [ "$attempts" == "3" ]; then die "Failed to unlock LUKS device $dev with the provided passphrase. Exiting..." - elif [ "$attempts" != "3" ] && [ "$luks_drk_passphrase_valid" == "1" ]; then - #We failed unlocking with DRK passphrase another LUKS container - die "LUKS device $key_devices cannot all be unlocked with same passphrase. Please make $key_devices devices unlockable with the same passphrase. Exiting" else warn "Failed to unlock LUKS device $dev with the provided passphrase. Please try again." fi fi done + + # Ensure the loop blocks and completes testing for the current device before moving to the next + if [ "$luks_drk_passphrase_valid" == "0" ]; then + die "Unable to unlock all LUKS devices with the provided passphrase. Exiting..." + fi done +MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do - read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting:\n' key_password + #TODO: race condition fix for multiple LUKS devices + read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters):\n' key_password + if [ ${#key_password} -lt $MIN_PASSPHRASE_LENGTH ]; then + attempts=$((attempts + 1)) + warn "Disk Unlock Key (DUK) passphrase is too short. Please try again." + continue + fi + read -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting:\n' key_password2 if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) - if [ "$attempts" == "3" ]; then - die "Disk Unlock Key (DUK) passphrases do not match. Exiting..." - else - warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." - fi + warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." else break fi done +if [ $attempts -ge 3 ]; then + die "Failed to set a valid Disk Unlock Key (DUK) passphrase after 3 attempts. Exiting..." +fi + # Generate key file echo "++++++ Generating new randomized 128 bytes key file that will be sealed/unsealed by LUKS TPM Disk Unlock Key passphrase" dd \ From 4ef7473107d26c90b86d4b59a25dca1568d3573d Mon Sep 17 00:00:00 2001 From: Jonathon Hall Date: Tue, 15 Apr 2025 10:51:26 -0400 Subject: [PATCH 09/21] prompts: Only continue on Enter if we ask to press Enter Asking to press Enter is more forgiving than "any key" and good, but we also have to actually continue on Enter instead of any key. Signed-off-by: Jonathon Hall --- initrd/bin/network-init-recovery | 4 ++-- initrd/bin/reboot | 2 +- initrd/etc/functions | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/initrd/bin/network-init-recovery b/initrd/bin/network-init-recovery index fb09d2ff9..638d2b3b0 100755 --- a/initrd/bin/network-init-recovery +++ b/initrd/bin/network-init-recovery @@ -28,7 +28,7 @@ mobile_tethering() echo "* Linux: Set the wired connection's IPv4 method on the mobile phone to 'Shared to other computers'." echo "Heads supports CDC-NCM and CDC-EEM. Android phones using RNDIS and Apple phones are not supported." echo "" - read -p "Press Enter to continue..." -n 1 -r + read -p "Press Enter to continue..." -r network_modules="mii usbnet cdc_ether cdc_ncm cdc_eem" for module in $(echo $network_modules); do @@ -44,7 +44,7 @@ mobile_tethering() echo "* Android phones requiring RNDIS and Apple phones are not supported." echo "* Make sure the cable used works with data and that the phone has tethering enabled." echo "" - read -p "Press Enter to continue..." -n 1 -r + read -p "Press Enter to continue..." -r fi fi } diff --git a/initrd/bin/reboot b/initrd/bin/reboot index b3c8e6530..8aeea1c6c 100755 --- a/initrd/bin/reboot +++ b/initrd/bin/reboot @@ -5,7 +5,7 @@ TRACE_FUNC if [ "$CONFIG_DEBUG_OUTPUT" = "y" ]; then #Generalize user prompt to continue reboot or go to recovery shell - read -r -n 1 -s -p "Press Enter to continue reboot or 'r' to go to recovery shell: " REPLY + read -r -p "Press Enter to continue reboot or 'r' to go to recovery shell: " REPLY echo if [ "$REPLY" = "r" ] || [ "$REPLY" = "R" ]; then recovery "Reboot call bypassed to go into recovery shell to debug" diff --git a/initrd/etc/functions b/initrd/etc/functions index 1ee96a4c9..0a869ba1f 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -11,8 +11,8 @@ die() { echo -e "!!! ERROR: $* !!!" >&2 fi - # ask user to press any key prior to exit - read -n 1 -s -r -p $'Press Enter to continue...\n\n' + # ask user to press Enter prior to exit + read -r -p $'Press Enter to continue...\n\n' exit 1 } From d9730bed1fc8985b7f6bf42628e1aa7f88ec7be3 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 28 Apr 2025 16:21:47 -0400 Subject: [PATCH 10/21] kexec-select-boot+functions : Use NOTE instead of warn, and have NOTE sleep one second before continuing Signed-off-by: Thierry Laurion --- initrd/bin/kexec-select-boot | 2 +- initrd/etc/functions | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/initrd/bin/kexec-select-boot b/initrd/bin/kexec-select-boot index 292e037ee..405713934 100755 --- a/initrd/bin/kexec-select-boot +++ b/initrd/bin/kexec-select-boot @@ -314,7 +314,7 @@ user_select() { # continue below to boot the new default option true else - warn "Rebooting to start the new default option" + NOTE "Rebooting to start the new default option" reboot fi fi diff --git a/initrd/etc/functions b/initrd/etc/functions index 0a869ba1f..758d8b3cd 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -62,6 +62,7 @@ TRACE() { # doc/logging.md. NOTE() { echo "NOTE:" "$@" | tee -a /tmp/debug.log + sleep 1 } # Use INFO for contextual information that might make sense to non-developers, From 9c7ef3f821b714ef59a06cfd3343851612879b60 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Tue, 29 Apr 2025 12:18:54 -0400 Subject: [PATCH 11/21] kexec-seal-key: have proper spacing before and after console read prompts Signed-off-by: Thierry Laurion --- initrd/bin/kexec-seal-key | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index eb972979d..df4498a6c 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -70,7 +70,9 @@ for dev in $key_devices; do while [ $attempts -lt 3 ]; do if [ "$luks_drk_passphrase_valid" == "0" ]; then # Ask for the passphrase only once - read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"':\n' disk_recovery_key_passphrase + echo "" + read -s -p "Enter LUKS Disk Recovery Key (DRK) passphrase that can unlock $key_devices: " disk_recovery_key_passphrase + echo "" # Using the provided passphrase as the DRK "keyfile" for unattended operations echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" fi @@ -100,14 +102,19 @@ MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do #TODO: race condition fix for multiple LUKS devices - read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters):\n' key_password + echo "" + read -s -p "New LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum $MIN_PASSPHRASE_LENGTH characters): " key_password + echo "" if [ ${#key_password} -lt $MIN_PASSPHRASE_LENGTH ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrase is too short. Please try again." continue fi - read -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting:\n' key_password2 + echo "" + read -s -p "Repeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: " key_password2 + echo "" + echo "" if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." @@ -188,7 +195,9 @@ for dev in $key_devices; do # Heads expects key slot LUKSv1:7 or LUKSv2:31 to be used for TPM DUK setup. # Ask user to confirm otherwise warn "LUKS key slot $keyslot is not typical ($duk_keyslot expected) for TPM Disk Unlock Key setup" - read -p $'\nAre you sure you want to wipe it? [y/N]\n' -n 1 -r + echo "" + read -p "Are you sure you want to wipe it? [y/N] " -n 1 -r + echo "" # If user does not confirm, skip this slot if [[ $REPLY =~ ^[Yy]$ ]]; then wipe_desired="yes" From d10a4244ae5e6135a033149e86ea734b01b409be Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Tue, 29 Apr 2025 13:10:09 -0400 Subject: [PATCH 12/21] kexec-seal-key: make sure TPM DRK passphrase is verified to unlock all selected containers prior of prompting for new DUK Signed-off-by: Thierry Laurion --- initrd/bin/kexec-seal-key | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index df4498a6c..df697b4fe 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -63,45 +63,47 @@ fi DEBUG "$(pcrs)" - +# First, collect all the LUKS devices that need to be tested luks_drk_passphrase_valid=0 -for dev in $key_devices; do - attempts=0 - while [ $attempts -lt 3 ]; do - if [ "$luks_drk_passphrase_valid" == "0" ]; then - # Ask for the passphrase only once - echo "" - read -s -p "Enter LUKS Disk Recovery Key (DRK) passphrase that can unlock $key_devices: " disk_recovery_key_passphrase - echo "" - # Using the provided passphrase as the DRK "keyfile" for unattended operations - echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" - fi +attempts=0 - DEBUG "Testing $DISK_RECOVERY_KEY_FILE keyfile created from provided passphrase against $dev individual key slots" - if cryptsetup open $dev --test-passphrase --key-file "$DISK_RECOVERY_KEY_FILE" >/dev/null 2>&1; then - echo "++++++ $dev: LUKS device unlocked successfully with the DRK passphrase" - luks_drk_passphrase_valid=1 +# Ask for the DRK passphrase first, before testing any devices +while [ $attempts -lt 3 ] && [ $luks_drk_passphrase_valid -eq 0 ]; do + echo "" + read -s -p "Enter LUKS Disk Recovery Key (DRK) passphrase that can unlock $key_devices: " disk_recovery_key_passphrase + echo "" + echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" + + # Test the passphrase against ALL devices before deciding if it's valid + all_devices_unlocked=1 + + for dev in $key_devices; do + DEBUG "Testing $DISK_RECOVERY_KEY_FILE keyfile against $dev" + if ! cryptsetup open $dev --test-passphrase --key-file "$DISK_RECOVERY_KEY_FILE" >/dev/null 2>&1; then + warn "Failed to unlock LUKS device $dev with the provided passphrase." + all_devices_unlocked=0 break else - attempts=$((attempts + 1)) - if [ "$attempts" == "3" ]; then - die "Failed to unlock LUKS device $dev with the provided passphrase. Exiting..." - else - warn "Failed to unlock LUKS device $dev with the provided passphrase. Please try again." - fi + echo "++++++ $dev: LUKS device unlocked successfully with the DRK passphrase" fi done - # Ensure the loop blocks and completes testing for the current device before moving to the next - if [ "$luks_drk_passphrase_valid" == "0" ]; then - die "Unable to unlock all LUKS devices with the provided passphrase. Exiting..." + if [ $all_devices_unlocked -eq 1 ]; then + luks_drk_passphrase_valid=1 + else + attempts=$((attempts + 1)) + if [ $attempts -eq 3 ]; then + die "Failed to unlock all LUKS devices with the provided passphrase after 3 attempts. Exiting..." + else + warn "Please try again." + fi fi done +# Now that all devices are verified with the DRK passphrase, proceed with DUK setup MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do - #TODO: race condition fix for multiple LUKS devices echo "" read -s -p "New LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum $MIN_PASSPHRASE_LENGTH characters): " key_password echo "" @@ -173,7 +175,7 @@ for dev in $key_devices; do # Get all the key slots that are used on $dev luks_used_keyslots=($(cryptsetup luksDump "$dev" | grep -E "$regex" | sed "$sed_command")) DEBUG "$dev LUKS key slots: ${luks_used_keyslots[*]}" - + #Find the key slot that can be unlocked with the provided passphrase drk_key_slot=$(find_drk_key_slot) @@ -195,8 +197,7 @@ for dev in $key_devices; do # Heads expects key slot LUKSv1:7 or LUKSv2:31 to be used for TPM DUK setup. # Ask user to confirm otherwise warn "LUKS key slot $keyslot is not typical ($duk_keyslot expected) for TPM Disk Unlock Key setup" - echo "" - read -p "Are you sure you want to wipe it? [y/N] " -n 1 -r + read -p $'Are you sure you want to wipe it? [y/N]\n' -n 1 -r echo "" # If user does not confirm, skip this slot if [[ $REPLY =~ ^[Yy]$ ]]; then @@ -218,7 +219,6 @@ for dev in $key_devices; do fi done - echo "++++++ $dev: Adding LUKS TPM Disk Unlock Key to LUKS key slot $duk_keyslot" DO_WITH_DEBUG cryptsetup luksAddKey \ --key-file "$DISK_RECOVERY_KEY_FILE" \ From 19d400a69ba33198d931994d3e82f6ab193166e8 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Tue, 29 Apr 2025 14:02:10 -0400 Subject: [PATCH 13/21] /etc/functions: simplify logic of increment_tpm_counter to ease its understanding and debugging Signed-off-by: Thierry Laurion --- initrd/etc/functions | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/initrd/etc/functions b/initrd/etc/functions index 758d8b3cd..12b48f12e 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -858,10 +858,32 @@ read_tpm_counter() { # Increment the TPM counter value in the TPM. increment_tpm_counter() { TRACE_FUNC - DO_WITH_DEBUG tpmr counter_increment -ix "$1" -pwdc '' | - tee /tmp/counter-"$1" >/dev/null 2>&1 || - die "TPM counter increment failed for rollback prevention. Please reset the TPM." - DEBUG "TPM counter incremented successfully for index $1" + local counter_id="$1" + + # Check if counter exists by reading it first + if ! DO_WITH_DEBUG tpmr counter_read -ix "$counter_id" >/tmp/counter-check 2>/dev/null; then + DEBUG "TPM counter $counter_id could not be read before incrementing" + # Continue with increment attempt anyway to get detailed error messages + else + DEBUG "TPM counter $counter_id exists and was read successfully" + fi + + # Try to increment the counter + if ! DO_WITH_DEBUG tpmr counter_increment -ix "$counter_id" -pwdc '' | + tee /tmp/counter-"$counter_id" >/dev/null 2>&1; then + + # Check if we need to create a new counter + DEBUG "TPM counter increment failed. Attempting to create a new counter..." + + if DO_WITH_DEBUG tpmr counter_create -pwdc '' -la 3135106223 >/tmp/new-counter 2>/dev/null; then + NEW_COUNTER=$(cut -d: -f1 Date: Tue, 29 Apr 2025 17:10:04 -0400 Subject: [PATCH 14/21] etc/functions: NOTE echoes to console before and after its message and leaves 1 second to the user to read the notice Signed-off-by: Thierry Laurion --- initrd/etc/functions | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/initrd/etc/functions b/initrd/etc/functions index 12b48f12e..7b967007d 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -61,7 +61,14 @@ TRACE() { # Don't overuse this - too much NOTE output will cause users to ignore it. See # doc/logging.md. NOTE() { + #TODO: add colors to output, here blue for NOTE? + + # Make sure the user sees this message: seperate it from the rest of the output + echo echo "NOTE:" "$@" | tee -a /tmp/debug.log + echo + + # Sleep for a second to give the user time to read the message sleep 1 } From 19289953043949e9817ae4b820306f8c1b21a11b Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 23 Jun 2025 15:06:44 -0400 Subject: [PATCH 15/21] initrd/etc/functions: fix TPM counter newline presence/stripping Tested under QEMU - wipe of /boot/kexec_* - TPM reset + boot default + define default + TPM DUK - remove qemu *.rom files (so keyring injected is unique and triggers TPM unseal error on boot) - Reseal TPMTOTP+HOTP succeeds giving debug output of TPM counter increment succeeding - comparing hashes under /boot/kexec_rollback.txt validates TPM increment works and is validated (rollback is to prevent copying old kexec*.txt + kexec.sig under /boot) Signed-off-by: Thierry Laurion --- initrd/bin/kexec-sign-config | 2 +- initrd/etc/functions | 38 +++++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/initrd/bin/kexec-sign-config b/initrd/bin/kexec-sign-config index 7f655f737..a3f1a7c32 100755 --- a/initrd/bin/kexec-sign-config +++ b/initrd/bin/kexec-sign-config @@ -85,7 +85,7 @@ if [ "$rollback" = "y" ]; then die "$paramsdir: Unable to find/create tpm counter" TRACE_FUNC - TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || + die "Unable to create TPM counter" + TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || die "Unable to create TPM counter" - TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || - die "Counter read failed for index $1" + local counter_id + counter_id="$(echo "$1" | tr -d '\n')" + if [ ! -e /tmp/counter-"$counter_id" ]; then + DEBUG "Counter file /tmp/counter-$counter_id not found. Attempting to read from TPM." + DO_WITH_DEBUG tpmr counter_read -ix "$counter_id" | tee /tmp/counter-"$counter_id" >/dev/null 2>&1 || + die "Counter read failed for index $counter_id" fi - DEBUG "Counter file /tmp/counter-$1 read successfully." + DEBUG "Counter file /tmp/counter-$counter_id read successfully." } -# Increment the TPM counter value in the TPM. increment_tpm_counter() { TRACE_FUNC - local counter_id="$1" + local counter_id + counter_id="$(echo "$1" | tr -d '\n')" # Check if counter exists by reading it first if ! DO_WITH_DEBUG tpmr counter_read -ix "$counter_id" >/tmp/counter-check 2>/dev/null; then @@ -883,7 +899,7 @@ increment_tpm_counter() { DEBUG "TPM counter increment failed. Attempting to create a new counter..." if DO_WITH_DEBUG tpmr counter_create -pwdc '' -la 3135106223 >/tmp/new-counter 2>/dev/null; then - NEW_COUNTER=$(cut -d: -f1 Date: Mon, 30 Jun 2025 11:26:28 -0400 Subject: [PATCH 16/21] initrd/etc/functions: silence output of verify_checksums + escape_zero when no previous file exists Signed-off-by: Thierry Laurion --- initrd/etc/functions | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/initrd/etc/functions b/initrd/etc/functions index 77d7219f3..8a89c49d2 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -1128,9 +1128,17 @@ verify_checksums() { # produce a diff that can safely be presented to the user # this is relatively hard as file names may e.g. contain backslashes etc., # which are interpreted by whiptail, less, ... - escape_zero "(new) " <"$TMP_TREE_FILE" >"${TMP_TREE_FILE}.user" - escape_zero "(new) " /tmp/tree_output.user - diff "${TMP_TREE_FILE}.user" /tmp/tree_output.user | grep -E '^\+\(new\).*$' | sed -r 's/^\+\(new\)/(new)/g' >>/tmp/hash_output + if [ -r "$TMP_TREE_FILE" ]; then + escape_zero "(new) " <"$TMP_TREE_FILE" >"${TMP_TREE_FILE}.user" 2>/dev/null + else + touch "${TMP_TREE_FILE}.user" + fi + if [ -r /tmp/tree_output ]; then + escape_zero "(new) " /tmp/tree_output.user 2>/dev/null + else + touch /tmp/tree_output.user + fi + diff "${TMP_TREE_FILE}.user" /tmp/tree_output.user 2>/dev/null | grep -E '^\+\(new\).*$' | sed -r 's/^\+\(new\)/(new)/g' >>/tmp/hash_output 2>/dev/null rm -f "${TMP_TREE_FILE}.user" rm -f /tmp/tree_output.user fi From b95ffcce25b1ae431f7060d7be31de70c8e318f6 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Mon, 30 Jun 2025 18:49:19 -0400 Subject: [PATCH 17/21] initrd/etc/functions: check_tpm_counter(), remove unused code, add warning to user that he will be prompted for TPM Owner Password to set new TPM counter for /boot content rollback prevention Signed-off-by: Thierry Laurion --- initrd/etc/functions | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/initrd/etc/functions b/initrd/etc/functions index 8a89c49d2..d31cb7751 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -839,18 +839,13 @@ check_tpm_counter() { # Robustly extract the first hex string after 'counter-' on any line TPM_COUNTER=$(grep -Eo 'counter-[0-9a-fA-F]+' "$1" | sed -n 's/counter-//p' | head -n1 | tr -d '\n') DEBUG "Extracted TPM_COUNTER: '$TPM_COUNTER' from $1" - if [ -z "$TPM_COUNTER" ]; then - INFO "$1 exists but no valid TPM counter found; creating new TPM counter" - tpmr counter_create \ - -pwdc '' \ - -la $LABEL | - tee /tmp/counter >/dev/null 2>&1 || - die "Unable to create TPM counter" - TPM_COUNTER=$(cut -d: -f1 Date: Wed, 2 Jul 2025 13:44:37 -0400 Subject: [PATCH 18/21] codebase: unify all 'read' prompts to inject newline prior of asking for input Signed-off-by: Thierry Laurion --- initrd/bin/kexec-seal-key | 13 +++---------- initrd/bin/kexec-unseal-key | 2 +- initrd/bin/seal-hotpkey | 8 ++------ initrd/etc/functions | 11 ++++------- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index df697b4fe..218906de8 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -69,9 +69,7 @@ attempts=0 # Ask for the DRK passphrase first, before testing any devices while [ $attempts -lt 3 ] && [ $luks_drk_passphrase_valid -eq 0 ]; do - echo "" - read -s -p "Enter LUKS Disk Recovery Key (DRK) passphrase that can unlock $key_devices: " disk_recovery_key_passphrase - echo "" + read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"': ' disk_recovery_key_passphrase echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" # Test the passphrase against ALL devices before deciding if it's valid @@ -104,19 +102,14 @@ done MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do - echo "" - read -s -p "New LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum $MIN_PASSPHRASE_LENGTH characters): " key_password - echo "" + read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters): ' key_password if [ ${#key_password} -lt $MIN_PASSPHRASE_LENGTH ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrase is too short. Please try again." continue fi - echo "" - read -s -p "Repeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: " key_password2 - echo "" - echo "" + read -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: ' key_password2 if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." diff --git a/initrd/bin/kexec-unseal-key b/initrd/bin/kexec-unseal-key index 346eda9b8..1252ca333 100755 --- a/initrd/bin/kexec-unseal-key +++ b/initrd/bin/kexec-unseal-key @@ -26,7 +26,7 @@ DEBUG "Show PCRs" DEBUG "$(pcrs)" for tries in 1 2 3; do - read -s -p "Enter LUKS TPM Disk Unlock Key passphrase (blank to abort): " tpm_password + read -s -p $'\nEnter LUKS TPM Disk Unlock Key passphrase (blank to abort): ' tpm_password echo if [ -z "$tpm_password" ]; then die "Aborting unseal disk encryption key" diff --git a/initrd/bin/seal-hotpkey b/initrd/bin/seal-hotpkey index d5ba46795..868ce85a6 100755 --- a/initrd/bin/seal-hotpkey +++ b/initrd/bin/seal-hotpkey @@ -144,15 +144,11 @@ fi if [ "$admin_pin_status" -ne 0 ]; then # prompt user for PIN and retry - echo "" - read -s -p "Enter your $HOTPKEY_BRANDING $prompt_message PIN: " admin_pin - echo -e "\n" + read -s -p $'\nEnter your '"$HOTPKEY_BRANDING $prompt_message"' PIN: ' admin_pin hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING" if [ $? -ne 0 ]; then - echo -e "\n" - read -s -p "Error setting HOTP secret, re-enter $prompt_message PIN and try again: " admin_pin - echo -e "\n" + read -s -p $'\nError setting HOTP secret, re-enter '"$prompt_message"' PIN and try again: ' admin_pin if ! hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING"; then # don't leak key on failure shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null diff --git a/initrd/etc/functions b/initrd/etc/functions index d31cb7751..d16a8548f 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -171,8 +171,7 @@ confirm_gpg_card() { gpg_admin_pin="" while [ -z "$gpg_admin_pin" ]; do #TODO: change all passphrase prompts in codebase to include -r to prevent backslash escapes - read -r -s -p "Please enter GPG Admin PIN needed to use the GPG backup thumb drive: " gpg_admin_pin - echo + read -r -s -p $'\nPlease enter GPG Admin PIN needed to use the GPG backup thumb drive: ' gpg_admin_pin done #prompt user to select the proper encrypted partition, which should the first one on next prompt warn "Please select encrypted LUKS on GPG key material backup thumb drive (not public labeled one)" @@ -790,8 +789,7 @@ prompt_tpm_owner_password() { return 0 fi - read -s -p "TPM Owner Password: " tpm_owner_password - echo # new line after password prompt + read -s -p $'\nTPM Owner Password: ' tpm_owner_password # Cache the password externally to be reused by who needs it DEBUG "Caching TPM Owner Password to /tmp/secret/tpm_owner_password" @@ -809,10 +807,9 @@ prompt_new_owner_password() { tpm_owner_password=1 tpm_owner_password2=2 while [ "$tpm_owner_password" != "$tpm_owner_password2" ] || [ "${#tpm_owner_password}" -gt 32 ] || [ -z "$tpm_owner_password" ]; do - read -s -p "New TPM Owner Password (2 words suggested, 1-32 characters max): " tpm_owner_password - echo + read -s -p $'\nNew TPM Owner Password (2 words suggested, 1-32 characters max): ' tpm_owner_password - read -s -p "Repeat chosen TPM Owner Password: " tpm_owner_password2 + read -s -p $'\nRepeat chosen TPM Owner Password: ' tpm_owner_password2 echo if [ "$tpm_owner_password" != "$tpm_owner_password2" ]; then From f47529697be00188fbfdc641367d5d1c33e45867 Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Wed, 2 Jul 2025 14:55:40 -0400 Subject: [PATCH 19/21] codebase: unify all 'read' prompts to add -r, so that backslackes in answers are escaped prior of being put in variable (remove TODO) Signed-off-by: Thierry Laurion --- initrd/bin/kexec-boot | 2 +- initrd/bin/kexec-seal-key | 6 +++--- initrd/bin/kexec-unseal-key | 2 +- initrd/bin/seal-hotpkey | 4 ++-- initrd/etc/functions | 7 +++---- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/initrd/bin/kexec-boot b/initrd/bin/kexec-boot index fa37ebf99..62338c386 100755 --- a/initrd/bin/kexec-boot +++ b/initrd/bin/kexec-boot @@ -158,7 +158,7 @@ DO_WITH_DEBUG eval "$kexeccmd" 2>/dev/null \ if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then #Ask user if they want to continue booting without echoing back the input (-s) - read -s -n 1 -p "[DEBUG] Continue booting? [Y/n]: " debug_boot_confirm + read -r -s -n 1 -p "[DEBUG] Continue booting? [Y/n]: " debug_boot_confirm echo if [ "${debug_boot_confirm^^}" = N ]; then # abort diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 218906de8..0c3ffc2d3 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -69,7 +69,7 @@ attempts=0 # Ask for the DRK passphrase first, before testing any devices while [ $attempts -lt 3 ] && [ $luks_drk_passphrase_valid -eq 0 ]; do - read -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"': ' disk_recovery_key_passphrase + read -r -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"': ' disk_recovery_key_passphrase echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" # Test the passphrase against ALL devices before deciding if it's valid @@ -102,14 +102,14 @@ done MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do - read -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters): ' key_password + read -r -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters): ' key_password if [ ${#key_password} -lt $MIN_PASSPHRASE_LENGTH ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrase is too short. Please try again." continue fi - read -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: ' key_password2 + read -r -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: ' key_password2 if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." diff --git a/initrd/bin/kexec-unseal-key b/initrd/bin/kexec-unseal-key index 1252ca333..3573d250c 100755 --- a/initrd/bin/kexec-unseal-key +++ b/initrd/bin/kexec-unseal-key @@ -26,7 +26,7 @@ DEBUG "Show PCRs" DEBUG "$(pcrs)" for tries in 1 2 3; do - read -s -p $'\nEnter LUKS TPM Disk Unlock Key passphrase (blank to abort): ' tpm_password + read -r -s -p $'\nEnter LUKS TPM Disk Unlock Key passphrase (blank to abort): ' tpm_password echo if [ -z "$tpm_password" ]; then die "Aborting unseal disk encryption key" diff --git a/initrd/bin/seal-hotpkey b/initrd/bin/seal-hotpkey index 868ce85a6..9767f5675 100755 --- a/initrd/bin/seal-hotpkey +++ b/initrd/bin/seal-hotpkey @@ -144,11 +144,11 @@ fi if [ "$admin_pin_status" -ne 0 ]; then # prompt user for PIN and retry - read -s -p $'\nEnter your '"$HOTPKEY_BRANDING $prompt_message"' PIN: ' admin_pin + read -r -s -p $'\nEnter your '"$HOTPKEY_BRANDING $prompt_message"' PIN: ' admin_pin hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING" if [ $? -ne 0 ]; then - read -s -p $'\nError setting HOTP secret, re-enter '"$prompt_message"' PIN and try again: ' admin_pin + read -r -s -p $'\nError setting HOTP secret, re-enter '"$prompt_message"' PIN and try again: ' admin_pin if ! hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING"; then # don't leak key on failure shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null diff --git a/initrd/etc/functions b/initrd/etc/functions index d16a8548f..56b49b365 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -170,7 +170,6 @@ confirm_gpg_card() { echo gpg_admin_pin="" while [ -z "$gpg_admin_pin" ]; do - #TODO: change all passphrase prompts in codebase to include -r to prevent backslash escapes read -r -s -p $'\nPlease enter GPG Admin PIN needed to use the GPG backup thumb drive: ' gpg_admin_pin done #prompt user to select the proper encrypted partition, which should the first one on next prompt @@ -789,7 +788,7 @@ prompt_tpm_owner_password() { return 0 fi - read -s -p $'\nTPM Owner Password: ' tpm_owner_password + read -r -s -p $'\nTPM Owner Password: ' tpm_owner_password # Cache the password externally to be reused by who needs it DEBUG "Caching TPM Owner Password to /tmp/secret/tpm_owner_password" @@ -807,9 +806,9 @@ prompt_new_owner_password() { tpm_owner_password=1 tpm_owner_password2=2 while [ "$tpm_owner_password" != "$tpm_owner_password2" ] || [ "${#tpm_owner_password}" -gt 32 ] || [ -z "$tpm_owner_password" ]; do - read -s -p $'\nNew TPM Owner Password (2 words suggested, 1-32 characters max): ' tpm_owner_password + read -r -s -p $'\nNew TPM Owner Password (2 words suggested, 1-32 characters max): ' tpm_owner_password - read -s -p $'\nRepeat chosen TPM Owner Password: ' tpm_owner_password2 + read -r -s -p $'\nRepeat chosen TPM Owner Password: ' tpm_owner_password2 echo if [ "$tpm_owner_password" != "$tpm_owner_password2" ]; then From 21adb0d67681b7b00dfcfcd9478f24ebdf61ecaa Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Wed, 2 Jul 2025 17:02:01 -0400 Subject: [PATCH 20/21] codebase: fix console echo needed accordingly to previous read changes Signed-off-by: Thierry Laurion --- initrd/bin/kexec-boot | 2 +- initrd/bin/kexec-seal-key | 4 ++++ initrd/bin/seal-hotpkey | 3 ++- initrd/etc/functions | 13 +++++-------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/initrd/bin/kexec-boot b/initrd/bin/kexec-boot index 62338c386..fa37ebf99 100755 --- a/initrd/bin/kexec-boot +++ b/initrd/bin/kexec-boot @@ -158,7 +158,7 @@ DO_WITH_DEBUG eval "$kexeccmd" 2>/dev/null \ if [ "$CONFIG_DEBUG_OUTPUT" = "y" ];then #Ask user if they want to continue booting without echoing back the input (-s) - read -r -s -n 1 -p "[DEBUG] Continue booting? [Y/n]: " debug_boot_confirm + read -s -n 1 -p "[DEBUG] Continue booting? [Y/n]: " debug_boot_confirm echo if [ "${debug_boot_confirm^^}" = N ]; then # abort diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 0c3ffc2d3..5cda58bbf 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -70,6 +70,7 @@ attempts=0 # Ask for the DRK passphrase first, before testing any devices while [ $attempts -lt 3 ] && [ $luks_drk_passphrase_valid -eq 0 ]; do read -r -s -p $'\nEnter LUKS Disk Recovery Key (DRK) passphrase that can unlock '"$key_devices"': ' disk_recovery_key_passphrase + echo echo -n "$disk_recovery_key_passphrase" >"$DISK_RECOVERY_KEY_FILE" # Test the passphrase against ALL devices before deciding if it's valid @@ -103,6 +104,7 @@ MIN_PASSPHRASE_LENGTH=12 attempts=0 while [ $attempts -lt 3 ]; do read -r -s -p $'\nNew LUKS TPM Disk Unlock Key (DUK) passphrase for booting (minimum '"$MIN_PASSPHRASE_LENGTH"' characters): ' key_password + echo if [ ${#key_password} -lt $MIN_PASSPHRASE_LENGTH ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrase is too short. Please try again." @@ -110,6 +112,7 @@ while [ $attempts -lt 3 ]; do fi read -r -s -p $'\nRepeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: ' key_password2 + echo if [ "$key_password" != "$key_password2" ]; then attempts=$((attempts + 1)) warn "Disk Unlock Key (DUK) passphrases do not match. Please try again." @@ -123,6 +126,7 @@ if [ $attempts -ge 3 ]; then fi # Generate key file +echo echo "++++++ Generating new randomized 128 bytes key file that will be sealed/unsealed by LUKS TPM Disk Unlock Key passphrase" dd \ if=/dev/urandom \ diff --git a/initrd/bin/seal-hotpkey b/initrd/bin/seal-hotpkey index 9767f5675..271547b97 100755 --- a/initrd/bin/seal-hotpkey +++ b/initrd/bin/seal-hotpkey @@ -145,10 +145,11 @@ if [ "$admin_pin_status" -ne 0 ]; then # prompt user for PIN and retry read -r -s -p $'\nEnter your '"$HOTPKEY_BRANDING $prompt_message"' PIN: ' admin_pin - + echo hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING" if [ $? -ne 0 ]; then read -r -s -p $'\nError setting HOTP secret, re-enter '"$prompt_message"' PIN and try again: ' admin_pin + echo if ! hotp_initialize "$admin_pin" $HOTP_SECRET $counter_value "$HOTPKEY_BRANDING"; then # don't leak key on failure shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null diff --git a/initrd/etc/functions b/initrd/etc/functions index 56b49b365..c1da2f721 100644 --- a/initrd/etc/functions +++ b/initrd/etc/functions @@ -142,10 +142,7 @@ confirm_gpg_card() { message="Please confirm that your GPG card is inserted [Y/n]: " fi - read \ - -n 1 \ - -p "$message" \ - card_confirm + read -r -n 1 -p $'\n'"$message" card_confirm echo if [ "$card_confirm" != "y" \ @@ -167,10 +164,10 @@ confirm_gpg_card() { shred -n 10 -z -u "$CR_NONCE" "$CR_SIG" >/dev/null 2>&1 || true #Prompt user for configured GPG Admin PIN that will be passed along to mount-usb and to import gpg subkeys - echo gpg_admin_pin="" while [ -z "$gpg_admin_pin" ]; do read -r -s -p $'\nPlease enter GPG Admin PIN needed to use the GPG backup thumb drive: ' gpg_admin_pin + echo done #prompt user to select the proper encrypted partition, which should the first one on next prompt warn "Please select encrypted LUKS on GPG key material backup thumb drive (not public labeled one)" @@ -789,6 +786,7 @@ prompt_tpm_owner_password() { fi read -r -s -p $'\nTPM Owner Password: ' tpm_owner_password + echo # Cache the password externally to be reused by who needs it DEBUG "Caching TPM Owner Password to /tmp/secret/tpm_owner_password" @@ -807,14 +805,13 @@ prompt_new_owner_password() { tpm_owner_password2=2 while [ "$tpm_owner_password" != "$tpm_owner_password2" ] || [ "${#tpm_owner_password}" -gt 32 ] || [ -z "$tpm_owner_password" ]; do read -r -s -p $'\nNew TPM Owner Password (2 words suggested, 1-32 characters max): ' tpm_owner_password - read -r -s -p $'\nRepeat chosen TPM Owner Password: ' tpm_owner_password2 - echo if [ "$tpm_owner_password" != "$tpm_owner_password2" ]; then - echo "Passphrases entered do not match. Try again!" echo + echo "Passphrases entered do not match. Try again!" fi + echo done # Cache the password externally to be reused by who needs it From 561126c233d2ad78b962262be08e183bd96e03ff Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Thu, 20 Nov 2025 16:27:42 -0500 Subject: [PATCH 21/21] gui-init: remove TODO fixed in this PR Signed-off-by: Thierry Laurion --- initrd/bin/gui-init | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/initrd/bin/gui-init b/initrd/bin/gui-init index 6596e805a..7eedc4757 100755 --- a/initrd/bin/gui-init +++ b/initrd/bin/gui-init @@ -564,12 +564,11 @@ reset_tpm() { TPM_COUNTER=$(cut -d: -f1 /dev/null 2>&1 || die "Unable to increment tpm counter" - #TODO: should this be here? DO_WITH_DEBUG sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt || die "Unable to create rollback file"