Measured Boot, automatic discovery with encryption
This article assumes an Arch Linux system using the default layout and tooling unless stated otherwise.
You should keep configuration under strict control, ideally in dedicated configuration files or a managed repository. Boot-related changes are unforgiving, and small mistakes tend to compound quickly.
The Unified Kernel Image is expected at /efi/EFI/BOOT/BOOTx64.efi, inside ESP mount point /efi. You MUST let systemd handle automatic mounting of ESP, or pay close attention to paths mentioned below.
This solution needs tpm2-tools tpm2-tss ukify, sbctl and dracut.
You should configure command line arguments in /etc/kernel/cmdline.
Status Quo
On a default Arch installation, mkinitcpio generates the relevant initramfs, and the user at install time picks a bootloader, whose job is to load the kernel, initramfs, and user-defined kernel command line arguments. Secure boot requires signing the entirety of ESP, of which may contain dozens of files. Disk encryption either have manual password prompts, or use TPM but only PCR slot 7 which is not very secure.
This is ancient and fragile:
- OS Updates may brick secure boot if even a single file on the ESP is missed or unsigned
- PCR values are hard to pre-calculate when Grub / Shim is involved
- PCR slot 11 is un-measured, and lots of people enroll Microsoft keys to prevent bricking devices with OptionROM. Which means someone may swap the initrd/UKI and perform key extraction.
Let’s address these problems one by one.
What now?
For now, we’ll still use sbctl to manage secure boot keys. Create them if not exists:
1 | sbctl create-keys |
OS Updates brick secure boot
Now that we have Unified Kernel Images, operating system updates typically affect only a single file on the ESP: the UKI itself.
The Unified Kernel Image contains OS release information, kernel command line arguments, the initrd and much more in separate PE sections. It also contains the public key in PEM format that matches the signatures of the .pcrsig PE section, and a JSON file encoding expected PCR 11 hash values.
Essentially, it is the sole executable required to boot the system, which is also executable by the firmware. The “stub” component handles the transition from UEFI to the Linux kernel, as illustrated below:
In moeOS, Unified Kernel Image generation use kernel-install. So let’s configure it via /etc/kernel/install.conf.d/moeOS.conf:
1 | layout=uki |
The file first tells kernel-install to use UKI layout. You may wonder how is ukify and dracut is involved here, it’s because kernel-install alone can’t generate UKIs or initrds, and ukify can’t do initrds either.
You may remember that we are still using sbctl to manage secure boot keys, and it has several hooks like: kernel-install, mkinitcpio, pacman. We need to mask them, to prevent sbctl from messing with kernel images:
1 | ln -sf \ |
Dracut’s pacman hook should also be disabled:
1 | ln -sf /dev/null \ |
With the default hooks removed, we need pacman hooks that trigger kernel-install when relevant files change:
1 | # /usr/share/libalpm/hooks/40-moe-kernel-install.hook |
1 | # /usr/share/libalpm/hooks/90-kernel-install-add.hook |
and the relevant alpm script: /usr/share/libalpm/scripts/moe-kernel-install
1 | #!/bin/bash |
Those should ensure kernel-install is triggered on-demand.
Next, configure Ukify via /etc/kernel/uki.conf:
1 | [UKI] |
Looks self-explanatory, isn’t it? systemd-sbsign is used as a signing tool, while keys generated by sbctl is used to sign the Linux binary itself before it is embedded into the combined image. You may be wondering what the heck is PCR signature? TPM measurements change on every update, but signatures should remain unchanged. Hence it is more useful where software update is possible without losing access to previously-enrolled LUKS2 volumes. We’ll create it with ukify:
1 | ukify genkey \ |
The dracut configuration also needs update, to load the relevant TPM and pcrphase modules:
1 | # /etc/dracut.conf.d/moeOS.conf |
By default, kernel-install installs UKIs into versioned paths. To avoid maintaining UEFI boot entries, we can create an override at /etc/kernel/install.d/90-uki-copy.install to always install it to the standard location, ensuring firmware can always boot the system without relying on NVRAM entries:
1 |
|
Automatic discovery of Root / Swap / ESP
systemd-gpt-auto-generator is a unit generator that automatically discovers the root partition, /home/, /srv/, /var/, /var/tmp/, the EFI System Partition (ESP), the Extended Boot Loader Partition (XBOOTLDR), and swap partitions and creates mount and swap units for them, based on the partition type GUIDs of GUID partition tables (GPT). See UEFI Specification, chapter 5 for more details. It implements the UAPI.2 Discoverable Partitions Specification.
For the auto generator to work, you first need to set the right partition types. See the UAPI.2 specification for detail.
If you use btrfs subvolumes, you may still need a rootflags entry in your kernel cmdline, and an fstab entry for /home. The rest of which (albeit root=, rd.luks.name= cmdline, other fstab entries) can now be removed.
Measuring PCR slot 11
With all components in place, TPM measurement can be extended to PCR 11.
Note: the following assumes your root LUKS partition is in /dev/nvme0n1p2, adapt accordingly!
Enroll TPM PCR 7 and PCR 11 w/ public key:
1 | systemd-cryptenroll /dev/nvme0n1p2 --tpm2-device=auto --tpm2-pcrs=7 --tpm2-public-key=/var/lib/moeOS/TPM-Keys/Public.pem --tpm2-public-key-pcrs=11 |
Re-generate your UKI with kernel-install add-all and reboot.
At this point, the system boots using a single signed UKI, automatically discovers partitions, and unlocks disk encryption using measured boot with PCR 11 enforced.


