Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fde-tools for openSUSE:Factory checked in at 2026-09-23 14:34:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fde-tools (Old) and /work/SRC/openSUSE:Factory/.fde-tools.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fde-tools" Wed Sep 23 14:34:13 2026 rev:38 rq:1379413 version:0.7.9 Changes: -------- --- /work/SRC/openSUSE:Factory/fde-tools/fde-tools.changes 2026-08-12 16:10:59.926657927 +0200 +++ /work/SRC/openSUSE:Factory/.fde-tools.new.383539/fde-tools.changes 2026-09-23 14:35:33.428851049 +0200 @@ -1,0 +2,11 @@ +Mon Sep 21 07:03:14 UTC 2026 - Gary Ching-Pang Lin <[email protected]> + +- Update to 0.7.9 + + Handle the return value of pcr-oracle when signing the key + (bsc#1279917) + + Properly detect the sealed key in tpm-activate/tpm-enable + (bsc#1279917) + + search for fde password also in EFI grub.cfg + + ensure keyfile is not dropped if reencryption failed + +------------------------------------------------------------------- Old: ---- fde-tools-0.7.7.tar.bz2 New: ---- fde-tools-0.7.9.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fde-tools.spec ++++++ --- /var/tmp/diff_new_pack.Gjy6Pk/_old 2026-09-23 14:35:34.046876604 +0200 +++ /var/tmp/diff_new_pack.Gjy6Pk/_new 2026-09-23 14:35:34.048876687 +0200 @@ -21,7 +21,7 @@ %endif Name: fde-tools -Version: 0.7.7 +Version: 0.7.9 Release: 0 Summary: Tools required for Full Disk Encryption License: GPL-2.0-only ++++++ fde-tools-0.7.7.tar.bz2 -> fde-tools-0.7.9.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/fde.sh new/fde-tools-0.7.9/fde.sh --- old/fde-tools-0.7.7/fde.sh 2026-08-11 09:15:24.667343794 +0200 +++ new/fde-tools-0.7.9/fde.sh 2026-09-21 09:01:35.111879156 +0200 @@ -22,7 +22,7 @@ : ${SHAREDIR:=/usr/share/fde} -version=0.7.7 +version=0.7.9 opt_bootloader=grub2 opt_uefi_bootdir="" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/firstboot/fde new/fde-tools-0.7.9/firstboot/fde --- old/fde-tools-0.7.7/firstboot/fde 2025-07-29 05:00:40.734993675 +0200 +++ new/fde-tools-0.7.9/firstboot/fde 2026-09-21 08:57:10.277341262 +0200 @@ -52,6 +52,10 @@ grub_get_fde_password "$@" } +function bootloader_get_keyslots { + grub_get_keyslots "$@" +} + function bootloader_platform_parameters { grub_platform_parameters } @@ -87,6 +91,10 @@ with_tpm=false with_ccid=false + # Set when the user asked for a recovery password but we could not install + # it. The key slots have to be left alone in that case. + pass_failed=false + is_reencrypted=false for method in $FDE_PROTECTION; do @@ -134,47 +142,78 @@ luks_current_password="${luks_recovery_pass}" - # Check if the installer/imager has created a secondary slot that is protected - # by a random key. - # "cryptsetup reencrypt" doesn't really deal all that well with a LUKS - # header that has more than one valid key slot. To avoid any ugly gymnastics, - # simply drop that slot. - if [ -n "$luks_keyfile" ]; then - # Skip luks_drop_key if the partition is already reencrypted - if [ "$is_reencrypted" == "false" ]; then - if ! luks_drop_key "${luks_dev}" "${luks_keyfile}"; then - display_errorbox "Failed to remove initial random key" - return 1 - fi - fi - - rm -f "${luks_keyfile}" - - # Remove the dracut conf for the key file - rm -f /etc/dracut.conf.d/99-luks-boot.conf - - # Replace the key file path in /etc/crypttab with "/.virtual-root.key" - # to avoid errors when unmounting the LUKS partition (bsc#1218181) - sed -i "s,${luks_keyfile},/.virtual-root.key,g" /etc/crypttab - - luks_keyfile="" + # The installer/imager may have created a secondary slot that is protected + # by a random key, stored in ${luks_keyfile}. That key is a far better + # credential than the built-in recovery password: we can check that it is + # valid, whereas the recovery password is only a guess, and in FIPS mode + # PBKDF2 refuses passphrases shorter than 8 characters, so a short built-in + # password fails to open any key slot at all. + # Do not drop the slot here: it is the only credential we are sure about, + # and both the recovery path below and fde_setup_unencrypted still need it. + luks_imager_keyfile="" + if [ -n "$luks_keyfile" ] && luks_verify_password "${luks_dev}" "${luks_keyfile}"; then + luks_imager_keyfile="${luks_keyfile}" fi # Change the built-in recovery password to the one provided by the user # FIXME: only do this if the password is well-known, eg when dealing with a VM image # shipped by us. if $with_pass; then - if luks_change_password "${luks_dev}" "${luks_current_password}"; then + if luks_change_password "${luks_dev}" "${luks_current_password}" "${luks_imager_keyfile}"; then luks_current_password="${result_password}" else display_errorbox "Failed to change recovery password." with_pass=false + pass_failed=true fi fi # Write the current password to a file for the later operations pass_keyfile=$(luks_write_password pass "${luks_current_password}") + # Now that the recovery password is in place, drop every key slot we no + # longer need: the imager's random key, and the slot that still holds the + # built-in recovery password when luks_change_password authenticated with + # the imager key rather than with that password. TPM protected slots are + # preserved. + # "cryptsetup reencrypt" doesn't really deal all that well with a LUKS + # header that has more than one valid key slot, so this has to happen + # before luks_reencrypt below. + # Leave the header alone if we failed to install the recovery password: + # dropping slots at that point would only throw away the last credential + # the recovery path below can still use. + drop_imager_keyfile=false + if $with_pass; then + if ! luks_drop_other_keyslots "${luks_dev}" "${pass_keyfile}"; then + display_errorbox "Failed to remove initial random key" + return 1 + fi + drop_imager_keyfile=true + elif ! $pass_failed && [ -n "$luks_imager_keyfile" ]; then + # No recovery password was requested, so there is no obsolete slot to + # collect either; just drop the imager's random key as we always did. + if ! luks_drop_key "${luks_dev}" "${luks_imager_keyfile}"; then + display_errorbox "Failed to remove initial random key" + return 1 + fi + drop_imager_keyfile=true + fi + + if $drop_imager_keyfile && [ -n "$luks_keyfile" ]; then + rm -f "${luks_keyfile}" + + # Remove the dracut conf for the key file + rm -f /etc/dracut.conf.d/99-luks-boot.conf + + # Replace the key file path in /etc/crypttab with + # "/.virtual-root.key" to avoid errors when unmounting the LUKS + # partition (bsc#1218181). The path must not be on / itself, which is + # read-only on SL Micro. + sed -i "s,${luks_keyfile},/.virtual-root.key,g" /etc/crypttab + + luks_keyfile="" + fi + # Reencrypt with the new password # FIXME: only do this if the LUKS master key is well-known, eg when dealing with # a VM image. @@ -236,7 +275,18 @@ return 1 fi - luks_decrypt "${luks_dev}" "${luks_keyfile}" + # We may be called to recover from a failed fde_setup_encrypted, which + # drops the imager key file once it is no longer needed. Fall back to the + # recovery password in that case. + if [ ! -s "${luks_keyfile}" ]; then + luks_keyfile=$(luks_write_password recovery "${luks_recovery_pass}") + fi + + if ! luks_decrypt "${luks_dev}" "${luks_keyfile}"; then + rm -f "${luks_keyfile}" + display_errorbox "Failed to decrypt ${luks_dev}" + return 1 + fi rm -f "${luks_keyfile}" rm -f /etc/crypttab @@ -322,6 +372,14 @@ function fde_firstboot { + # fde_setup_encrypted keeps these two up to date as it goes: it consumes + # the imager key file once the recovery password is in place, and replaces + # the built-in recovery password with the one chosen by the user. The + # recovery path below has to use whatever is valid by then, not the + # credentials we were originally called with. + luks_keyfile="$2" + luks_current_password="$3" + if fde_choose_protection; then if fde_setup_encrypted "$@"; then return 0 @@ -330,7 +388,7 @@ display_errorbox "Failed to set up for full disk encryption. Trying to recover" fi - fde_setup_unencrypted "$@" + fde_setup_unencrypted "$1" "${luks_keyfile}" "${luks_current_password}" } function fde_systemd_firstboot { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/share/commands/tpm-activate new/fde-tools-0.7.9/share/commands/tpm-activate --- old/fde-tools-0.7.7/share/commands/tpm-activate 2025-07-29 05:00:40.734993675 +0200 +++ new/fde-tools-0.7.9/share/commands/tpm-activate 2026-09-21 08:57:08.235352536 +0200 @@ -26,7 +26,11 @@ local luks_devices="$1" - if bootloader_check_sealed_key; then + # A sealed key alone is not enough to skip activation: the installer + # may have left a firstboot password behind, and it is only good for + # one reboot. tpm_enable removes it, so let it run in that case. + if bootloader_check_sealed_key && \ + [ -z "$(bootloader_get_fde_password)" ]; then fde_trace "LUKS key already sealed. Skip activation." return 0 fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/share/commands/tpm-enable new/fde-tools-0.7.9/share/commands/tpm-enable --- old/fde-tools-0.7.7/share/commands/tpm-enable 2025-07-29 05:00:40.734993675 +0200 +++ new/fde-tools-0.7.9/share/commands/tpm-enable 2026-09-21 08:57:08.236352531 +0200 @@ -28,6 +28,18 @@ st=1 + # Remember whether a firstboot password was set: the removal below + # clears it, so the check further down can no longer detect it. + local had_fde_password= + if [ -n "$(bootloader_get_fde_password)" ]; then + had_fde_password=y + fi + + # Ensure the sealed key is usable before removing firstboot password. + if ! tpm_enable_check_sealed_key; then + return 1 + fi + # The installer may have set a random password to avoid the password # prompt on firstboot. # We must clear this before computing any PCR policies, otherwise @@ -66,9 +78,11 @@ # systemd). Obviously, there was no key to enroll, so we might # as well return. # - # However, if there was a firstboot password, fall thru to remove the password. + # However, if there was a firstboot password, fall thru to commit + # the configuration, so that the password is dropped from the + # boot loader config in the ESP too. # After all, it's supposed to be good for one reboot only. - if [ "$(bootloader_get_fde_password)" = "" ]; then + if [ -z "$had_fde_password" ]; then return 0 fi st=0 @@ -82,6 +96,35 @@ return $st } +# Verify that the sealed key can be loaded and get its SRK algorithm. +function tpm_enable_check_sealed_key { + + if [[ ! "$FDE_USE_AUTHORIZED_POLICIES" =~ y.* ]] || + [ -z "$FDE_AUTHORIZED_POLICY" ]; then + return 0 + fi + + # Older pcr-oracle versions cannot answer this, skip check. + if ! tpm_have_load_test; then + fde_trace "Unable to determine the SRK of the sealed key" + return 0 + fi + + tpm_define_authorized_policy_paths "$FDE_AUTHORIZED_POLICY" + + if [ ! -f "$FDE_AP_SEALED_SECRET" ]; then + # Handled by tpm_enable_authorized_policy. + return 0 + fi + + if tpm_get_srk_alg "$FDE_AP_SEALED_SECRET" >/dev/null; then + return 0 + fi + + display_errorbox "The sealed key in $FDE_AP_SEALED_SECRET cannot be loaded by this TPM. Refusing to update the boot loader configuration, as the system would no longer boot unattended. Use \"fdectl regenerate-key\" to seal a new key." + return 1 +} + function tpm_enable_authorized_policy { local luks_devices="$1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/share/grub2 new/fde-tools-0.7.9/share/grub2 --- old/fde-tools-0.7.7/share/grub2 2026-08-11 09:15:17.915379638 +0200 +++ new/fde-tools-0.7.9/share/grub2 2026-09-21 08:57:10.278341256 +0200 @@ -68,11 +68,48 @@ . /etc/default/grub - if [ -z "$GRUB_CRYPTODISK_PASSWORD" ]; then + if [ -n "$GRUB_CRYPTODISK_PASSWORD" ]; then + echo "$GRUB_CRYPTODISK_PASSWORD" + return 0 + fi + + # Fallback: the image build may have embedded the well-known + # passphrase directly in the EFI grub.cfg via 'cryptomount -p "..."' + # instead of GRUB_CRYPTODISK_PASSWORD in /etc/default/grub. + local pass + [ -f /boot/efi/EFI/BOOT/grub.cfg ] || return 1 + pass=$(sed -n 's/.*cryptomount[[:space:]].*-p[[:space:]]*"\([^"]*\)".*/\1/p' /boot/efi/EFI/BOOT/grub.cfg | head -n1) + if [ -n "$pass" ]; then + echo "$pass" + return 0 + fi + + return 1 +} + +# Get the configured SRK algorithm from grub. +function grub_get_srk_alg { + + if [ -f /etc/default/grub ]; then + . /etc/default/grub + fi + + echo "$GRUB_TPM2_SRK_ALG" +} + +################################################################## +# Print the path of the sealed key the boot loader reads. +################################################################## +function grub_get_sealed_key_path { + + local grub_efi_dir + grub_efi_dir=$(uefi_get_current_efidir) + + if [ -z "$grub_efi_dir" ]; then return 1 fi - echo "$GRUB_CRYPTODISK_PASSWORD" + echo "$grub_efi_dir/sealed.tpm" } ################################################################## @@ -82,13 +119,14 @@ function grub_update_early_config { local sealed_key_file="$1" + local srk_alg="$2" grub_set_control GRUB_ENABLE_CRYPTODISK "y" grub_set_control GRUB_TPM2_SEALED_KEY "$sealed_key_file" - if [ "$(tpm_get_ecc_srk_support)" = "yes" ]; then - grub_set_control GRUB_TPM2_SRK_ALG "ECC" - else - grub_set_control GRUB_TPM2_SRK_ALG "RSA" + + # Keep current SRK setting if caller cannot determine it. + if [ -n "$srk_alg" ]; then + grub_set_control GRUB_TPM2_SRK_ALG "$srk_alg" fi # Do not clear the password implicitly; require fdectl or @@ -113,8 +151,37 @@ function grub_enable_fde_authorized_policy { - # Set up grub.cfg - grub_update_early_config sealed.tpm + local sealed_key_file="$1" + + # Retain the key's existing SRK. + grub_update_early_config sealed.tpm "$(tpm_get_srk_alg "$sealed_key_file")" +} + +################################################################## +# Align configured SRK algorithm with the key's actual SRK. +################################################################## +function grub_repair_srk_alg { + + local sealed_key_file="$1" + local srk_alg + local config_alg + + srk_alg=$(tpm_get_srk_alg "$sealed_key_file") || return 0 + + config_alg=$(grub_get_srk_alg) + if [ "$srk_alg" = "$config_alg" ]; then + return 0 + fi + + fde_trace "Boot loader configured for a ${config_alg:-unset} SRK, but the sealed key uses ${srk_alg}; updating the configuration" + + grub_set_control GRUB_TPM2_SRK_ALG "$srk_alg" + + # Commit config before signing. + if ! grub_commit_config; then + fde_trace "Failed to update the boot loader configuration" + return 1 + fi } function grub_authorize_pcr_policy { @@ -122,8 +189,10 @@ private_key_file="$1" sealed_key_file="$2" - grub_efi_dir=$(uefi_get_current_efidir) - if [ -z "$grub_efi_dir" ]; then + local grub_sealed_key + grub_sealed_key=$(grub_get_sealed_key_path) || return 1 + + if ! grub_repair_srk_alg "$sealed_key_file"; then return 1 fi @@ -133,23 +202,21 @@ # append the valid signatures into the key file. tpm_authorize "$private_key_file" "$sealed_key_file" \ - "$grub_efi_dir/sealed.tpm" + "$grub_sealed_key" } function grub_enable_fde_pcr_policy { luks_keyfile="$1" - grub_efi_dir=$(uefi_get_current_efidir) - if [ -z "$grub_efi_dir" ]; then - return 1 - fi + local grub_sealed_key + grub_sealed_key=$(grub_get_sealed_key_path) || return 1 # First update grub.cfg... - grub_update_early_config sealed.tpm + grub_update_early_config sealed.tpm "$(tpm_get_new_srk_alg)" # ... then seal the key against a PCR9 value that covers grub.cfg - tpm_seal_secret "${luks_keyfile}" "$grub_efi_dir/sealed.tpm" + tpm_seal_secret "${luks_keyfile}" "$grub_sealed_key" } function grub_enable_fde_without_tpm { @@ -166,26 +233,54 @@ function grub_check_sealed_key { - grub_efi_dir=$(uefi_get_current_efidir) - if [ -z "$grub_efi_dir" ]; then + local sealed_key + sealed_key=$(grub_get_sealed_key_path) || return 1 + + if [ ! -f "${sealed_key}" ]; then return 1 fi - if [ -f "$grub_efi_dir/sealed.tpm" ]; then - return 0 + # Without authorized policies there is no base key to compare + # against: the key in the EFI partition is the key. + if [[ ! "$FDE_USE_AUTHORIZED_POLICIES" =~ y.* ]]; then + return 0 fi - return 1 + # With authorized policies, the signed key in the EFI partition is + # only valid if it is derived from the sealed base key. Anything that + # prevents the comparison leaves a stale key indistinguishable from a + # good one, so report the key as not sealed and let tpm_enable emit a + # proper error. + if [ -z "${FDE_AUTHORIZED_POLICY}" ]; then + fde_trace "Authorized policy not created yet: ESP key is stale." + return 1 + fi + + # The paths are defined here since this function may run before any + # caller of tpm_set_authorized_policy_paths. + tpm_define_authorized_policy_paths "${FDE_AUTHORIZED_POLICY}" + + if [ ! -f "${FDE_AP_SEALED_SECRET}" ]; then + fde_trace "Base key not found: the sealed key in the ESP is stale." + return 1 + fi + + if ! type tpm2key-tool >/dev/null 2>&1; then + fde_trace "tpm2key-tool not available: cannot verify the ESP key." + return 0 + fi + + # If both keys contain the same public and private keys, the signed + # key is derived from the base key. + tpm2key-tool cmp "${sealed_key}" "${FDE_AP_SEALED_SECRET}" >/dev/null 2>&1 + return $? } function grub_remove_sealed_key { # Remove the sealed key file in the EFI system partition - grub_efi_dir=$(uefi_get_current_efidir) - if [ -z "$grub_efi_dir" ]; then - return 1 - fi - grub_sealed_key="$grub_efi_dir/sealed.tpm" + local grub_sealed_key + grub_sealed_key=$(grub_get_sealed_key_path) || return 1 if [ -f "${grub_sealed_key}" ]; then rm -f "${grub_sealed_key}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/share/luks new/fde-tools-0.7.9/share/luks --- old/fde-tools-0.7.7/share/luks 2026-08-11 09:15:17.915379638 +0200 +++ new/fde-tools-0.7.9/share/luks 2026-09-21 08:57:10.278341256 +0200 @@ -217,6 +217,96 @@ } ################################################################## +# List the active key slots of a LUKS device +################################################################## +function luks_list_keyslots { + + local luks_dev=$1 + + LC_ALL=C cryptsetup luksDump "${luks_dev}" | + sed -n '/^Keyslots:/,/^Tokens:/s/^ \([0-9]\+\): luks2$/\1/p' +} + +################################################################## +# Report the key slot that the given key file unlocks +################################################################## +function luks_keyslot_for_keyfile { + + local luks_dev=$1 + local luks_keyfile="$2" + + LC_ALL=C cryptsetup open --test-passphrase --verbose \ + --key-file "${luks_keyfile}" "${luks_dev}" | + sed -En 's/^Key slot ([0-9]+) unlocked\.$/\1/p' +} + +################################################################## +# Drop every key slot of a LUKS device except the one unlocked by +# the given key file and those protected by the TPM. +# +# The TPM key slots are identified through the bootloader specific +# LUKS2 tokens, so that a system that is already enrolled does not +# lose its TPM protection here. +# +# Note that "cryptsetup luksKillSlot" happily wipes the last +# remaining key slot and does not validate --key-file, so we have +# to make sure ourselves that we never wipe the slot we want to +# keep. +################################################################## +function luks_drop_other_keyslots { + + local luks_dev=$1 + local luks_keyfile="$2" + local keep_slot + local tpm_slots + local slot + + keep_slot=$(luks_keyslot_for_keyfile "${luks_dev}" "${luks_keyfile}") + if [ -z "${keep_slot}" ]; then + fde_trace "Unable to identify the key slot to preserve on ${luks_dev}" + return 1 + fi + # A key file should unlock a single slot. If it opens more than one + # we cannot tell which to keep, so bail rather than guessing. + if [[ "${keep_slot}" == *$'\n'* ]]; then + fde_trace "Key file unlocks several slots on ${luks_dev}, cannot decide which to preserve: ${keep_slot}" + return 1 + fi + + # Bail out rather than wipe a TPM slot we merely failed to enumerate. + if ! tpm_slots=$(bootloader_get_keyslots "${luks_dev}"); then + fde_trace "Unable to list the TPM protected key slots of ${luks_dev}" + return 1 + fi + # A TPM slot is one the bootloader reports. bootloader_get_keyslots + # (fdectl-grub-tpm2 list --key-only) prints the keyslot ids bare, one + # per line. We do not quote the list: "echo" without quotes collapses + # the newlines into single spaces so every id becomes " 12 ", and the + # space-bounded substring test keeps id 1 distinct from id 10. + tpm_slots=" $(echo ${tpm_slots}) " + + for slot in $(luks_list_keyslots "${luks_dev}"); do + test "${slot}" != "${keep_slot}" || continue + if [[ "${tpm_slots}" == *" ${slot} "* ]]; then + fde_trace "Preserving TPM protected key slot ${slot}" + continue + fi + + display_infobox "Dropping obsolete key slot ${slot} (${luks_dev})" + # Pass the key file even though luksKillSlot does not insist on one, so + # that it does not go looking for a passphrase on our stdin, which + # belongs to the dialog UI + if ! cryptsetup luksKillSlot -q --key-file "${luks_keyfile}" \ + "${luks_dev}" "${slot}" </dev/null; then + fde_trace "Warning: luksKillSlot ${slot} indicates failure" + return 1 + fi + done + + return 0 +} + +################################################################## # Verify an existing password ################################################################## function luks_verify_password { @@ -254,13 +344,25 @@ # Change an existing password # This function uses request_new_password to prompt the user for # the new password. +# +# The third argument is optional. When given, it names a key file +# that is known to unlock the device, eg the random key left behind +# by the imager, and it is used as the credential for luksChangeKey +# instead of the old password. This matters because the old password +# is often only a guess, and because PBKDF2 in FIPS mode rejects +# passphrases shorter than 8 characters outright, so a short built-in +# password cannot open any key slot at all. +# Note that luksChangeKey rewrites the key slot it authenticated +# with, so the caller is responsible for dropping the slot that still +# holds the old password. ################################################################## function luks_change_password { local luks_dev=$1 local luks_old_password="$2" + local luks_unlock_keyfile="$3" - if [ -z "$luks_old_password" ]; then + if [ -z "$luks_old_password" -a -z "$luks_unlock_keyfile" ]; then request_password "Please enter old LUKS recovery password" if [ -z "$result_password" ]; then fde_trace "Unable to obtain old recovery password" @@ -275,14 +377,20 @@ return 1 fi - old_keyfile=$(luks_write_password oldpass "${luks_old_password}") + if [ -n "$luks_unlock_keyfile" ]; then + old_keyfile="${luks_unlock_keyfile}" + else + old_keyfile=$(luks_write_password oldpass "${luks_old_password}") + fi new_keyfile=$(luks_write_password newpass "${result_password}") if ! luks_set_password "${luks_dev}" "${old_keyfile}" "${new_keyfile}"; then - rm -f ${new_keyfile} ${old_keyfile} + rm -f ${new_keyfile} + test -n "$luks_unlock_keyfile" || rm -f ${old_keyfile} return 1 fi - rm -f ${new_keyfile} ${old_keyfile} + rm -f ${new_keyfile} + test -n "$luks_unlock_keyfile" || rm -f ${old_keyfile} } function luks_add_password { @@ -405,14 +513,32 @@ luks_dev="$1" luks_keyfile="$2" + local status_file + local status + + # The exit status of cryptsetup is swallowed by the pipeline feeding the + # gauge, so stash it in a file. Callers act on the result by removing + # /etc/crypttab, which must not happen when the device is still encrypted. + status_file=$(fde_make_tempfile decrypt.status) # Online reencryption works with LUKS2 only. If we ever want to do FDE with luks1, # we need to perform reencryption during installation, after dd'ing the image to # disk and prior to mounting it. { - LC_ALL=C cryptsetup reencrypt --decrypt --key-file "$luks_keyfile" --progress-frequency 1 $luks_dev 2>&1| + { LC_ALL=C cryptsetup reencrypt --decrypt --key-file "$luks_keyfile" --progress-frequency 1 $luks_dev 2>&1 + echo $? >"$status_file"; } | sed -u 's/.* \([0-9]*\)[0-9.]*%.*/\1/' echo 100 } | display_gauge "Decrypting LUKS device $luks_dev" + + status=$(cat "$status_file" 2>/dev/null) + rm -f "$status_file" + + if [ "$status" != "0" ]; then + fde_trace "Warning: cryptsetup reencrypt --decrypt indicates failure" + return 1 + fi + + return 0 } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fde-tools-0.7.7/share/tpm new/fde-tools-0.7.9/share/tpm --- old/fde-tools-0.7.7/share/tpm 2026-08-11 09:15:17.915379638 +0200 +++ new/fde-tools-0.7.9/share/tpm 2026-09-21 08:57:08.236352531 +0200 @@ -125,6 +125,61 @@ echo "$__fde_ecc_srk" } +################################################################## +# Check if pcr-oracle supports load-test. +################################################################## +function tpm_have_load_test { + + declare -g __fde_have_load_test + + if [ -z "$__fde_have_load_test" ]; then + if LC_ALL=C pcr-oracle load-test 2>&1 | grep -q "Unknown action"; then + __fde_have_load_test="no" + else + __fde_have_load_test="yes" + fi + fi + + [ "$__fde_have_load_test" = "yes" ] +} + +################################################################## +# Get the SRK algorithm of an existing sealed key. +################################################################## +function tpm_get_srk_alg { + + local sealed_key="$1" + + declare -g __fde_srk_alg + + if [ -z "$__fde_srk_alg" ]; then + if [ ! -f "$sealed_key" ] || ! tpm_have_load_test; then + return 1 + fi + + __fde_srk_alg=$(pcr-oracle --input "$sealed_key" load-test 2>/dev/null) + if [ -z "$__fde_srk_alg" ]; then + fde_trace "${sealed_key} cannot be loaded under any known SRK" + return 1 + fi + fi + + echo "$__fde_srk_alg" +} + +################################################################## +# Get the expected SRK algorithm for a newly sealed key. +################################################################## +function tpm_get_new_srk_alg { + + if [ "$(tpm_get_ecc_srk_support)" = "yes" ]; then + echo "ECC" + return + fi + + echo "RSA2048" +} + function tpm_snapshot { # TODO Add an ID to the snapshot name local snapshot=${FDE_SNAPSHOT_NAME} @@ -323,8 +378,11 @@ --after \ seal-secret \ "$FDE_SEAL_PCR_LIST" + local rc=$? tpm_snapshot + + return $rc } function tpm_test { @@ -436,7 +494,9 @@ ################################################################## # Authorized policy support ################################################################## -function tpm_set_authorized_policy_paths { +# Define the authorized policy paths without touching the file system, +# so that read-only callers can look the files up. +function tpm_define_authorized_policy_paths { policy_name="$1" @@ -447,6 +507,11 @@ declare -g FDE_AP_AUTHPOLICY="$FDE_AP_CONFIG_DIR/authorized-policy.tpm" declare -g FDE_AP_PUBLIC_KEY="$FDE_AP_CONFIG_DIR/public-key.tpm" declare -g FDE_AP_SEALED_SECRET="$FDE_AP_CONFIG_DIR/sealed.tpm" +} + +function tpm_set_authorized_policy_paths { + + tpm_define_authorized_policy_paths "$1" mkdir -p -m 755 "$FDE_AP_CONFIG_DIR" } @@ -498,6 +563,14 @@ local extra_opts=$(tpm_platform_parameters) local stop_event=$(bootloader_stop_event) + # pcr-oracle writes straight to its --output, which is the key the + # boot loader is currently using. Sign into a sibling file and rename + # it over the live one only on success, so that a failed signature + # leaves the previous working key in place. The staged file must be a + # sibling to stay on the same file system, otherwise the rename falls + # back to a copy and may leave a partial key behind. + local staged_key_file="${signed_key_file}.new" + pcr-oracle ${extra_opts} \ --algorithm "$FDE_SEAL_PCR_BANK" \ --private-key "$private_key_file" \ @@ -505,8 +578,18 @@ --stop-event "$stop_event" \ --after \ --input "$sealed_key_file" \ - --output "$signed_key_file" \ + --output "$staged_key_file" \ sign "$FDE_SEAL_PCR_LIST" + local rc=$? + + if [ $rc -eq 0 ]; then + mv -f "$staged_key_file" "$signed_key_file" + rc=$? + else + rm -f "$staged_key_file" + fi tpm_snapshot + + return $rc }
