Secure Boot and Linux Kernel Modules

Secure Boot is a feature of the UEFI (Unified Extensible Firmware Interface) specification that ensures that each software used in the boot process is signed with a key that’s recognized by the system’s firmware (the UEFI BIOS). The purpose is to protect the boot process against bootkit attacks that could inject malicious code.

secure boot image, a key being inserted into a computer screen.

When the system starts up, the firmware checks the signature of each piece of boot software, including the bootloader. If any software isn’t correctly signed with a trusted key, the firmware doesn’t allow the system to boot.

In Linux, the bootloader (i.e., GRUB2) loads the kernel, which again checks the signature to make sure it’s valid. This can also extend to kernel modules via a Linux kernel feature called module signature verification. When this feature is enabled (CONFIG_MODULE_SIG), the kernel checks the signature of each module before it loads the module. If a module’s signature is invalid or if it’s unsigned, the kernel refuses to load the module.

This only provides security if Secure Boot is enabled.

The effectiveness of this entire process depends on the management and protection of the cryptographic keys used to sign the bootloader, kernel, and kernel modules. The private keys used to sign these components must be stored securely to prevent unauthorized access, and the public keys must be correctly installed in the firmware and/or the kernel keyring to allow signature verification to be carried out. If these keys are mishandled, the security of the boot process can be compromised.

Signing kernel modules

To sign a Linux kernel module, you’ll need to generate a signing key pair and then use that key pair to sign the module, so you should first:

  1. Generate a key pair (or use one you have already registered with MOK)
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=Your Name/"

This command generates a new 2048-bit RSA key pair, with the private key written to MOK.priv and the public key is written to MOK.der in DER format.

  1. Sign the kernel module:

To sign a kernel module, you can use the kmodsign command in Linux. kmodsign takes the module, the private key, and the public key as arguments:

/usr/src/kernels/$(uname -r)/scripts/sign-file sha512 ./MOK.priv ./MOK.der path/to/module.ko

This signs the module at path/to/module.ko using the private key in ./MOK.priv and the public key in ./MOK.der, with SHA-512 as the hash algorithm.

Please note that the signature is appended to the binary file; it’s not an ELF section. Using strip on the .ko would remove the signature.

Enrolling MOKs (Machine Owner Keys)

Before the kernel accepts a signed module, we need to enroll the public key into the Machine Owner Key (MOK) list. This can be done using the mokutil command:

mokutil --import MOK.der

This will prompt you to enter a password, which will be required to complete the enrollment process on the next reboot.

UEFI MOK management console

After reboot, once the key is installed, you can list it via:

mokutil -l

You can verify the keys being loaded also by looking at the kernel output:

$ dmesg | grep "integrity:"
[ 0.040036] integrity: Platform Keyring initialized
[ 0.131398] integrity: Loading X.509 certificate: UEFI:MokListRT
[ 0.131512] integrity: Loaded X.509 cert 'Red Hat Secure Boot (CA key 1): 4016841644ce3a810408050766e8f8a29c65f85c'