The Correct Way to use Secure Boot with Linux
Previously, I’ve setup full disk encryption on my system with the key stored in TPM. One annoyance with the setup was that the TPM seal was tied to my kernel, so any time I update my kernel or initramfs, I will have to reboot, enter my passphrase and re-add my encryption key to the TPM. (It has to be after a reboot because you don’t know what your PCR value will be unless you’re booted with that kernel.)
The solution to that is Secure Boot. Previously, I’ve complained that Secure Boot is useless on x86-based systems since anyone can boot a “trusted” Windows Installation disk. However, I was looking it the wrong way. The goal is not to prevent the boot up of untrusted executables, but to know if the executable you’ve booted is trusted or not.
The way to achieve this is to take control of Secure Boot by generating our own keys and installing it to the system. There are 4 different stores in Secure Boot:
- PK — Platform Key. Generated by the computer’s manufacturer. Used to update KEK.
- KEK — Key Exchange Key. Used to update db and dbx. A Microsoft key is usually installed to allow Windows to update the database.
- db — Signature Database. Usually provided by Microsoft. List of signatures and hashes allowed to boot the system.
- dbx — Forbiddened Signature Database. List of signatures and hashes that is not allowed to boot the system even if it is in db. This is used to revoke signed binaries where exploits have been later found. Microsoft updates this list through Windows Update.
I am not going to list the instruction of how to setup the keys here since there are many other good guides on the Internet. Here is a good guide by Rod Smith. Note that if you want to boot Windows, you will have to save the existing key and also load them into the database.
To allow my Linux kernel + initramfs + cmdline bundle to be trusted (see my previous article), I add the follow command to the hook used to build my kernel bundle. (Note that it is highly important that we sign only the bundle and not only the kernel, otherwise someone might be able to subvert the security by changing the kernel or initramfs which are not encrypted.)
sbsign --key /etc/efi-keys/db.key --cert /etc/efi-keys/db.crt --output /efi/EFI/Linux/Linux.efi /efi/EFI/Linux/Linux.efi
So how does this tie into the TPM? Remember that the TPM have many PCRs and PCR 7 is the “Secure Boot status”. What I’ve found is that on my system, the PCR is changed depending on the key used to boot. For example, if I boot my system using a kernel signed by my key, the value of PCR 7 will be different than if I boot using PreLoader or shim which are signed by Microsoft’s key. Thus, even if someone tamper with my boot process and use PreLoader or shim to boot up the system instead, they will not be able to decrypt my harddisk because the PCR 7 value is wrong and the TPM will not let them retrieve the key.
Finally, I can change my encryption script to only use PCR 7. Now when I change my kernel, I no longer need to go through the process of re-adding the key every time there is a kernel update.
WARNING: As one commenter pointed out that his PCR 7 does not vary on the signature being used in Secure Boot, this behavior is firmware dependent and your system firmware might have a bug causing PCR 7 to not change. Thus, you will want to check the behavior of your system before applying this strategy.
WARNING 2: Another commenter pointed out that this setup has a vulnerability where an attacker can replace the LUKS partition with their own partition (which they know the password to), boot into the partition, retrieve the stored encryption key from the TPM (since all PCR values still match) and decrypt the original partition. Please see this discussion thread for a potential mitigation strategy.