Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Debian/Proxmox | TPM2] Add multiple LUKS devices. #42

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 73 additions & 66 deletions 3-tpm2clevis-prepluksandinstallhooks.sh
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"
Expand Down
102 changes: 102 additions & 0 deletions 4-register-additional-luks-device.sh
Original file line number Diff line number Diff line change
@@ -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 <device> <name>"
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 <<EOL > "$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"
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<details>
<summary>Steps to register an additional device</summary>

- Encrypt your device with LUKS
- Run the register script. `./4-register-additional-luks-device.sh <device> <name>` (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/<the-uuid>"
CRYPTNAME="sda1_crypt"
SLOT=1
TOKENID=0
TPMHASHTYPE="sha384"
BINDPCR="7"
HEADERFILE="./sda1_crypt.header"
LUKSVER=2
HEADERSHA256=<the-header>
```
(I replaced generated values with `<the-uuid>` and `<the-header>`)

- 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
```

</details>

## 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.
Expand Down
37 changes: 27 additions & 10 deletions res/debian/tpm2clevis/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down