ยปMeasured Debian Boot with TPM 2.0 and UEFI
I travel a lot. (Well, at least in the pre-COVID era, I did.) This means I drag my laptop 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
From various places (see references for a list of such resources) I managed to piece together a working measured boot that unseals a LUKS decryption passphrase for the root partition on a trusted Debian installation, requiring only the plaintext EFI system partition and not an additional plaintext /boot
partition. This solution provides both tamper detection (the machine won't boot automatically if the passphrase cannot be unsealed) and protection for data-at-rest (via the use of dm-crypt for the root partition).
I had a few requirements going into this project:
- Use my machine's existing TPM (in most cases nowadays provided by the CPU or SOC rather than discrete)
- 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
- Use my machine's native UEFI boot manager with no intermediate/chaining bootloader (like systemd-boot or Trusted GRUB)
- Avoid the need for any persistent public key infrastructure (PKI)
- Keep the implementation simple and self-contained, avoiding the need for any packages not in the main Debian repository
Work in Progress
The current implementation of my measured boot solution is available as a work-in-progress on GitHub. While I've put a lot of work into idiot-proofing it, there is still some not-insignificant setup required before it will install, so don't attempt this lightly.
With a little bit of investigation, I managed to get it to work with more recent versions of the systemd EFI stub (in Debian Trixie, this is version 257 and is located in the systemd-boot-efi package). Via a gross hack, it will also work with the older version I had been pinned to for years (version 247). The difference in behavior between the two is that the newer versions extend PCR 4 via a separate measurement of just the kernel; this, despite the fact that the kernel has already been measured into PCR 4 by the firmware as part of the unified kernel image (UKI). I wish this behavior were configurable or that the stub were smart enough to know how it was packaged and avoid the re-measurement when in UKI mode. That said, I don't want to maintain my own stub source code, so this is an acceptable trade-off given that systemd-boot is designed to use a chainloader with separate kernel and initrd images rather than UKIs.
I will hopefully soon begin work on turning this into a Debian package. That said, while it will perform safety checks to prevent you from locking you out of your system, it will not automate any of the steps (listed in the repo README) required to convert your partition layout into one that can support this mechanism. The next step will then be to modify the Debian installer to create a fresh install with the proper configuration.
Alternatives
The systemd-cryptenroll package provides similar protection and may be a better choice for most users, at least once the dust settles and everything works out of the box rather than requiring one to piece together a config from sources that may be out-of-date. The downsides to their implementation from my perspective are:
- It introduces another element into the trusted computing base (TCB), mainly the systemd-boot bootloader, whereas this solution trusts only the hardware, BIOS/UEFI, and chosen unified kernel image.
- It requires a plaintext
/boot
partition in addition to the EFI system partition (ESP), while this solution places the kernel and initrd inside unified kernel images that are located directly on the ESP. - It requires dracut rather than initramfs-tools. I hate dracut.
The one benefit of systemd-cryptenroll's approach is the use of public key crypto to sign an arbitrary number of measurements that can be stored alongside the kernel, obviating the need to re-seal the LUKS passphrase itself to each permitted kernel version. This level of indirection is appealing, and it may be something I implement in the future should systemd-cryptenroll not first achieve my security requirements with plug-and-play simplicity.