#Measured boot with systemd
I travel a lot. (Well, at least in the pre-COVID era, I did.) This means I drag my Framework 13 (and formerly my Thinkpad X1 Carbon gen 6) to many places, and often leave it unattended in hotel rooms. While my data is all encrypted-at-rest, getting access to the data when the machine is first powered on necessarily involves running some code that is neither encrypted nor authenticated: namely, the bootloader and enough of the operating system kernel to decrypt and mount volumes.
Furthermore, this process requires me to type in a passphrase every time I boot the machine, which exposes the passphrase to anyone observing surreptitiously, and is in general an annoyance.
To increase the complexity of someone instrumenting my laptop without my knowledge and in doing so gaining access to secrets as I use the compromised machine unawares, I decided I needed to explore the use of trusted computing technologies to protect my installation from tampering.
Measured boot
There are a number of technologies that can reduce (but of course not eliminate) the attack surface of a machine. For this purpose, I am limiting my evaluation of technologies to those that protect the boot sequence from most kinds of tampering. Other measures are required for runtime protection against zero-day remote exploits, physical attacks that employ USB DMA, tire irons, etc., as well as some advanced kinds of physical tampering.
My adversary is someone who intends to tamper with my machine without my knowledge, with the purposes of either making off with some information or instrumenting the machine such that information encrypted-at-rest is revealed to the attacker out-of-band. There's not a lot of value to me in tamper prevention: once someone's screwed with my machine, I am unlikely to trust it again regardless. My main goal is tamper detection. I thus settled on measured boot, which measures the sequence of executable code launched during the boot process in a way that is highly resistant to forgery: at the end of this process, this measurement can be used to verify that the machine has not been tampered with, as well as to unlock disk encryption keys that can be used to mount volumes without requiring the user enter a passphrase.
Juniper has a short page outlining the differences between measured boot and secure boot.
Requirements
I used to maintain a set of scripts for implementing measured boot on Debian, but it looks like the systemd folks have finally lapped me and put together a much more flexible and complete solution using systemd-cryptenroll. The basic steps for doing this are outlined on a Arch wiki. I used dracut.
NB: You are well-advised to avoid taking bits and pieces of the process from from ChatGPT or other sources, because some steps you might find (like creating a public/private key pair in /etc/systemd) will confuse systemd and cause basic measured boot to fail. So, for instance, to rebuild initrd I need to do dpkg-reconfigure linux-image-<version>
rather than the dracut -f
I see recommended in some places because the latter fails with an obscure error that I haven't had time to track down.
My requirements haven't changed much from when I was maintaining my own scripts, except for now accepting the use of the systemd-boot boot loader:
- Use my laptop's existing TPM, which is the PTT in the (dreaded) Intel Management Engine
- Measure everything from the BIOS through the kernel, initrd, and kernel command line
- Require an expected boot chain measurement before automatically decrypting and mounting any data partitions
- Fall back to passphrase entry on failure
- Avoid the need for any persistent PKI
- Keep the implementation simple and avoid the need for any packages not in the main Debian repository
As a result, I settled on PCRs 0, 2, 4, 11, 12, and 13 to cover all executable code prior to mounting the root partition:
- 0 and 2 measure firmware
- 4 measures the bootloader itself (systemd-boot) and modules
- 11, 12, and 13 cover the kernel, kernel config (i.e., /proc/cmdline), and (this seemingly dubious) "sysexts" thing that sound like a lot of complexity. (I'm still cargo-culting to some extent.)
There may be redundancy in the above set of PCRs (what various components of systemd are measuring into each PCR seems to be covered in detail here), but it's basically free to include stable values, so I'm okay with that.
Here are the pros and cons so far of my experience relative to my own scripts. The pros:
- I no longer have to build EFI blobs, i.e., "unified kernel images" or UKIs, as the bootloader handles measuring, extending, and loading multiple files; which means the basic machinery of installing and upgrading kernels works fine with measured boot.
- I like betting on the winner, because it reduces the amount of maintenance work I need to do over time. If there are bugs, they'll eventually get fixed; and the same mechanism will work on multiple distributions.
The cons:
- The LUKS passphrase must be sealed to the boot block/firmware/bootloader only, because the kernel and initrd are not on the EFI system partition, which means
systemd-bootx64.efi
must be able to decrypt the root partition to get at them. I am assuming the initrd or bootloader checks PCRs values later, or the passphrase is again sealed to the full PCR set specified above and unsealed at mount time in the initrd. This will take some investigation if it isn't actually documented someplace. In either case, the bootloader is a trusted part of the chain, so as long as it cannot be used to take unauthorized actions after unsealing the passphrase, the drive is still protected from all but attacks on system RAM. (Note that this was the main reason I used a UKI in my solution: everything required to get to mount time was stored on the ESP.) - After changing the kernel or initrd, I seem to always have to reboot and enter my passphrase once, suggesting that systemd-measure is not being used to predict the new PCR values. I think systemd-cryptenroll is instead just sealing the passphrase to the existing PCR values, which is a strict downgrade from my solution, but one I expect to have a fix or get fixed.