Skip to content

Instantly share code, notes, and snippets.

@jdoss
Last active March 2, 2024 10:31
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save jdoss/777e8b52c8d88eb87467935769c98a95 to your computer and use it in GitHub Desktop.
Save jdoss/777e8b52c8d88eb87467935769c98a95 to your computer and use it in GitHub Desktop.
Decrypt LUKS volumes with a TPM on Fedora 35+

Decrypt LUKS volumes with a TPM on Fedora 35+

This guide allows you to use the TPM on your computer to decrypt your LUKS encrypted volumes. If you are worried about a cold boot attack on your hardware please DO NOT use this guide with your root volume!

Preflight Checks

Verify that you have a TPM in your computer:

# systemd-cryptenroll --tpm2-device=list
PATH        DEVICE      DRIVER
/dev/tpmrm0 MSFT0101:00 tpm_crb

Note: If you have more than one TPM in your computer you will need to change --tpm2-device=auto to the exact TPM you want to use: --tpm2-device=/dev/tpmrm0 and rd.luks.options=tpm2-device=/dev/tpmrm0 in GRUB_CMDLINE_LINUX.

Verify that you are booted into SecureBoot.

# mokutil --sb-state
SecureBoot disabled

If you see that it is disabled you will need to enable it in the BIOS. You should enable SecureBoot before you start.

Note: Enabling SecureBoot will cause third party kernel modules (such as NVIDIA drivers) to fail to load. You can work around this by using something like this to automatically sign the drivers built by akmod.

Find your LUKS encrypted volumes

# blkid -t TYPE=crypto_LUKS
/dev/nvme0n1p3: UUID="0818cd36-a007-11ec-aaab-7c10c93c41b1" TYPE="crypto_LUKS" PARTUUID="c362bcd2-87"
/dev/nvme1n1p1: UUID="15bc3342-a007-11ec-a502-7c10c93c41b1" TYPE="crypto_LUKS" PARTUUID="e8ead241-02"

Enroll your encrypted volumes

# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+4+7+8 /dev/nvme0n1p3
# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+4+7+8 /dev/nvme1n1p1

This will ask for your volume's passphrase. If you'd like to automate this in a script you may set the PASSWORD environment variable to your LUKS passphrase.

When setting --tpm2-pcrs=0+2+4+7+8 the following items are these are validated at boot time:

0: System firmware executable
2: Kernel
4: Bootloader
7: Secure boot state
8: Cmdline

PCR 0,2,4,7,8 verifies the firmware, kernel, cmdline and bootloader before releasing the decryption key. If you are using PCR 2 and multiple kernels you will need to enroll a key for each kernel. If you have updated the firmware, kernel, or bootloader, and cmdline then auto volume decryption on your next reboot will fail. As long as you have a password set on your LUKS volumes you will be prompted to have to enter it to decrypt them and you will need to wipe the old key and enroll a new key if anything changes.

Remove all TPM2 keys and enroll a new key
systemd-cryptenroll /dev/nvme0n1p3 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8
systemd-cryptenroll /dev/nvme1n1p1 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8

Edit /etc/crypttab

Add tpm2-device=auto,discard to the end of each LUKS device line in /etc/crypttab

# cat /etc/crypttab
luks-014aa5a6-a007-11ec-a054-7c10c93c41b1 UUID=0818cd36-a007-11ec-aaab-7c10c93c41b1 - tpm2-device=auto,discard
luks-0e9e99f6-a007-11ec-8130-7c10c93c41b1 UUID=15bc3342-a007-11ec-a502-7c10c93c41b1 - tpm2-device=auto,discard
Edit /etc/default/grub

Edit /etc/default/grub and add rd.luks.options=tpm2-device=auto to GRUB_CMDLINE_LINUX.

GRUB_CMDLINE_LINUX="rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1 rd.luks.uuid=luks-014aa5a6-a007-11ec-a054-7c10c93c41b1 rd.luks.uuid=luks-0e9e99f6-a007-11ec-8130-7c10c93c41b1 rd.luks.options=tpm2-device=auto rhgb quiet rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1"
Add TPM2 support to dracut (Not needed if you are on the current Fedora releases (Fedora 36+)

Work around this BZ by creating /etc/dracut.conf.d/tss2.conf and adding this line to it:

install_optional_items+=" /usr/lib64/libtss2* /usr/lib64/libfido2.so.* "

and then run dracut -f to rebuild the initrd. This should not be needed for Fedora 36+ as it is fixed in Dracut 056.

Recovery Key Enrollment (optional)

If you have a safe place to store a recovery key you can generate and add one for each LUKS volume. It will show the recovery key phrase on screen and generate a QR code you may scan off screen.

systemd-cryptenroll --recovery-key /dev/nvme0n1p3
systemd-cryptenroll --recovery-key /dev/nvme1n1p1
Verify and reboot!

Verify that you have the TPM added to the encrypted volumes:

# systemd-cryptenroll /dev/nvme0n1p3
SLOT TYPE
   0 password
   1 tpm2
   2 recovery
# systemd-cryptenroll /dev/nvme1n1p1
SLOT TYPE
   0 password
   1 tpm2
   2 recovery

and now you can reboot and your TPM should unlock your encrypted drives!

Sources:

@sergeypdev
Copy link

Thanks for this guide!

I tried this on Fedora 37 and skipped the step with dracut -f, but it still asked for the password each time. I had to do dracut -f once to make it work, without any config changes in dracut.d.

I have an idea on how to automate tpm2 key re-enrollment after a system update, so that it can be completely passwordless (but still safe from tampering). If it works I will write a guide on it too :)

@IPlayZed
Copy link

IPlayZed commented Jan 7, 2023

@sergeypdev On Arch this would be done with a Pacman hook, I am not sure if Fedora provides such. BTW don't use in that casethe kernel PCR register for enrollment, as keys are not valid because you run a new kernel version after updating. I think 0+7 is enough. Maybe you could write a script that runs the system update if you have really wanted to and updates the key enrolled. Systemd-enroll will need your keyphrase input anyway so rn there is no way to automate this fully.

@loveisfoss
Copy link

loveisfoss commented Feb 5, 2023

Thanks for this guide!

I tried this on Fedora 37 and skipped the step with dracut -f, but it still asked for the password each time. I had to do dracut -f once to make it work, without any config changes in dracut.d.

I have an idea on how to automate tpm2 key re-enrollment after a system update, so that it can be completely passwordless (but still safe from tampering). If it works I will write a guide on it too :)

Hey! Any news on your automated re-enrollment process? :)

@IPlayZed
Copy link

IPlayZed commented Feb 6, 2023

Thanks for this guide!
I tried this on Fedora 37 and skipped the step with dracut -f, but it still asked for the password each time. I had to do dracut -f once to make it work, without any config changes in dracut.d.
I have an idea on how to automate tpm2 key re-enrollment after a system update, so that it can be completely passwordless (but still safe from tampering). If it works I will write a guide on it too :)

Hey! Any news on your automated re-enrollment process? :)

This entirely depends if systemd will add it as a feature. If they add it, you might have to reenroll enabling no password prompt, depending on how it is implemented.

@stuomas
Copy link

stuomas commented Jun 11, 2023

which step depends on secure boot? What will fail if I don't have it enabled?

@IPlayZed
Copy link

which step depends on secure boot? What will fail if I don't have it enabled?

You should go into setup mode in the firmware before starting the whole process, but at the very least, before trying to enroll keys with sbctl.

@chrsin
Copy link

chrsin commented Jun 15, 2023

After using this script for a while I'm experiencing the following:
"Please enter current passphrase for disk /dev/nvme0n1p3: ************
Failed to add new TPM2 key to /dev/nvme0n1p3: No space left on device"
I've built a small script to automate some of it:

#!/bin/bash
#Enrolls luks into tpm so you don't have to type password everytime you boot
#Script is inspired by the excellent instructions at https://gist.github.com/jdoss/777e8b52c8d88eb87467935769c98a95

set -e
DEVID=$(sudo blkid -t TYPE=crypto_LUKS | awk '{print $1;}' | sed 's/://')

if [ $DEVID ]
then
  	echo Device id is: $DEVID
else 
	echo No encrypted devices found
  	exit 1
fi 

for DEVICE in $DEVID
do
	echo "Enrolling $DEVICE for tpm"
	sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+4+7 $DEVICE
	echo "Done... Generating a recovery key for $DEVICE"
	sudo systemd-cryptenroll --recovery-key $DEVICE
done

I can't figure out how to mount the device. I just get:
"mount: /mnt: unknown filesystem type 'crypto_LUKS'.
dmesg(1) may have more information after failed mount system call."

Does anyone know how to solve this issue?

Thanks in advance

@chrsin
Copy link

chrsin commented Jul 4, 2023

Was able to clear it using the command
systemd-cryptenroll --wipe-slot=tpm2
After that I could add new keys again

@IPlayZed
Copy link

Was able to clear it using the command systemd-cryptenroll --wipe-slot=tpm2 After that I could add new keys again

For the full capabilities see the manual page!

@Blaimi
Copy link

Blaimi commented Oct 8, 2023

DO NOT SKIP PCR8 !!!! (Hash of the kernel command line, Supported by grub and systemd-boot)

If you don't use PCR8, Malory can add init=/bin/bash to the kernel's cmdline in grub and has a root-shell with decrypted harddisk. From here any user-password can be reset. reboot -> login with new password -> full access!!!

@jdoss
Copy link
Author

jdoss commented Oct 9, 2023

@Blaimi Good point and nice catch. I updated the Gist with your suggestion.

@IPlayZed
Copy link

IPlayZed commented Oct 9, 2023

DO NOT SKIP PCR8 !!!! (Hash of the kernel command line, Supported by grub and systemd-boot)

If you don't use PCR8, Malory can add init=/bin/bash to the kernel's cmdline in grub and has a root-shell with decrypted harddisk. From here any user-password can be reset. reboot -> login with new password -> full access!!!

This is irrelevant when using unified kernel images, as the kernel ignores the command line if the image is booted from a secure boot environment. Of course as an additional security policy sure, but your statement is factually wrong if using signed images. If you are not using one, bootkits will bypass any TPM policy check anyways.

@77Lomitko
Copy link

First, I would like to thank you for this wonderful guide. It is straightforward and saved me a lot of time. in my case, something of great importance is missing - do not forget to acctually recreate grub config so that it reflects the change to GRUB_CMDLINE_LINUX (in /etc/default/grub). In my case, that would be
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

that needs to be done before the tpm enrollment. (as doing it after effectively violates the 8 part. ).

__

@Blaimi
Copy link

Blaimi commented Nov 8, 2023

I had to run dracut -f on a fresh fc39 installation. I did not need to change it's configuration, but a rebuild of the initrd was necessary (maybe because of the change in crypttab??)

@fdobrovolny
Copy link

Using the grubby seems to be the right way to add the parameters?

grubby --update-kernel=ALL --args="rd.luks.options=tpm2-device=auto"

@Blaimi
Copy link

Blaimi commented Nov 15, 2023

PCR8 and non-ui reboots or update-installations

During the investigation why the system doesn't unlock when I reboot via ssh without any login, I played around with grub-setenv list and systemd-analyze pcrs. PCR8 does not only hash the cmdline like explained in ArchWiki but also the commands executed in grub (see grub docs). PCR8 is indeed the only register which changes when adding init=/bin/bash to the cmdline.

grubenv

PCR8 does not contain the cmdline only but every command which is executed by grub. This register changes it's content everytime when the grub-env vars boot_success and/or boot_indeterminate are changing because it changes the way grub executes the commands. And because boot_success is only set when a user-session is running for two minutes (systemctl status --user grub-boot-success.{service,timer}) or when rebooting via the gdm-ui (0001-Fedora-Set-grub-boot-flags-on-shutdown-reboot.patch) a reboot via ssh will not set boot_success. This also explains why two reboots are necessary after an update to fix the unlock because this also plays with boot_indeterminate (systemctl cat grub-boot-indeterminate.service).

TL;DR;

I disabled the grub-scripts which are responsible for the autohide and timeout-changes and set a countdown timeout of 1 second:

sudo chmod -x /etc/grub.d/08_fallback_counting /etc/grub.d/12_menu_auto_hide /etc/grub.d/14_menu_show_once /etc/grub.d/10_reset_boot_success
sudo sed -n -e '/^GRUB_TIMEOUT=/!p' -e '$aGRUB_TIMEOUT=1' -i /etc/default/grub
sudo sed -n -e '/^GRUB_TIMEOUT_STYLE=/!p' -e '$aGRUB_TIMEOUT_STYLE=countdown' -i /etc/default/grub
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# then reboot and enroll

PCR9

PCR9 should also be added to the list. The initrd is not measured in PCR8, so an attacker could manipulate it, e.g. with a simple initrd which only prints the luks headers on the screen or uploads it somewhere.

envfile and boot_indeterminate

PCR9 not only get changed when the initrd changes but when any file changes which is read by grub, booting when executing updates asks for the password because of the change in grubenv. To prevent this, execute systemctl disable grub-boot-indeterminate.service.

cmdline

The additinal parameter in the cmdline is not necessary.

@Yrlish
Copy link

Yrlish commented Dec 22, 2023

If you have updated the firmware, kernel, or bootloader, and cmdline then auto volume decryption on your next reboot will fail.

Does this mean that for every kernel update through dnf the boot and decryption will break? And that I manually have to add new keys to tpm?

@IPlayZed
Copy link

If you have updated the firmware, kernel, or bootloader, and cmdline then auto volume decryption on your next reboot will fail.

Does this mean that for every kernel update through dnf the boot and decryption will break? And that I manually have to add new keys to tpm?

It completely depends on your encryption's PCR configuration. See more at man systemd-cryptenroll.

@Blaimi
Copy link

Blaimi commented Dec 31, 2023

Does this mean that for every kernel update through dnf the boot and decryption will break? And that I manually have to add new keys to tpm?

If you have PCR 8 and/or 9 configured: yes. I strongly recommend this because of comment #4717836.

Fedora/RedHat is currently working on a Unified Kernel Image which can also boot without grub. This makes the contents of the PCRs predictable after a update before the reboot has happened and enables the possibility to add the new decryption key without breaking auto-unlock after updates. This prediction is not possible to do with the current implementation of grub and I assume never will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment