heads/initrd/bin/kexec-seal-key
Thierry Laurion f43fe1a836 initrd/bin/kexec-seal-key initrd/etc/luks-functions: last fixups
- fi misplaced
- rework reencryption loop
- added verbose output on TPM DUK key addition when LUKS container can be unlocked with DRK

Current state, left todo for future work:

TPM DUK:
- TPM DUK setup on defautl boot reuses /boot/kexec_key_devices.txt if present
- If not, list all LUKS partitions, asks user for selection and makes sure LUKS passphrase can unlock all
- Works on both LUKSv1 and LUKSv2 containers, reusing OS installer settings (Heads doesn't enforce better then OS installer LUKS parameters)

LUKS passphrase change/LUKS reencryption:
- Reuses /boot/kexec_key_devices.txt if existing
- If not, prompts for LUKS passphase, list all LUKS containers not being USB based and attempt to unlock all those, listing only the ones successfully unlocked
- Prompts user to reuse found unlockable LUKS partitions with LUKS passphrase, caches and reuse in other LUKS operations (passphrase change as well from oem factory reset/re-ownership)
- Deals properly with LUKSv1/LUKSv2/multiple LUKS containers and reencrypt/passphrase changes them all if accepted, otherwise asks user to select individual LUKS container

Tested on luksv1,luksv2, btrfs under luks (2x containers) and TPM DUK setup up to booting OS. All good

TODO:
- LUKS passphrase check is done multiple times across TPM DUK, reencryption and luks passphrase. Could refactor to change this, but since this op is done only one reencrypt+passphrase change) upon hardare reception from OEM, I stopped caring here.

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
2024-10-30 14:18:20 -04:00

258 lines
9.3 KiB
Bash
Executable File

#!/bin/bash
# This will generate a disk encryption key and seal / encrypt
# with the current PCRs and then store it in the TPM NVRAM.
# It will then need to be bundled into initrd that is booted.
set -e -o pipefail
. /etc/functions
find_drk_key_slot() {
local temp_drk_key_slot=""
local keyslot
for keyslot in "${luks_used_keyslots[@]}"; do
if [ -z "$temp_drk_key_slot" ]; then
DEBUG "Testing LUKS key slot $keyslot against $DISK_RECOVERY_KEY_FILE for Disk Recovery Key slot..."
if DO_WITH_DEBUG cryptsetup open --test-passphrase --key-slot "$keyslot" --key-file "$DISK_RECOVERY_KEY_FILE" "$dev"; then
temp_drk_key_slot="$keyslot"
DEBUG "Disk Recovery key slot is $temp_drk_key_slot"
break
fi
fi
done
echo "$temp_drk_key_slot"
}
TPM_INDEX=3
TPM_SIZE=312
DUK_KEY_FILE="/tmp/secret/secret.key"
TPM_SEALED="/tmp/secret/secret.sealed"
DISK_RECOVERY_KEY_FILE="/tmp/secret/recovery.key"
. /etc/functions
. /tmp/config
TRACE_FUNC
paramsdir=$1
if [ -z "$paramsdir" ]; then
die "Usage $0 /boot"
fi
KEY_DEVICES="$paramsdir/kexec_key_devices.txt"
KEY_LVM="$paramsdir/kexec_key_lvm.txt"
key_devices=$(cat "$KEY_DEVICES" | cut -d\ -f1 | tr '\n' ' ')
if [ ! -r "$KEY_DEVICES" ]; then
die "No devices defined for disk encryption"
else
DEBUG "Devices defined for disk encryption: $key_devices"
fi
if [ -r "$KEY_LVM" ]; then
# Activate the LVM volume group
VOLUME_GROUP=$(cat $KEY_LVM)
if [ -z "$VOLUME_GROUP" ]; then
die "No LVM volume group defined for activation"
fi
lvm vgchange -a y $VOLUME_GROUP ||
die "$VOLUME_GROUP: unable to activate volume group"
else
DEBUG "No LVM volume group defined for activation"
fi
DEBUG "$(pcrs)"
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
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
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"
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
break
else
attempts=$((attempts + 1))
if [ "$attempts" == "3" ] && [ "$luks_drk_passphrase_valid" == "0" ]; 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
done
attempts=0
while [ $attempts -lt 3 ]; do
read -s -p "New LUKS TPM Disk Unlock Key passphrase (DUK) for booting: " key_password
echo
read -s -p "Repeat LUKS TPM Disk Unlock Key (DUK) passphrase for booting: " key_password2
echo
if [ "$key_password" != "$key_password2" ]; then
attempts=$((attempts + 1))
if [ "$attempts" == "3" ]; then
die "Disk Unlock Key passphrases do not match. Exiting..."
else
warn "Disk Unlock Key passphrases do not match. Please try again."
fi
else
break
fi
done
# Generate key file
echo "++++++ Generating new randomized 128 bytes key file that will be sealed/unsealed by LUKS TPM Disk Unlock Key passphrase"
dd \
if=/dev/urandom \
of="$DUK_KEY_FILE" \
bs=1 \
count=128 \
2>/dev/null ||
die "Unable to generate 128 random bytes"
previous_luks_header_version=0
for dev in $key_devices; do
# Check and store LUKS version of the devices to be used later
luks_version=$(cryptsetup luksDump "$dev" | grep "Version" | cut -d: -f2 | tr -d '[:space:]')
if [ "$luks_version" == "2" ] && [ "$previous_luks_header_version" == "1" ]; then
die "$dev: LUKSv2 device detected while LUKSv1 device was detected previously. Exiting..."
fi
if [ "$luks_version" == "1" ] && [ "$previous_luks_header_version" == "2" ]; then
die "$dev: LUKSv1 device detected while LUKSv2 device was detected previously. Exiting..."
fi
if [ "$luks_version" == "2" ]; then
# LUKSv2 last key slot is 31
duk_keyslot=31
regex="^\s+([0-9]+):\s*luks2"
sed_command="s/^\s\+\([0-9]\+\):\s*luks2/\1/g"
previous_luks_header_version=2
DEBUG "$dev: LUKSv2 device detected"
elif [ "$luks_version" == "1" ]; then
# LUKSv1 last key slot is 7
duk_keyslot=7
regex="Key Slot ([0-9]+): ENABLED"
sed_command='s/Key Slot \([0-9]\+\): ENABLED/\1/'
previous_luks_header_version=1
DEBUG "$dev: LUKSv1 device detected"
else
die "$dev: Unsupported LUKS version $luks_version"
fi
# drk_key_slot will be the slot number where the passphrase was tested against as valid. We will keep that slot
drk_key_slot="-1"
# 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)
# If we didn't find the DRK key slot, we exit (this should never happen)
if [ "$drk_key_slot" == "-1" ]; then
die "$dev: Unable to find a key slot that can be unlocked with provided passphrase. Exiting..."
fi
# If the key slot is not the expected DUK o FRK key slot, we will ask the user to confirm the wipe
for keyslot in "${luks_used_keyslots[@]}"; do
if [ "$keyslot" != "$drk_key_slot" ]; then
#set wipe_desired to no by default
wipe_desired="no"
if [ "$keyslot" != "$drk_key_slot" ] && [ "$keyslot" == "1" ]; then
wipe_desired="yes"
DEBUG "LUKS key slot $keyslot not DRK. Will wipe this DUK key slot silently"
elif [ "$keyslot" != "$drk_key_slot" ] && [ "$keyslot" != "$duk_keyslot" ]; then
# 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
# If user does not confirm, skip this slot
if [[ $REPLY =~ ^[Yy]$ ]]; then
wipe_desired="yes"
fi
elif [ "$keyslot" == "$duk_keyslot" ]; then
# If key slot is the expected DUK keyslot, we wipe it silently
DEBUG "LUKS key slot $keyslot is the expected DUK key slot. Will wipe this DUK key slot silently"
wipe_desired="yes"
fi
if [ "$wipe_desired" == "yes" ] && [ "$keyslot" != "$drk_key_slot" ]; then
echo "++++++ $dev: Wiping LUKS key slot $keyslot"
DO_WITH_DEBUG cryptsetup luksKillSlot \
--key-file "$DISK_RECOVERY_KEY_FILE" \
$dev $keyslot ||
warn "$dev: removal of LUKS slot $keyslot failed: Continuing"
fi
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" \
--new-key-slot $duk_keyslot \
$dev "$DUK_KEY_FILE" ||
die "$dev: Unable to add LUKS TPM Disk Unlock Key to LUKS key slot $duk_keyslot"
done
# Now that we have setup the new keys, measure the PCRs
# We don't care what ends up in PCR 6; we just want
# to get the /tmp/luksDump.txt file. We use PCR16
# since it should still be zero
echo "$key_devices" | xargs /bin/qubes-measure-luks ||
die "Unable to measure the LUKS headers"
pcrf="/tmp/secret/pcrf.bin"
tpmr pcrread 0 "$pcrf"
tpmr pcrread -a 1 "$pcrf"
tpmr pcrread -a 2 "$pcrf"
tpmr pcrread -a 3 "$pcrf"
# Note that PCR 4 needs to be set with the "normal-boot" path value, read it from event log.
tpmr calcfuturepcr 4 >>"$pcrf"
if [ "$CONFIG_USER_USB_KEYBOARD" = "y" -o -r /lib/modules/libata.ko -o -x /bin/hotp_verification ]; then
DEBUG "Sealing LUKS TPM Disk Unlock Key with PCR5 involvement (additional kernel modules are loaded per board config)..."
# Here, we take pcr 5 into consideration if modules are expected to be measured+loaded
tpmr pcrread -a 5 "$pcrf"
else
DEBUG "Sealing LUKS TPM Disk Unlock Key with PCR5=0 (NO additional kernel modules are loaded per board config)..."
#no kernel modules are expected to be measured+loaded
tpmr calcfuturepcr 5 >>"$pcrf"
fi
# Precompute the value for pcr 6
DEBUG "Precomputing TPM future value for PCR6 sealing/unsealing of LUKS TPM Disk Unlock Key..."
tpmr calcfuturepcr 6 "/tmp/luksDump.txt" >>"$pcrf"
# We take into consideration user files in cbfs
tpmr pcrread -a 7 "$pcrf"
DO_WITH_DEBUG --mask-position 7 \
tpmr seal "$DUK_KEY_FILE" "$TPM_INDEX" 0,1,2,3,4,5,6,7 "$pcrf" \
"$TPM_SIZE" "$key_password" || die "Unable to write LUKS TPM Disk Unlock Key to NVRAM"
# should be okay if this fails
shred -n 10 -z -u "$pcrf" 2>/dev/null ||
warn "Failed to delete pcrf file - continuing"
shred -n 10 -z -u "$DUK_KEY_FILE" 2>/dev/null ||
warn "Failed to delete key file - continuing"
mount -o rw,remount $paramsdir || warn "Failed to remount $paramsdir in RW - continuing"
cp -f /tmp/luksDump.txt "$paramsdir/kexec_lukshdr_hash.txt" ||
warn "Failed to copy LUKS header hashes to /boot - continuing"
mount -o ro,remount $paramsdir || warn "Failed to remount $paramsdir in RO - continuing"