Safe automatic decryption of LUKS partition using TPM2

In this article I demonstrate and explain how to safely decrypt a LUKS encrypted disk automatically using a TPM2 chip, the clevis package and initramfs.

This article is meant for and tested only on Fedora Linux. It assumes using initramfs, dracut and a set of Fedora packages.

This article contains optional sections on background and explains stuff in detail to make debugging easier. If you just want to get it done just jump to the “Prerequisites” and “Configure clevis” sections - but be aware of the security implications.

Motivation

Disk encryption protects your data (private keys and critical documents) through direct access of your hardware. Think of selling your notebook / smartphone or getting it stolen with an opportunistic evil actor. Any data, even if it was “deleted”, may be recoverable and hence fall into the hands of an unknown third party.

Disk encryption does not protect your data from access on the running system, e.g. by malware running in your user or kernel space. It’s already decrypted at that point.

Entering the passphrase to decrypt the disk at boot can become quite tedious. On modern systems a secure hardware chip called “TPM” (Trusted Platform Module) can store a secret to decrypt your disk automatically. This is considered an alternative factor opposed to a second factor. Keep that in mind! If done right it’s a valid alternative with a similar level of security compared to entering the passphrase on every boot. I’ll elaborate a bit more on possible pitfalls in the Security Implications.

Background

A TPM2 chip is a little hardware module inside your device which basically provides APIs for either WRITE-only or READ-only information. This way you might write a secret onto it, but you can never read it out later (but the TPM may use it later internally). Or you can write info at one point which can only be read later. The TPM2 provides something called PCRs (Platform Configuration Registers). These registers can be filled with SHA1 or SHA256 hashes and are used to measure and assert integrity of let’s say your UEFI configuration.

Secure Boot can be enabled in a systems UEFI. Among other things it computes hashes of every component in the boot-chain (UEFI and it’s configuration, bootloader, etc.) which are chained together such that a change in one of those components changes the computed and stored hashes in all following PCRs. This way you can build up trust about the environment you are in, when it comes to decrypting your disk for example. The UEFI Secure Boot specification defines PCRs 0 - 7. Everything beyond that is free for the OS and applications to use. Here is a little summary what is measured in the PCRs according to the spec:

Some examples on what is measured into which PCR:

A tool called clevis generates a new decryption secret for the LUKS encrypted disk, stores it in the TPM2 chip and configures the TPM2 to only return the secret if the PCR state matches the one at configuration time. At boot clevis then attempts to retrieve the secret and decrypt the disk which it only can if the state is trusted.

Security Implications

You read a lot about trust in this article. This is because trust has to be established. At it’s root we trust the manufacturer of the TPM2 and the root PCR to be immutable. Next we trust that if the UEFI, bootloader, kernel, initramfs, etc. are all unmodified we expect a trustworthy environment where it is OK to e.g. decrypt the disk. In terms of Secure Boot this means all the measurements stored in the PCRs do not change from one boot to another. This trust would be violated if one alters the UEFI settings, the boot partition, or boot the system from a live image instead of our main operating system.

You kinda have to trust or better verify that the manufacturer did not f* anything up in the overall platform design for this to be considered fairly safe. That being said there are a range of cases where they f* things up. E.g. when security researches showed that BitLocker on a Lenovo notebook would use unencrypted SPI communication with the TPM2 leaking the LUKS passphrase in plain text without even altering the system. Or that BitLocker may use native encryption features of SSD drives which may be by-passed through factory reset.

These examples are all about BitLocker but it should become clear that if the overall design is broken, then the secret is accessible and this alternative less secure than a passphrase only present in your head (and somewhere safe like a password manager). On the other hand keep in mind that in most cases elaborate research and attacks to access a drives data are not worth the effort for an opportunistic bad actor. Additionally not having to enter a passphrase on every boot should help adoption of this technology as it is transparent but adds additional hurdles to unwanted access.

Prerequisites

First check that:

Clevis is where the magic happens. It’s a tool we use in the running OS to bind the TPM2 as an alternative decryption method and use it inside the initramfs to read the decryption secret from the TPM2.

In the running system check if secure boot is enabled. The output of dmesg should look like this:

$ dmesg | grep Secure
[    0.000000] secureboot: Secure boot enabled
[    0.000000] Kernel is locked down from EFI Secure Boot mode; see man kernel_lockdown.7
[    0.005537] secureboot: Secure boot enabled
[    1.582598] integrity: Loaded X.509 cert 'Fedora Secure Boot CA: fde32599c2d61db1bf5807335d7b20e4cd963b42'
[   35.382910] Bluetooth: hci0: Secure boot is enabled

If a TPM2 chip is available it is also logged in dmesg:

$ dmesg | grep TPM
[    0.005598] ACPI: TPM2 0x000000005D757000 00004C (v04 DELL   Dell Inc 00000002      01000013)

Install the clevis dependencies and regenerate your initramfs using dracut.

sudo dnf install clevis clevis-luks clevis-dracut clevis-udisks2 clevis-systemd
sudo dracut -fv --regenerate-all
sudo systemctl reboot

The reboot is important as only then the new PCR values based on the initramfs will be available for the next step.

Configure clevis

Command to bind the LUKS-encrypted partition with the TPM2 chip. Point it to your (root) LUKS partition and specify the PCRs it should use.

Enter your current LUKS passphrase when asked. The process uses this to generate a new independent secret, tying your LUKS partition to the TPM2 as an alternative decryption method. So if it does not work you may still just enter your decryption passphrase as usual.

sudo clevis luks bind -d /dev/nvme... tpm2 '{"pcr_ids":"1,4,5,7,9"}'

As mentioned in the Background section PCRs 1, 4 and 5 change when booting into another system such as a live disk. PCR 7 tracks the current UEFI Secure Boot policy and PCR 9 changes if the initramfs loaded via EFI changes.

Note: if you just want to protect the LUKS passphrase from live images but don’t care about more “elaborate” attacks such as altering the unsigned initramfs on the unencrypted boot partition, then you might omit PCR 9 and safe yourself the trouble of rebinding on updates.

Updating your system

On every update of your system that makes changes to the kernel, grub2 or initramfs you’ll have to rebind the TPM2, if you opted to use PCR 9. This is because this PCR will change after such an update and rightly so as this is protection against unwanted changes.

Rebind the TPM with the following command, entering your primary LUKS encryption passphrase:

sudo clevis luks regen -d /dev/nvme0n1... -s 1

More encrypted disks

In case of secondary encrypted partitions use /etc/crypttab:

Use systemd-cryptenroll to register the disk for systemd to unlock:

sudo systemd-cryptenroll /dev/nvme0n1... --tpm2-device=auto --tpm2-pcrs=1,4,5,7,9

Then reflect that config in your /etc/crypttab by appending the options tpm2-device=auto,tpm2-pcrs=1,4,5,7,9.

Unbind, rebind, and edit

List all current bindings of a device:

$ sudo clevis luks list -d /dev/nvme0n1... tpm2
1: tpm2 '{"hash":"sha256","key":"ecc","pcr_bank":"sha256","pcr_ids":"0,1,2,3,4,5,7,9"}'

Unbind a device:

sudo clevis luks unbind -d /dev/nvme0n1... -s 1 tpm2

The -s param specifies the slot of the alternative secret for this disk stored in the TPM. It should be 1 if you always unbind before binding again. If in doubt print alternative bindings with:

Regenerate binding, in case the PCRs have changed:

sudo clevis luks regen -d /dev/nvme0n1... -s 1 tpm2

Edit the configuration of a device:

sudo clevis luks edit -d /dev/nvme0n1... -s 1 -c '{"pcr_ids":"0,1,2,3,4,5,7,9"}'

Troubleshooting

Disk decryption passphrase prompt shows at boot, but goes away after a while

To avoid mounting the drives before the TPM is loaded slow down systemd by adding a sleep command to the systemd-ask-password-playmouth.service file using systemd edit:

[Service]
ExecStartPre=/bin/sleep 10

Add the following to the config file /etc/dracut.conf.d/systemd-ask-password-plymouth.conf:

install_items+=" /etc/systemd/system/systemd-ask-password-plymouth.service.d/override.conf "

Then regenerate dracut via sudo dracut -fv --regenerate-all

Reboot and then regenerate the binding:

sudo systemctl reboot
...
sudo clevis luks regen -d /dev/nvme0n1... -s 1

Resources

Any thoughts of your own?

Feel free to raise a discussion with me on Mastodon or drop me an email.

Licenses

The text of this post is licensed under the Attribution 4.0 International License (CC BY 4.0). You may Share or Adapt given the appropriate Credit.

Any source code in this post is licensed under the MIT license.