From 3fff2752cda686efc0011ee1b61422a24cbe85c8 Mon Sep 17 00:00:00 2001 From: Fabian Schurig Date: Sun, 11 Aug 2024 17:50:34 +0200 Subject: [PATCH] [Debian/Proxmox | TPM2] Add multiple LUKS devices. Signed-off-by: Fabian Schurig --- 3-tpm2clevis-prepluksandinstallhooks.sh | 139 +++++++++++++----------- 4-register-additional-luks-device.sh | 102 +++++++++++++++++ README.md | 51 +++++++++ res/debian/tpm2clevis/install.sh | 37 +++++-- 4 files changed, 253 insertions(+), 76 deletions(-) create mode 100755 4-register-additional-luks-device.sh diff --git a/3-tpm2clevis-prepluksandinstallhooks.sh b/3-tpm2clevis-prepluksandinstallhooks.sh index bd68867..b231eba 100755 --- a/3-tpm2clevis-prepluksandinstallhooks.sh +++ b/3-tpm2clevis-prepluksandinstallhooks.sh @@ -1,82 +1,89 @@ #!/usr/bin/env bash # Noah Bliss -MORTAR_FILE="/etc/mortar/mortar.env" OLD_DIR="$PWD" -source "$MORTAR_FILE" +MORTAR_DIR="/etc/mortar" +MORTAR_FILES=("$MORTAR_DIR"/mortar*.env) + echo "Testing if secure boot is on and working." od --address-radix=n --format=u1 /sys/firmware/efi/efivars/SecureBoot-* read -p "ENTER to continue only if the last number is a \"1\" and you are sure the TPM registers are as you want them." asdf -# Determine LUKS version of CRYPTDEV if not told. -if [ -z "$LUKSVER" ]; then - if cryptsetup isLuks --type luks1 "$CRYPTDEV"; then - LUKSVER=1 - elif cryptsetup isLuks --type luks2 "$CRYPTDEV"; then - LUKSVER=2 - fi -fi +for MORTAR_FILE in "${MORTAR_FILES[@]}"; do + echo "Processing MORTAR_FILE: $MORTAR_FILE" + source "$MORTAR_FILE" -#Remove tpmfs from failed runs if applicable. -if [ -d tmpramfs ]; then - echo "Removing existing tmpramfs..." - umount tmpramfs - rm -rf tmpramfs -fi + # Determine LUKS version of CRYPTDEV if not told. + if [ -z "$LUKSVER" ]; then + if cryptsetup isLuks --type luks1 "$CRYPTDEV"; then + LUKSVER=1 + elif cryptsetup isLuks --type luks2 "$CRYPTDEV"; then + LUKSVER=2 + fi + fi -#Create tpmramfs for read user luks password to file. -if mkdir tmpramfs && mount tmpfs -t tmpfs -o size=1M,noexec,nosuid tmpramfs; then - echo "Created tmpramfs to store luks key during setup." - trap "if [ -f tmpramfs/user.key ]; then rm -f tmpramfs/user.key; fi" EXIT - echo -n "Enter luks password: "; read -s PASSWORD; echo - echo -n "$PASSWORD" > tmpramfs/user.key - unset PASSWORD -else - echo "Failed to create tmpramfs for storing the key." - exit 1 -fi + # Remove tpmfs from failed runs if applicable. + if [ -d tmpramfs ]; then + echo "Removing existing tmpramfs..." + umount tmpramfs + rm -rf tmpramfs + fi -# if luks1 -if [ "$LUKSVER" == "1" ]; then - echo "Wiping any existing metadata in the luks keyslot." - luksmeta wipe -d "$CRYPTDEV" -s "$SLOT" #Wipe metadata from keyslot if present. -fi + # Create tpmramfs for read user luks password to file. + if mkdir tmpramfs && mount tmpfs -t tmpfs -o size=1M,noexec,nosuid tmpramfs; then + echo "Created tmpramfs to store luks key during setup." + trap "if [ -f tmpramfs/user.key ]; then rm -f tmpramfs/user.key; fi" EXIT + echo -n "Enter luks password to open $CRYPTNAME: "; read -s PASSWORD; echo + echo -n "$PASSWORD" > tmpramfs/user.key + unset PASSWORD + else + echo "Failed to create tmpramfs for storing the key." + exit 1 + fi -# if luks2 -if [ "$LUKSVER" == "2" ]; then - echo "Wiping any clevis token from LUKS header." - cryptsetup token remove --token-id "$TOKENID" "$CRYPTDEV" -fi + # if luks1 + if [ "$LUKSVER" == "1" ]; then + echo "Wiping any existing metadata in the luks keyslot." + luksmeta wipe -d "$CRYPTDEV" -s "$SLOT" # Wipe metadata from keyslot if present. + fi + # if luks2 + if [ "$LUKSVER" == "2" ]; then + echo "Wiping any clevis token from LUKS header." + cryptsetup token remove --token-id "$TOKENID" "$CRYPTDEV" + fi -echo "Wiping any old luks key in the keyslot." -cryptsetup luksKillSlot --key-file tmpramfs/user.key "$CRYPTDEV" "$SLOT" -echo "Generating clevis key, adding it to the luks slot, and mapping it to the TPM PCRs." -clevis-luks-bind -d "$CRYPTDEV" -k tmpramfs/user.key -s "$SLOT" tpm2 '{"pcr_bank":"'"$TPMHASHTYPE"'","pcr_ids":"'"$BINDPCR"'"}' -echo "Wiping keys and unmounting tmpramfs." -rm tmpramfs/user.key -umount -l tmpramfs -rm -rf tmpramfs + echo "Wiping any old luks key in the keyslot." + cryptsetup luksKillSlot --key-file tmpramfs/user.key "$CRYPTDEV" "$SLOT" + echo "Generating clevis key, adding it to the luks slot, and mapping it to the TPM PCRs." + clevis-luks-bind -d "$CRYPTDEV" -k tmpramfs/user.key -s "$SLOT" tpm2 '{"pcr_bank":"'"$TPMHASHTYPE"'","pcr_ids":"'"$BINDPCR"'"}' + echo "Wiping keys and unmounting tmpramfs." + rm tmpramfs/user.key + umount -l tmpramfs + rm -rf tmpramfs + + echo "Adding new sha256 of the luks header to the mortar env file." + if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi + cryptsetup luksHeaderBackup "$CRYPTDEV" --header-backup-file "$HEADERFILE" + HEADERSHA256=$(sha256sum "$HEADERFILE" | cut -f1 -d' ') + sed -i -e "/^HEADERSHA256=.*/{s//HEADERSHA256=$HEADERSHA256/;:a" -e '$!N;$!b' -e '}' "$MORTAR_FILE" + if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi + + # Get slot uuid and write to MORTAR_FILE. + if [ "$LUKSVER" == "1" ]; then + CURSLOTUUID=$(luksmeta show -d "$CRYPTDEV" -s "$SLOT") + if [ -z "$SLOTUUID" ]; then + SLOTUUID="$CURSLOTUUID" + sed -i -e "/SLOTUUID=/{s//SLOTUUID=$SLOTUUID/;:a" -e '$!N;$!ba' -e '}' "$MORTAR_FILE" + echo "Updated SLOTUUID in $MORTAR_FILE" + else + echo "Looks like SLOTUUID was set in the env file. Verify it is still correct with: luksmeta show -d $CRYPTDEV" + if [ "$SLOTUUID" == "$CURSLOTUUID" ]; then echo "[ MATCHES ]"; else echo "[ DIFFERS ]"; fi + echo "ENV file SLOTUUID: $SLOTUUID" + echo "Current SLOTUUID: $CURSLOTUUID" + fi + fi +done -echo "Adding new sha256 of the luks header to the mortar env file." -if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi -cryptsetup luksHeaderBackup "$CRYPTDEV" --header-backup-file "$HEADERFILE" -HEADERSHA256=`sha256sum "$HEADERFILE" | cut -f1 -d' '` -sed -i -e "/^HEADERSHA256=.*/{s//HEADERSHA256=$HEADERSHA256/;:a" -e '$!N;$!b' -e '}' "$MORTAR_FILE" -if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi -# Get slot uuid and write to MORTAR_FILE. -if [ "$LUKSVER" == "1" ]; then - CURSLOTUUID=`luksmeta show -d "$CRYPTDEV" -s "$SLOT"` - if [ -z "$SLOTUUID" ]; then - SLOTUUID="$CURSLOTUUID" - sed -i -e "/SLOTUUID=/{s//SLOTUUID=$SLOTUUID/;:a" -e '$!N;$!ba' -e '}' "$MORTAR_FILE" - echo "Updated SLOTUUID in $MORTAR_FILE" - else - echo "Looks like SLOTUUID was set in the env file. Verify it is still correct with: luksmeta show -d $CRYPTDEV" - if [ "$SLOTUUID" == "$CURSLOTUUID" ]; then echo "[ MATCHES ]"; else echo "[ DIFFERS ]"; fi - echo "ENV file SLOTUUID: $SLOTUUID" - echo "Current SLOTUUID: $CURSLOTUUID" - fi -fi # Figure out our distribuition. source /etc/os-release @@ -85,7 +92,7 @@ tpmverdir='tpm2clevis' if [ -d "$OLD_DIR/""res/""$ID/""$tpmverdir/" ]; then cd "$OLD_DIR/""res/""$ID/""$tpmverdir/" echo "Distribution: $ID" - echo "Installing kernel update and initramfs build scripts with mortar.env values..." + echo "Installing kernel update and initramfs build scripts with mortar*.env values..." bash install.sh # Start in new process so we don't get dropped to another directory. else echo "Distribution: $ID" diff --git a/4-register-additional-luks-device.sh b/4-register-additional-luks-device.sh new file mode 100755 index 0000000..4861411 --- /dev/null +++ b/4-register-additional-luks-device.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Noah Bliss + +# Variables +NEW_CRYPTDEV=$1 +NEW_CRYPTNAME=$2 +MORTAR_DIR="/etc/mortar" +MORTAR_FILE="$MORTAR_DIR/mortar-$NEW_CRYPTNAME.env" +CRYPTTAB_FILE="/etc/crypttab" +FSTAB_FILE="/etc/fstab" +INITIAL_MORTAR_FILE="$MORTAR_DIR/mortar.env" +MOUNT_POINT="/mnt/$NEW_CRYPTNAME" + +# Check if the device and name are provided +if [ -z "$NEW_CRYPTDEV" ] || [ -z "$NEW_CRYPTNAME" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Get the UUID of the device +UUID=$(blkid -s UUID -o value "$NEW_CRYPTDEV") +if [ -z "$UUID" ]; then + echo "Error: Unable to find UUID for $NEW_CRYPTDEV" + exit 1 +fi +NEW_CRYPTDEV="UUID=$UUID" + +# Check if the device is already in crypttab +if grep -q "$NEW_CRYPTDEV" "$CRYPTTAB_FILE"; then + echo "$NEW_CRYPTDEV is already registered in $CRYPTTAB_FILE" + exit 0 +fi + +# Register the hard disk in crypttab +echo "$NEW_CRYPTNAME $NEW_CRYPTDEV none luks" >> "$CRYPTTAB_FILE" +echo "Registered $NEW_CRYPTDEV as $NEW_CRYPTNAME in $CRYPTTAB_FILE" + +# Source the initial mortar env file and store values in temporary variables +source "$INITIAL_MORTAR_FILE" +INITIAL_SLOT=$SLOT +INITIAL_TOKENID=$TOKENID +INITIAL_TPMHASHTYPE=$TPMHASHTYPE +INITIAL_BINDPCR=$BINDPCR + +# Create the new mortar env file +cat < "$MORTAR_FILE" +# Environment variables for $NEW_CRYPTNAME +CRYPTDEV="/dev/disk/by-uuid/$UUID" +CRYPTNAME="$NEW_CRYPTNAME" +SLOT=$INITIAL_SLOT +TOKENID=$INITIAL_TOKENID +TPMHASHTYPE="$INITIAL_TPMHASHTYPE" +BINDPCR="$INITIAL_BINDPCR" +HEADERFILE="./$NEW_CRYPTNAME.header" +EOL + +# Determine LUKS version of NEW_CRYPTDEV +if cryptsetup isLuks --type luks1 "$NEW_CRYPTDEV"; then + NEW_LUKSVER=1 +elif cryptsetup isLuks --type luks2 "$NEW_CRYPTDEV"; then + NEW_LUKSVER=2 +else + echo "Error: $NEW_CRYPTDEV is not a valid LUKS device." + exit 1 +fi +echo "LUKSVER=$NEW_LUKSVER" >> "$MORTAR_FILE" + +# Backup LUKS header and calculate SHA256 +cryptsetup luksHeaderBackup "$NEW_CRYPTDEV" --header-backup-file "/etc/mortar/$NEW_CRYPTNAME.header" +NEW_HEADERSHA256=$(sha256sum "/etc/mortar/$NEW_CRYPTNAME.header" | cut -f1 -d' ') +echo "HEADERSHA256=$NEW_HEADERSHA256" >> "$MORTAR_FILE" + +# Get slot uuid if LUKS version is 1 +if [ "$NEW_LUKSVER" == "1" ]; then + NEW_SLOTUUID=$(luksmeta show -d "$NEW_CRYPTDEV" -s "$INITIAL_SLOT") + echo "SLOTUUID=$NEW_SLOTUUID" >> "$MORTAR_FILE" +fi + +echo "Created $MORTAR_FILE with necessary environment variables." + +# Remove existing mounts for the disk in fstab +sed -i "\|$NEW_CRYPTNAME|d" "$FSTAB_FILE" + +# Create mount point and add to fstab +mkdir -p "$MOUNT_POINT" +echo "/dev/mapper/$NEW_CRYPTNAME $MOUNT_POINT ext4 defaults 0 2" >> "$FSTAB_FILE" +echo "Added $NEW_CRYPTNAME to $FSTAB_FILE" + +# Open the LUKS device +cryptsetup luksOpen "$NEW_CRYPTDEV" "$NEW_CRYPTNAME" +if [ $? -ne 0 ]; then + echo "Error: Failed to open LUKS device $NEW_CRYPTDEV" + exit 1 +fi + +# Mount the device +mount "/dev/mapper/$NEW_CRYPTNAME" "$MOUNT_POINT" +if [ $? -ne 0 ]; then + echo "Error: Failed to mount $NEW_CRYPTNAME at $MOUNT_POINT" + exit 1 +fi +echo "Mounted $NEW_CRYPTNAME at $MOUNT_POINT" diff --git a/README.md b/README.md index e5f74f0..4495c44 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,58 @@ nano /etc/fstab # Remove /boot from /etc/fstab mount -a # remount your EFI partition if it was inside /boot mortar-compilesigninstall # (Optional) Make sure mortar can still find your kernel and initramfs ``` + +## Register additional luks devices +> **_INFO:_** Currently only available and tested with debian/ proxmox and TPM2. Feel free to create a PR for other versions as well. The implementation can be applied easily to other versions as well. + +The concept of adding multiple devices is to add another `mortar*.env` file in `/etc/mortar/` per disk. For each mortar file we create a `mortar*` script in the `local-top` of initramfs. Disks get decrypted one by one and mortar supports different passwords per disk. This way the feature is fully compatible to previous versions using one device. + +You can manually create another `mortar*.env` or you can use the convenience script to add a new (already encrypted) disk (it will add your device to `/etc/crypttab` and `/etc/fstab` as well). + +
+ Steps to register an additional device + - Encrypt your device with LUKS + - Run the register script. `./4-register-additional-luks-device.sh ` (creates new `.env` based on values in existing `mortar.env`) or create another `/etc/mortar/mortar*.env` manually + +```bash +./4-register-additional-luks-device.sh /dev/sda1 sda1_crypt +``` + +It will create a file like this `/etc/mortar/mortar-sda1_crypt.env` +```bash +# Environment variables for sda1_crypt +CRYPTDEV="/dev/disk/by-uuid/" +CRYPTNAME="sda1_crypt" +SLOT=1 +TOKENID=0 +TPMHASHTYPE="sha384" +BINDPCR="7" +HEADERFILE="./sda1_crypt.header" +LUKSVER=2 +HEADERSHA256= +``` +(I replaced generated values with `` and ``) + + - Rerun `./3-` and regenerate EFI. + - Reboot. + +You can check if the files were created correctly for initramfs +```bash +# lsinitramfs /boot/initrd.img-6.8.8-4-pve | grep mortar +scripts/local-top/mortar +scripts/local-top/mortar-sda1_crypt +scripts/local-top/mortar-sdb1_crypt +``` + +If you want to check the initramfs scripts for debugging: +```bash +mkdir -p /tmp/initrd && cd /tmp/initrd +unmkinitramfs /boot/initrd.img-6.8.8-4-pve . +cat ./main/scripts/local-top/mortar-sda1_crypt +``` + +
## TODO: - Add functionality that stores an OTP private key in the TPM instead of a LUKS key. This would allow an end user to leverage a TPM for boot integrity checking without having to trust it to securely store keys. diff --git a/res/debian/tpm2clevis/install.sh b/res/debian/tpm2clevis/install.sh index 79c04e9..6584473 100755 --- a/res/debian/tpm2clevis/install.sh +++ b/res/debian/tpm2clevis/install.sh @@ -4,17 +4,34 @@ # Install the kernel upgrade hook for generation and signing of the efi. cp -r kernel /etc/ -# Install the initramfs script and update hook. +# Install the initramfs script and update hook. cp -r initramfs-tools /etc/ -INITRAMFSSCRIPTFILE='/etc/initramfs-tools/scripts/local-top/mortar' -if ! [ "$1" == "nosource" ]; then source /etc/mortar/mortar.env; fi -sed -i -e "/^CRYPTDEV=.*/{s##CRYPTDEV=\"$CRYPTDEV\"#;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^CRYPTNAME=.*/{s//CRYPTNAME=$CRYPTNAME/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^SLOTUUID=.*/{s//SLOTUUID=$SLOTUUID/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^SLOT=.*/{s//SLOT=$SLOT/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^HEADERSHA256=.*/{s//HEADERSHA256=$HEADERSHA256/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^HEADERFILE=.*/{s##HEADERFILE=\"$HEADERFILE\"#;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" -sed -i -e "/^TOKENID=.*/{s//TOKENID=$TOKENID/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + +MORTAR_DIR="/etc/mortar" +MORTAR_FILES=("$MORTAR_DIR"/mortar*.env) +INITRAMFS_DIR="/etc/initramfs-tools/scripts/local-top" +INITIAL_MORTAR_FILE="$INITRAMFS_DIR/mortar" + +# Remove all previous existing files in /etc/initramfs-tools/scripts/local-top/ except the initial mortar file +find "$INITRAMFS_DIR" -type f ! -name "mortar" -exec rm -f {} + + +for MORTAR_FILE in "${MORTAR_FILES[@]}"; do + echo "Installing with MORTAR_FILE: $MORTAR_FILE" + source "$MORTAR_FILE" + + INITRAMFSSCRIPTFILE="$INITRAMFS_DIR/$(basename "$MORTAR_FILE" .env)" + echo "Create $INITRAMFSSCRIPTFILE ..." + cp "$INITIAL_MORTAR_FILE" "$INITRAMFSSCRIPTFILE" + + + sed -i -e "/^CRYPTDEV=.*/{s##CRYPTDEV=\"$CRYPTDEV\"#;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^CRYPTNAME=.*/{s//CRYPTNAME=$CRYPTNAME/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^SLOTUUID=.*/{s//SLOTUUID=$SLOTUUID/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^SLOT=.*/{s//SLOT=$SLOT/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^HEADERSHA256=.*/{s//HEADERSHA256=$HEADERSHA256/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^HEADERFILE=.*/{s##HEADERFILE=\"$HEADERFILE\"#;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" + sed -i -e "/^TOKENID=.*/{s//TOKENID=$TOKENID/;:a" -e '$!N;$!b' -e '}' "$INITRAMFSSCRIPTFILE" +done update-initramfs -u