Linux x86 bootloader and TPM2 policy disk encryption explanation
In regular Linux x86 distro, GRUB by GNU is still being the default bootloader. Personally, I don’t like GNU’s overall style, but no matter what as time changes, the latest boot method UKI(Unified Kernel Image) is the future.
It’s not a full tutorial, but an overall concept explanation with many examples you need.
Basic history about bootloader
Installing the bootloader in Arch Linux is always the first difficult and complex step that user encounter. But still under control, the beginners just need to run a few commands to get a GRUB install:
# root
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg
After installation, each time GRUB’s EFI entry is running, it will combine the microcode, initramfs and the kernel image at runtime.
Those file archive images are stored in EFI system partition(ESP), that’s /boot
in the filesystem.
So where do those archives come from?
The kernel file is a binary executable that’s downloaded from package manager, which is usually named vmlinuz-linux
. The microcode is the same, but named *-ucode.img
(e.g. amd-ucode.img
).
But the initramfs wouldn’t appear out of the air. It actually is a cpio archive that contains the basic kernel modules and the essential support files to help the kernel prepare the entire filesystem tree, then switch root into the real filesystem that’s placed on real drives.
About initramfs
In Arch Linux, the initramfs is generated by Mkinitcpio(whisper: because they made this). The other alternatives are Dracut and Booster(Though I never used booster at all).
Anyway, the dracut is a popular solution for other distributions like Fedora, I also use it personally.
In addition, microcode is usually packaged in initramfs. The difference between can be found here: ArchWiki Microcode
Some initramfs generator will package them automatically, so you probably don’t need worry about microcode in the later part.
About boot manager
Remember, the initramfs is just a filesystem archive, they are not executable. The real software that start from UEFI(or BIOS?) can be called “bootloader”, but many of them have a select menu and a kernel parameters editor, so they also can be called “boot manager”.
GRUB is an extremely popular and common software for this purpose, almost all the distributions use it, except Arch Linux is uses systemd-boot. Considering its some auto-discover features, we will also use it later in some non-traditional way.
As an example, my dracut configuration is this:
# /etc/dracut.conf.d/<any_filename>.conf
# the world best compression algorithms.
compress="zstd"
# some essential modules for following steps, I'm not sure.
add_dracutmodules+=" systemd resume crypt tpm2-tss "
# cause bugs on my machine, but may not on yours.
omit_dracutmodules+=" brltty "
You can call it manually, but I will use kernel-install to call it automatically which I’ll explain later.
About stub
There is a last thing may cause some confusing: stub
It’s kind a filler between UEFI and kernel. So if you want, boot the kernel image just from firmware setup menu directly isn’t impossible. Don’t try it yourself, unhealthy for mind…
UKI(Unified Kernel Image)
After finishing the classic method, let’s talk about Unified Kernel Image(UKI).
Systemd’s UKI is basically around the systemd-stub, it’s a stub that we previously talked about. But the special point is it will lookup the resources that kernel needs from its executable binary section self, like kernel command line parameters, kernel binary and initramfs.
The systemd-stub is just a bunch of support files and a format spec, the generator of this format by systemd is systemd-ukify.
It’s a command line focused software, it feels like designed for command but added config file support later.
I didn’t use it individually, but with another systemd tiny script util: kernel-install. It can help me get some kernel ver staff automatically and call ukify.
It sounds like a lot of stuff here, but the basic configuration and the usage are very simple:
# /etc/kernel/install.conf
layout=uki
initrd_generator=dracut
uki_generator=ukify
# /etc/kernel/uki.conf
# basic configuration doesn't need any content in this file
# but if you want... add a splash image are also great (O-O)
[UKI]
Splash=/usr/share/systemd/bootctl/splash-arch.bmp
# /etc/kernel/cmdline
# put your command line parameters here. for example:
ro quiet splash
Then run this to install all the kernel versions with UKI format:
# root
kernel-install add-all
It will auto-discover the ESP mount point(e.g. /boot) and output the .efi
file in /boot/EFI/Linux/
directory. It maybe named <random_id>-6.15.8-arch1-2.efi
.
Now you have two options to boot this efi file with UEFI firmware:
- Fix the filename, and add a UEFI boot entry manually(via efibootmgr?) then boot it directly from firmware.
- Install another boot manager, dynamically search all available efi files then chain-loading them. You will also get a selection menu as a bonus. (o-o)
They are both ok, and I tried both of them for a long time.
By the way, the
.efi
file is actually a PE format executable, just like Windows(because Microsoft is the leader of UEFI).
systemd-boot
systemd-boot is a UEFI boot manager, it’s mainly used to chain-loading other efi files.
It doesn’t require much configuration, it will find common efi files with itself and show them up in the selection menu(like Windows Boot Manager or /boot/EFI/Linux/*.efi).
Run this command to install it, but notice it won’t be auto update by default:
# root
bootctl install
It will generate some stuff in /boot/loader/, don’t care.
Ok, those are all the knowledge you and me should know, time to switch_topic.service to TPM2.
The last
.service
runs in initramfs namedswitch_root.service
PCR(Platform Configuration Registers)
I need to say it is a meaningless term, we can clearly call it “TPM2 Registers”.
The PCR is the most important part of TPM2, which measures the platform state and reflects the states into those registers.
Now, only PCR 0-15 has been used, and the PCR 0-7 are controlled by platform firmware. For example, the PCR 0 means: “Core system firmware executable code”.
A full reference: Linux TPM PCR Registry
As for the PCR 11, this register is used by systemd. The systemd-stub and systemd-pcrphase will continually extend this register in the whole boot process.
- PCR 11 will be reset to 0 when platform power on.
- systemd-stub will measure the bootloader(including kernel) info before the kernel starts.
- systemd-pcrphase just like its name, contains multiple services that build many barriers to separate the PCR 11’s value in each boot state.
Due to the way TPM2 works, the value in PCR are inreversible, so theoretically we can restrict some action to only happen in a specific boot phase.
(even though I didn’t understand this part now, more documents please qwq)
Ukify PCR signatures
An important tool made by systemd is systemd-measure. When generating UKI, this tool is designed for reproduce what the things that systemd-stub and systemd-pcrphase will do with PCR 11 at next time they are started. Unlike PCR 0-7, this is possible because PCR 11 is only related to systemd components in UKI.
It also can sign that PCR 11’s value with a pre-generated asymmetric encryption private key, other application can verify these signature later.
You can run these commands to generate a key pair into the default path:
# root
ukify genkey \
--pcr-private-key /etc/systemd/tpm2-pcr-private-key.pem \
--pcr-public-key /etc/systemd/tpm2-pcr-public-key.pem
To use this key pair, there’s some configuration need to be added to the ukify config file:
# /etc/kernel/uki.conf
# <previous content>
[PCRSignature]
PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem
PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem
After generated a key pair. When next time ukify generating UKI image it will call systemd-measure to pre-calculate the PCR 11, and sign the value by above cert pair then insert the signatures into .pcrsig
UKI executable section.
The public key is also placed in executable section .pcrpkey
. With these two section, the application now can ensure the bootloader has not been tampered with.
You can check all the sections content with ukify inspect <UKI path>
.
Enroll TPM2 policy into LUKS2 disk
The systemd-cryptenroll can help us to setup decrypt method in systemd ecosystem.
If you don’t know, you can use this following command to bind a inflexible PCR value into LUKS2 metadata:
# root
systemd-cryptenroll --tpm2-device=auto /dev/<block_device>
Also add a
--tpm2-with-pin=true
flag will blend a password(PIN) in the authorization process. TPM2 can even protect this PIN out of brute force attacks.
Back to the TPM2 policy. It can use PCR signature(PCR 11 only at here) and asymmetric encryption key pair above to calculate a secret to encrypt/decrypt LUKS2 disks. With this method, the secrets are no longer bound to a specific set of PCR values, instead the trust chain transferred to a proof that the bootloader has not been modified.
To be fair, we always bound the PCR 7(SecureBoot state) as the LUKS2 secret to verify bootloader previously, so we are still doing the same things.
In Arch Linux, each bootloader file are generated in user’s environment self, but in many case the distribution can generate their own key pair and share the public key to the users. This is telling users their bootloader has not been modified and works properly until the decrypt moment.
(I don’t know who really did this at now, it’s a cutting-edge feature of systemd :|)
To use this TPM2 policy, you can use these following flags of systemd-cryptenroll:
# root
systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="" --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem /dev/<block_device>
Emm… you can see the man page online: systemd-cryptenroll
--tpm2-public-key=PATH, --tpm2-public-key-pcrs=PCR[+PCR...], --tpm2-signature=PATH
If you don’t add --tpm2-public-key
, it still will search file tpm2-pcr-public-key.pem
in some directories by default.
And you maybe seen the --tpm2-public-key-pcrs
flag looks so attractive, can I also bind a PCR value together with the TPM2 policy? No, it’s just an option to declare which PCR are signed, but the systemd-measure will only calculate and sign PCR 11, so it’s maybe useless now.
A TPM2 PIN is still recommended.
After enroll, you can try to close and reopen your LUKS2 disk. Since the secret is independent of the PCR’s value, feel free to turn off and on secure boot as a revenge. awa
End
Now I have finally said everything I want to say. It’s not my native language, and also had to reference many manuals. Tired works, it took me about two days.
But I got a new blog post for August now. Yeah, one month peace(or maybe two months).
It’s doesn’t matter, but still thank you, systemd. d(d'∀')