
Enable secure boot on a custom kernel
Once upon a time secure boot seemed a measure to prevent people from running anything other then MS Windows, wrapped in a nice statement about significantly improving computer security. But today that is no longer the case, probably because not all bios-es allow you to turn it off, today people running Linux can also utilize secure boot from booting GRUB to loading the Linux kernel to loading kernel modules. Secure boot utilizes cryptographic keys to verify the validity of kernel space software and as such does greatly improve on overall system security. If someone manages to infiltrate your system on this level there is little they cannot do, and do so undetected! Secure boot makes such exploits a lot harder.
But what if you compile your own kernel, perhaps because you need something not included by your Linux distro of choice? You can still utilize secure boot, but you need a couple of things and most importantly get everything right to make it work.
You need:
- To load your public keys in DER format into the MOK (machine own key) storage
- Sign your kernel using a private key.
- Sign your kernel modules using a private key.
- If you utilize the Linux kernel option to also verify inserted kernel modules by public key, point the kernel to your public keys in PEM format upon build.
When I was trying to get all of this to work I stumbled upon a chicken/egg problem: if you add public keys to the MOK storage and reboot, the system asks you to install those keys regardless if secure boot is enabled or not. However those keys are only added to the system’s platform keyring after a successful secure boot. So I used a Fedora 41 live USB image , which has secure boot enabled, to bootstrap my own secure boot 🙂
Personally I use AlmaLinux 9.5 which is a RHEL9 clone and compile my own kernel based on the Elrepo main-line kernel. Today all I need is the dm-vdo
kernel module which is included in kernel 6.9+ but not enabled in the default kernel-ml package from Elrepo. So I use their RPM spec and rpmbuild
to build the kernel packages. This spec has support for signing both kernel and modules, it’s just not enabled by default. So for me it was relatively easy to just alter the spec here and there. I do realize that your milage will probably vary: if you are into building your own kernel you probably don’ t use AlmaLinux or Elrepo kernels 😀 If anybody want’s the spec file I gladly provide it, but for the remainder of this post I’ll try to keep it more generic.
A piece of advise: you should keep everything that contains the private keys used for signing the kernel or kernel modules in a secure place, I suggest moving it to encrypted storage you don’t automount and sym-linking it back to its original location.
First of all you need to create public/private key pairs for both kernel signing and kernel module signing, from what I understand of this these need to be two key-pairs. On my distro you do this with efikeygen
and store them in a NSS database located in /etc/pki/pesign
:
efikeygen --dbdir /etc/pki/pesign \
--self-sign \
--module \
--common-name 'CN=Your organization module signing key' \
--nickname 'Custom Secure Boot Module key'
efikeygen --dbdir /etc/pki/pesign \
--self-sign \
--kernel \
--common-name 'CN=Your organization kernel signing key' \
--nickname 'Custom Secure Boot Kernel key'
Now to sign the kernel you can use:
pesign --certificate 'Custom Secure Boot Kernel key' \
--in {vmlinuz-image} \
--sign \
--out {vmlinuz-image.signed}
Afterwards you can overwrite your kernel image with the signed one.
For kernel modules a bit more is needed, included with the kernel headers is scripts/sign-file which needs the public and private key for the module key pair you just created. Also if you want the kernel to also verify module signatures you need to point the kernel at build time to the public keys of all key pairs you use for kernel modules. This includes dkms if you use that, which uses its own key-pair by default. The keys are located in /var/lib/dkms
. Use CONFIG_SYSTEM_TRUSTED_KEYS
and point it to a file where you concat all your public keys in PEM format. You can do so with:
openssl x509 -inform der -in {public der cert} -out {public pem cert.pem}
...
cat key1.pem key2.pem ... > certstore.pem
First export the public and private key in DER format:
certutil -d /etc/pki/pesign \
-n 'Custom Secure Boot Module key' \
-Lr \
> {public cert.cer}
pk12util -o {private cert.p12} \
-n 'Custom Secure Boot Module key' \
-d /etc/pki/pesign
Enter a password when prompted. Now export the private key in plain text and enter the password you just entered when prompted:
openssl pkcs12 \
-in {private cert.p12} \
-out {private cert.priv} \
-nocerts \
-noenc
Now you can sign your modules. You probably want to include this in your build process..
/usr/src/kernels/$(uname -r)/scripts/sign-file \
sha256 \
{private cert.priv} \
{public cert.cer} \
{a kernel module.ko}
Finally you need to upload your public keys with mokutil to the MOK storage:
mokutil --import {public cert.cer}
mokutil --import /var/lib/dkms/mok.pub
When you reboot your system will ask you if you want to install the MOK keys.
Now you should be ready to enable secure boot. Remember you need to allow your system to secure boot once to allow it to promote the MOK keys to its final destination. A Ubuntu live image and a USB key will suffice. Enter your UEFI bios, enable secure boot and boot form the USB key. Now reboot, enter your UEFI bios once more and make sure the normal boot order is restored. If everything is in order you should now be able to boot your custom kernel using secure boot. If you made sure your private keys (kernel, module and possibly dkms) are not available by default, it now becomes pretty hard to install a kernel level rootkit 🙂