mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-24 07:06:42 +00:00
0cef8e1edc
cryptsetup2 2.6.1 is a new release that supports reencryption of Q4.2 release LUKS2 volumes created at installation. This is a critical feature for the Qubes OS 4.2 release for added data at rest protection Cryptsetup 2.6.x internal changes: - Argon2 used externally and internally: requires a lot of RAM and CPU to derivate passphrase to key validated in key slots. - This is used to rate limit efficiently bruteforcing of LUKS key slots, requiring each offline brute force attempt to consume ~15-30 seconds per attempt - OF course, strong passphrases are still recommended, but bruteforcing LUKSv2 containers with Argon2 would require immense time, ram and CPU even to bruteforce low entropy passphrase/PINs. - passphrase change doesn't permit LUKS key slot specification anymore: key slot rotates (new one consusumed per op: then old one wiped internally. EG: LUKS key slot 1 created, then 0 deleted) - reencryption doesn't permit old call arguments. No more direct-io; inadmissively slow through AIO (async) calls, need workarounds for good enough perfs (arguments + newer kernel with cloudfare fixes in tree) cryptsetup 2.6.1 requires: - lvm2 2.03.23, which is also included in this PR. - requires libaio, which is also included in this PR (could be hacked out but deep dependency at first sight: left in) - requires util-linux 2.39 - patches for reproducible builds are included for above 3 packages. luks-functions was updated to support the new cryptsetup2 version calls/changes - reencryption happen in direct-io, offline mode and without locking, requiring linux 5.10.9+ to bypass linux queues - from tests, this is best for performance and reliability in single-user mode - LUKS container ops now validate Disk Recovery Key (DRK) passphrase prior and DRK key slot prior of going forward if needed, failing early. - Heads don't expect DRK to be in static key slot anymore, and finds the DRK key slot dynamically. - If reencrytipn/passphrase change: make sure all LUKS containers on same block device can be unlocked with same DRK - Reencryption: requires to know which key slot to reencrypt. - Find LUKS key slot that unlocks with DRK passphrase unlock prior of reencrypt call - Passphrase change: no slot can be passed, but key slot of DRK rotates. kexec-seal-key - TPM LUKS Disk Unlock Key key slots have changed to be set in max slots per LUKS version (LUKSv1:7 /LUKSv2: 31) - If key slot != default LUKS version's keyslot outside of DRK key slot: prompt the user before wiping that key slot, otherwise wipe automatically - This takes for granted that the DRK key slot alone is needed on the system and Heads controls the LUKS key slots. - If user has something else going on, ie: Using USB Security dongle + TPM DUK, then the user will need to say no when wiping keys. - It was suggested to leave LUKS key slots outside of DRK alone, but then: what to do when all key slots would be used? - Alternative implementation could be to only prompt users to wipe keyslots other then DRK when key slots are all used (LUKSv1: 0-7, LUKSv2: 0-31) - But then cleanup would need to happen prior of operations (LUKS passphrase change, TPM DUK setup) and could be problematic. - LUKS containers now checked to be same LUKS version prior of permitting to set TPM DUK and will refuse to go forward of different versions. TODO: - async (AIO) calls are not used. direct-io is used instead. libaio could be hacked out - this could be subject to future work Notes: - time to deprecated legacy boards the do not enough space for the new space requirements - x230-legacy, x230-legacy-flash, x230-hotp-legacy - t430-legacy, t430-legacy-flash, t430-hotp-legacy already deprecated Unrelated: - typos fixes found along the way Signed-off-by: Thierry Laurion <insurgo@riseup.net>
258 lines
9.3 KiB
Bash
Executable File
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
|
|
DEBUG "LUKS device $dev 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"
|