Manual Secure Boot Signing Configuration on Arch Linux
December 27, 2022•762 words
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.