Every once in a while there is a discussion in the Fedora development list about replacing the default GRUB
bootloader. That latest of these threads was "future of dual booting Windows and Fedora, redux" about a month ago, but searching for GRUB
in the mailing list archives one can find many similar conversations.
One of the options to replace GRUB
that is always mentioned is systemd-boot (sd-boot), since is quite minimal and enough for simple booting needs such as what is required for Fedora Workstation.
But currently this can’t be done without additional work, because shim only supports using GRUB
as a second stage loader. There is a RFE: add support for multiple second stage loaders #472 filed for shim
, but there wasn’t a conclusion on that issue about how to move forward.
This means that to use sd-boot
instead of GRUB
and keeping Secure Boot enabled, users need to create their own key pairs and enroll a cert with the public key into the UEFI firmware db
database.
To have an idea of what that would entail, I installed sd-boot
and removed shim
and GRUB
in one of my machines. Below are the steps I followed in case someone wants to replicate it.
To keep the example generic, I mention how a public key can be enrolled with the Open Virtual Machine Firmware (OVMF) UEFI firmware used to boot virtual machines. But a similar procedure can be followed to enroll keys using the UEFI settings of a physical machine. Refer to your hardware vendor documentation for how to do this.
1. Create a key pair to sign the binaries and enroll into the UEFI firmware db key database
$ cat << EOF > configuration_file.config
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts
[ req_distinguished_name ]
O = Organization
CN = Organization signing key
emailAddress = E-mail address
[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
EOF
$ openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 -batch -config \
configuration_file.config -outform DER -out public_key.der -keyout private_key.priv
$ openssl x509 -in public_key.der -inform DER -outform PEM -out public_key.pem
2. Enroll the public cert in the UEFI firmware db database
$ sudo cp public_key.der /boot/efi/
On reboot, enter into the UEFI firmware settings and go to:
Device Manager
Secure Boot Configuration
Secure Boot Mode
That will allow to enroll a key by choosing:
Custom Secure Boot Options
DB options
Enroll Signature
Enroll Signature Using File
After that, when booting the enrolled key should be present in db
and added to the Linux kernel .platform keyring:
$ mokutil --test-key public_key.der
public_key.der is already in db
$ keyctl list %:.platform
3 keys in keyring:
577585465: ---lswrv 0 0 asymmetric: Microsoft Windows Production PCA 2011: a92902398e16c49778cd90f99e4f9ae17c55af53
137367868: ---lswrv 0 0 asymmetric: Organization signing key: ae6f49657c238754eedc503e98cb97afdb064706
984007323: ---lswrv 0 0 asymmetric: Microsoft Corporation UEFI CA 2011: 13adbf4309bd82709c8cd54f316ed522988a1bd4
3. Install sd-boot and remove shim and grub2 packages
$ sudo bootctl install
$ sudo mkdir /boot/efi/$(cat /etc/machine-id)
$ sudo rm /etc/dnf/protected.d/{shim,grub2}*.conf
$ sudo dnf remove shim* grub2* -y
$ sudo rm -r /boot/loader
$ sudo dnf reinstall kernel-core -y
4. Sign the sd-boot and Linux kernel binaries
$ sudo dnf install -y sbsigntools pesign
$ sudo sbsign --key private_key.priv --cert public_key.pem /usr/lib/systemd/boot/efi/systemd-bootx64.efi \
--output /boot/efi/EFI/systemd/systemd-bootx64.efi
$ sudo sbsign --key private_key.priv --cert public_key.pem /lib/modules/$(uname -r)/vmlinuz \
--output /boot/efi/$(cat /etc/machine-id)/$(uname -r)/linux
Image was already signed; adding additional signature
Check that the binaries have been signed correctly:
$ sudo pesign -S -i /boot/efi/EFI/systemd/systemd-bootx64.efi
---------------------------------------------
certificate address is 0x7f348c12b1c8
Content was not encrypted.
Content is detached; signature cannot be verified.
The signer's common name is Test signing key
The signer's email address is e-mail address
Signing time: Thu Sep 01, 2022
There were certs or crls included.
---------------------------------------------
$ sudo pesign -S -i /boot/efi/$(cat /etc/machine-id)/$(uname -r)/linux
---------------------------------------------
certificate address is 0x7fb81a4b11e8
Content was not encrypted.
Content is detached; signature cannot be verified.
The signer's common name is Fedora Secure Boot Signer
No signer email address.
Signing time: Thu Apr 28, 2022
There were certs or crls included.
---------------------------------------------
certificate address is 0x7fb81a4b1b30
Content was not encrypted.
Content is detached; signature cannot be verified.
The signer's common name is kernel-signer
No signer email address.
Signing time: Thu Apr 28, 2022
There were certs or crls included.
---------------------------------------------
certificate address is 0x7fb81a4b26f8
Content was not encrypted.
Content is detached; signature cannot be verified.
The signer's common name is Organization signing key
The signer's email address is e-mail address
Signing time: Thu Sep 01, 2022
There were certs or crls included.
---------------------------------------------
On reboot the machine should be able to boot the signed sd-boot
and Linux kernel with Secure Boot enabled:
$ mokutil --sb-state
SecureBoot enabled
5. Sign an out-of-tree kernel module
This step is mentioned for completeness, since some users need to load out-of-tree kernel modules. And this is only possible for signed modules when Secure Boot is enabled.
If that’s needed, following is an example on how to do it:
$ sudo dnf install kernel-devel -y
$ git clone https://github.com/maK-/SimplestLKM.git
$ pushd SimplestLKM
$ make
$ /usr/src/kernels/$(uname -r)/scripts/sign-file sha256 key-private_key.priv \
public_key.der hello.ko
$ modinfo hello.ko | grep signature
signature: 91:F7:20:16:1C:85:AC:DC:54:25:C2:B2:E9:ED:02:93:79:43:1D:7F:
$ insmod hello.ko
$ dmesg
[ 1653.929819] hello: loading out-of-tree module taints kernel.
[ 1653.930435] Hello world!
A pain point with this setup is that one need to manually sign the sd-boot
and Linux kernel binaries each time that those are updated. This could be automated of course but that would require to have the private key in the filesystem and that would defeat the whole purpose of the Secure Boot protection.
Since in that case an attacker that gets root access could be able to sign any binaries they want and make their attack persistent across reboots.
For this reason what I’ve been done is keeping the private key in an external media and only attach it when there is a need to sign a new binary, which makes updating packages more inconvenient.
Hopefully this won’t be a problem anymore once the mentioned shim
issue #472 gets resolved. Because then the sd-boot
binary could get signed during package build with the Fedora key just like GRUB
and there won’t be a need to sign neither sd-boot
nor the Linux kernel binaries with a custom key.
And that’s all for today. Happy hacking!
Thanks for the article! I went a slightly different route by having systemd-boot load unified kernel+initramfs images generated by dracut. I then use sbctl for .efi signing, dkms’s built-in signing for .ko, and a bit of RPM post-transaction magic to make it all happen automatically.
If your computer can do UEFI firmware updates via fwupd, signing `/usr/libexec/fwupd/efi/fwupdx64.efi` and overwriting `/usr/libexec/fwupd/efi/fwupdx64.efi.signed` and then setting `DisableShimForSecureBoot=true` in `/etc/fwupd/uefi_capsule.conf` will allow things to work.
(By the way, in `sudo rm /etc/dnf/protected.d/{shim,sudo}*.conf`, did you mean to have grub2 instead of sudo?)
oh, it was a typo indeed. Fixed now, thanks for pointing that out!
And yes, using unified EFI images is also interesting for SB. I wondered about mentioning that but thought that could be the topic for another post.