linux tpm-based disk encryption basically just works
I set up a new laptop over the holidays, and wanted to figure out how to make my disk encryption auto-unlock a la BitLocker (Windows). There are some scattered docs in ArchWiki and a lot of relevant systemd man pages, but it took quite a lot of effort to figure out exactly what's needed and what security guarantees you get.
The way this basically works: during the boot process two security mechanisms keep things secure:
- Secure boot makes sure that only authorized EFI binaries can be started.
- The UEFI platform and boot binaries like boot loaders, Windows Boot Manager, or systemd-stub "measure" system properties into "PCRs" which are basically variables in the TPM. The PCRs basically operate as blockchains that reset every boot, so once a value is measured into a PCR, that PCR is permanently altered (until next boot). The TPM will only release the decryption key for specific PCR values, which should include PCR 7, measuring the secure boot state.
You can more or less follow this guide in ArchWiki, but a few steps are sort of unclear.
tl;dr Starting with a typical password-based FDE setup without secure boot, do this:
Make sure you're using a systemd-based initramfs.
/etc/mkinitcpio.conf
HOOKS=
should containsystemd
andsd-encrypt
, NOTudev
orencrypt
. See also ArchWiki.Enable secure boot. Use sbctl: reboot into secure boot steup mode, run
sbctl create-keys
thensbctl enroll-keys -m
. You may need to usechattr
to fix some errors, which seems totally fine. See also ArchWiki.mkinitcpio
will automatically usesbctl
to sign generated boot images.Set up a UKI.1 Install
systemd-ukify
. Edit/etc/mkinitcpio.d/linux.preset
and uncomment the*_uki=
lines, choosing a destination on your EFI system partition. Copy your kernel parameters into/etc/kernel/cmdline
(cp /proc/cmdline /etc/kernel/cmdline
) so they are automatically picked up. See also ArchWiki. Add the new UKI files to your bootloader or EFI configuration.[Optional] Set up PCR 11 signing keys.2. Optionally copy
/usr/lib/kernel/uki.conf
to/etc/kernel/uki.conf
for a base config file. Add the following to/etc/kernel/uki.conf
:[PCRSignature:initrd] PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem Phases=enter-initrd
Run
ukify genkey --config=/etc/kernel/uki.conf
. These keys will automatically be picked up by all the systemd tooling, includingmkinitcpio
viaukify
. See man pagesukify(1)
,systemd-measure(1)
,systemd-stub(7)
,systemd-cryptenroll(1)
, etc etc.mkinitcpio -P
and reboot into the new UKI boot entry.Enroll the TPM key:
systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=secure-boot-policy+kernel-config+shim-policy <encrypted drive path>
. (This choice of--tpm2-pcrs
is probably overkill. Omit the parameter to use the default, just secure-boot-policy (PCR 7), which is the bare minimum but likely sufficient. Choice of which PCRs to use is out of scope, but see below for more references.systemd-cryptenroll(1)
Table 1 includes a list of common PCRs.) If you did step #4, PCR 11 will automatically be set up as well.Reboot. Your disk should automatically decrypt.
This offers pretty reasonable protection against stolen laptop key exfiltration attempts - as long as you don't sign any non-UKI images for boot (e.g. bare linux kernel EFI stub). Some questions I had, and answers:
- Why can't I sign the EFI stub outside of a UKI? This vmlinuz-linux could be used by an attacker with a malicious initramfs to exfiltrate the key somehow. Even if you use PCR 11 (step #4 above), the PCR measurements could be extracted from the UKI and spoofed.
- Can't I use shim or PreLoader instead to execute that attack? Theoretically, no, because programs like shim should extend PCR 7. In my testing, the versions of PreLoader and shim that I found both do this, but I couldn't actually find any policy from Microsoft saying that this is a requirement. Since PCR 7 will be modified, the TPM won't release the key. Of course, it's totally possible that some vulnerable EFI binary signed by Microsoft's keys allow arbitrary code execution without extending PCR 7, and the only protection then is updated secure boot dbx (denylist).
- Can't an attacker just disable secure boot? Yes, but then the disk won't auto-decrypt because PCR 7 will be different and the TPM will not release the key.
- Can't an attacker just boot my laptop to decrypt the drive? Yes, your system actually has be secure after boot. Use a strong user password.
- Is this equivalent protection to BitLocker? Close. It's not exactly clear which PCRs BitLocker uses, but it seems like it includes PCR 4, which is updated by the UEFI platform whenever an EFI binary/bootloader is loaded. Windows can manage this because its bootloader rarely changes, and the bootloader unseals the disk decryption key and passes it to the OS. However, we have to load the kernel before unsealing the key. This means that whenever we update the kernel, the PCR 4 value will be changed, and we don't have a reliable way to predict/pre-calculate it. So we would need to manually decrypt the disk after each kernel update, then re-enroll the key into the TPM with the new PCR 4 value. This would be more secure but it's an unacceptable maintenance burden for me. PCR 11 tries to partially address this, but since it's not enforced by the platform, has issues.2
Maybe I will rework this into a full-fledged blog post at some point.
See also Brave New Trusted Boot World by Lennart Poettering (systemd author).
Edit: additional interesting discussion on security guarantees, feat. the GOATs Matthew Garrett and Lennart Poettering on the Fedora forums.
If you post a reply on another blog or social media, or just want to chat, email me! christopher@cg505.com
If we use a normal bootloader, initramfs is uncontrolled attack space. Someone who stole your laptop could replace the unencrypted and unsigned initramfs with a malicious one that just dumps your disk encryption key, and this would not invalidate the secure boot chain.↩
The systemd-stub based UKI that's automatically created by
ukify
(viamkinicpio
in our case) will automatically measure various properties about the UKI into PCR 11. That includes a hash of the linux kernel we're using, and a hash of the initramfs, so this will change any time we update the kernel. However, PCR 11 is designed to be easy to pre-calculate after a kernel update, so we can create a signature which authorizes the new PCR 11 before even rebooting. This allows auto-decryption to continue working correctly after an update, while still measuring certain aspects of the UKI itself. The idea is that this protects against an unauthorized kernel or initramfs being allowed to decrypt the disk. However, keep in mind that 1) secure boot already will keep an unauthorized kernel/initramfs from being booted, and 2) some malicious kernel/initramfs that somehow circumvents #1 could just spoof the PCR measurements and still get access to the TPM key. So I think the only scenario this really protects against is if you want to have multiple secure-boot-signed UKIs, but only some of them are allowed to decrypt the disk. (Could be the case if you have multiple OSes, and one is untrusted?) The "phases" mechanism (seesystemd-pcrphase.service(8)
) can also prevent later boot phases from accessing the TPM key, but this doesn't really matter unless your system is already compromised. [Edit: you can find some additional discussion about this in this LWN thread, but unfortunately it's not particularly enlightening.]↩