Manual Secure Boot Signing Configuration on Arch Linux

For the longest time since I first had a computer with Secure Boot support, I have been using third-party scripts like sbupdate to handle automatic Secure Boot signing on my Arch Linux installations. sbupdate handles generation of Unified Kernel Images (UKI) and signs them for Secure Boot as a whole, allowing the kernel, the ramdisk and the cmdline to be verified at the same time. Although I am skeptical that Secure Boot itself would be very useful to prevent targeted attacks with physical access (especially considering the buggy UEFI implementations), having it would at least prevent some not-so-targeted attacks. It always feels a little bit iffy though, for that I am depending on a script that only exists on AUR, which can break due to dependency updates at any moment, and for that it, as a random script on GitHub, could have security-related bugs that I cannot spot.

Turns out, it is not actually that hard to configure Secure Boot signing manually. mkinitcpio(8) now supports creation of UKIs natively, whose output can be easily signed from a pacman hook. To make mkinitcpio generate UKIs directly instead of just creating the initramfs images, one simply needs to comment out default_image and fallback_image in the respective presets (e.g. /etc/mkinitcpio.d/linux.preset), and replace them with default_uki and fallback_uki, pointing to paths where the UKIs need to be placed, something like (adapted from ArchWiki)

ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
ALL_microcode=(/boot/amd-ucode.img)

PRESETS=('default' 'fallback')

default_uki="/boot/EFI/Linux/archlinux-linux.efi"

fallback_uki="/boot/EFI/Linux/archlinux-linux-fallback.efi"
fallback_options="-S autodetect"

Note that /boot/EFI/Linux is the default path where systemd-boot would search for UKIs and automatically add them to the boot menu (assuming your /boot is also your EFI system partition). Notice that the two lines referring to the initramfs images have been removed entirely -- this causes mkinitcpio to make those images in a temporary directory, and then append them to the UKIs generated directly, without outputting the ramdisk itself as an intermediate step.

To sign the generated images, we only need to write a pacman hook that executes after all the other hooks (including mkinitcpio). We can simply copy the trigger condition of the mkinitcpio hook, as that is when our UKIs will be modified (because we have instructed mkinitcpio to output UKIs). There is one little bit of complication though: since I still want to use systemd-boot to have automatic UKI discovery, I have to sign its EFI binary as well. But systemd-boot updates are not installed to /boot automatically by pacman, and the update service it provides, systemd-boot-update.service, does not support signing hooks. Therefore, I had to add another hook before the automatic signing hook to handle systemd-boot updates.

The systemd-boot update hook is pretty simple (pretty much an exact copy from ArchWiki):

[Trigger]
Type = Package
Operation = Upgrade
Target = systemd

[Action]
Description = Gracefully upgrading systemd-boot...
When = PostTransaction
Exec = /usr/bin/systemctl restart systemd-boot-update.service

The main hook itself simply involves some shell scripting to loop through all unsigned images in /boot/EFI. This is also pretty much adapted from ArchWiki's systemd-boot page:

[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
# We can only target the systemd package
# because systemd-boot does not get installed to /boot by default
Target = systemd

[Trigger]
Operation = Install
Operation = Upgrade
Type = Path
Target = boot/*
Target = usr/lib/modules/*/vmlinuz
Target = usr/lib/initcpio/*
Target = usr/lib/**/efi/*.efi*

[Action]
Description = Signing EFI binaries...
When = PostTransaction
Exec = /usr/bin/find /boot/EFI -type f -name *.efi -exec /usr/bin/sh -c 'if ! /usr/bin/sbverify --list {} 2>/dev/null | /usr/bin/grep -q "signature certificates"; then echo "Signing $1..."; /usr/bin/sbsign --key /etc/efi-keys/DB.key --cert /etc/efi-keys/DB.crt --output "$1" "$1"; fi' _ {} ;
Depends = sbsigntools
Depends = findutils
Depends = grep

Note that two triggers are used for this hook, one for systemd (to re-sign updated systemd-boot binaries), and one largely copied from mkinitcpio to make sure that it executes under the same condition(s) as mkinitcpio. It also assumes your Secure Boot keys are located inside /etc/efi-keys (mostly just DB.key and DB.crt).

Also note that these two hooks must be named with a prefix that's alphanumerically after all the other hooks present in the system, and the systemd-boot one must rank before the signing one. I ended up prefixing the systemd-boot one with 95- and the signing one with zz-.

After all of these have been set up, you can simply re-install or update the current kernel package (or anything that triggers a mkinitcpio remake) to have your kernel images signed. Then, you could simply enroll your Secure Boot keys into your UEFI firmware to enable a fully custom Secure Boot experience.


You'll only receive email when they publish something new.

More from Snowy Day with Peter
All posts