Booting Arch Linux Like It's 2012; Secure Boot and TPM
I recently got a new job and with that came a new Laptop: a Dell XPS 13 Plus 9320. The first thing I did before installing the OS, is something that many Linux users will be familiar with: I disabled secure boot. This has been a tried-and-true ritual of mine. But as with all rituals, there comes a time when we need to change them. At the same time, I wanted to modernise other parts of my booting process, while still keeping it reasonably secure. To understand why I did this, I will start by giving a short overview of how I do it “currently” and then show what I did to improve the situation.
Normally, I encrypt my main root partition with LUKS while my boot partition stays unencrypted. I do this because encrypting the boot partition has downsides: it only supports LUKS 1 which would mean entering 2 passphrases. Not encrypting the boot partition means being open to evil maid attacks. But even encrypting the boot partition does not save one from this vector, because the ESP stays unencrypted. Secure boot solves this problem: it only allows booting operating systems that were signed and makes sure that the “boot chain” is secure. This does not prevent all attacks, but provides a reasonable hurdle for most use cases.
Configuring secure boot enables another convenience feature: unlocking the root partition with a key from the TPM rather than a passphrase. To do this, we need to configure the following components:
- systemd-boot
- mkinitcpio
- secure boot
- systemd-cryptenroll
systemd-boot
NB: This part is not strictly necessary, I think it would theoretically be possible to configure GRUB to do the same thing, but it “fits” well into the rest of the setup.
sudo bootctl install
After installing it, we need to add some configuration. First, we edit the
file /boot/loader/loader.conf
.
timeout 0
console-mode max
default arch.conf
editor no
Then we create the default boot entry (arch.conf
).
title Arch Linux
linux /vmlinuz-linux
initrd /intel-ucode.img
initrd /initramfs-linux.img
options root=/dev/mapper/root rw loglevel=3 quiet splash
To update our Bootloader every time the kernel or systemd is updated, we have
2 options. The first option is using systemd-boot-update.service
. This
updates the bootloader on the next reboot. This option won’t work for our setup,
since we need to generate and sign the boot loader files before we reboot. To solve this
issue, I installed systemd-boot-pacman-hook (AUR). There are
other solutions for this problem, but this seemed the simplest. It also works
well with the workflow of sbctl
that I will go into later in this article.
mkinitcpio
After installing the bootloader, we need to make sure that our disk can be decrypted. We do this by configuring the initramfs. I will be using the default tool under Arch Linux (mkinitcpio). If you’re using dracut the setup should be similar, but I have not tested it.
There are 2 ways of decrypting a drive with mkinitcpio: the
encrypt
or the
sd-encrypt
hook. The sd-encrypt
hook uses systemd, and it is the one I will be using. To
configure this hook, first we need to configure /etc/crypttab.initramfs
. This
file gets added to the initramfs and contains the devices that should be
decrypted by systemd-cryptsetup-generator
:
root UUID=<label> none tmp2-device=auto
The last column specifies that we want this drive to be automatically unlocked using a key stored in the TPM. If you want to use a passphrase, omit the last two columns.
We also need to set the correct hooks. To do that, we edit
/etc/mkinitcpio.conf
and change the HOOKS variable to the following:
HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)
Secure boot
This is the most finicky party of the setup, and the exact way of doing this depends on your hardware manufacturer/UEFI implementation. To use secure boot, there are two strategies: using your own keys, or using a signed bootloader. Most distros take the second route. I will be implementing the first one.
Note: Doing this can brick your device. Please make sure you understand what you are doing and don’t blindly follow the instructions in this article.
It’s a good idea to back up the variables, in case something goes wrong. These can then be restored later, if your UEFI supports it.
for var in PK KEK db dbx ; do efi-readvar -v $var -o old_${var}.esl ; done
After that, we need to put the UEFI in “Setup Mode”. This mode is activated
when the Platform Key (PK) is deleted. After deleting the key and rebooting, we
can generate our own keys with sbctl
.
sudo sbctl create-keys
After that, we can enroll these keys into the UEFI. The -m
also enrolls
Microsofts keys. This is necessary on many motherboards, since they use these
keys to sign their firmware. Only leave this option out if you know what you
are doing.
sudo sbctl enroll-keys -m
After that, we can sign our kernel image and boot loader.
sudo sbctl sign -s /boot/vmlinuz-linux
sudo sbctl sign -s /boot/EFI/systemd/systemd-bootx64.efi
Since I use fwupd I signed that one as well.
sudo sbctl sign -s /boot/EFI/Arch/fwupdx64.efi
After they are added once, the files will be automatically resigned every time
the kernel or systemd are updated. This is done via a pacman hook. For that
reason, we installed systemd-boot-pacman-hook
earlier: this automatically
regenerates systemd-bootx64.efi
when systemd-boot is updated. After that, it
will be signed by the pacman hook of sbctl
.
systemd-cryptenroll
Before we generate the key, we need to think about when we want systemd to use the TPM. This is configured using so-called Platform Configuration Registers (PCRs). One of these is the secure boot state (7). This means systemd will only unlock the drive if secure boot is enabled and no secure boot errors have been reported. This is the default setting. Some guides on the internet also use PCR 0 (firmware). To me personally, this does not make much sense. I’m not an expert, but I would think that we have to inherently trust the firmware anyway, since a malicious firmware could theoretically also falsely report PCR 0. If anyone has a good answer to this, feel free to contact me.
In the meantime, I’ve decided to not use PCR 0 since it would be quite annoying (rebinding every time the firmware is updated).
With that discussion out of the way, we can generate our key.
sudo systemd-cryptenroll --tpm2-device=auto /dev/sdX
If you’re paranoid, you can add a PIN as well with --tpm2-with-pin=true
.
It’s also sensible to generate a recovery key and store it somewhere safe. This can be used when systemd can not decrypt the drive from the TPM.
sudo systemd-cryptenroll --recovery /dev/sdX
I’m very happy with this setup. This problem has been in the back of my mind for some years now, and I’m glad I finally tried to solve it. I will probably be using this setup on all of my future Linux machines.