How to sign a kernel module for Secure Boot on Debian in 2023

Posted on Mar 26, 2023

This is a quick guide on how to write a Hello, world kernel module and sign it so that it’s loadable with Secure Boot enabled on Debian.

First, create a working directory, write a simple kernel module named hwkm (hello world kernel module) and compile it:

$ mkdir ~/src/hwkm && cd ~/src/hwkm
$ echo '#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>
static int __init minit(void) { printk(KERN_INFO "hwkm loaded.\n"); return 0; }
static void __exit mexit(void) { printk(KERN_INFO "hwkm unloaded.\n"); }
module_init(minit);
module_exit(mexit);
MODULE_LICENSE("GPL");' > hwkm.c
$ echo 'obj-m += hwkm.o
PWD := $(CURDIR)
all:
\tmake -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
\tmake -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean' > Makefile
$ sudo apt install linux-headers-$(uname -r)
$ sudo apt install clang
$ make
//...output omitted
make[1]: Leaving directory '/usr/src/linux-headers-6.1.0-6-amd64'

This will give you a binary for the kernel module - hwkm.ko. If you try to load it you will see that the kernel rejects it:

$ sudo insmod hwkm.ko
insmod: ERROR: could not insert module hwkm.o: Key was rejected by service
$ sudo dmesg
//...output omitted
[ 4082.598922] Loading of unsigned module is rejected

Why is that? Because major distributions nowadays have Secure Boot enabled by default and Secure Boot requires kernel modules to be signed in order to be loaded. The main program in charge of Secure Boot on Debian is shim, a first-stage bootloader which ships by default in Debian 11 and is signed by the Microsoft UEFI CA. shim checks that the kernel is signed, but also that kernel modules are signed. In order to sign a kernel module, it is necessary to enroll a Machine Owner Key. It is possible that shim already ships with a Machine Owner Key (check /var/lib/shim-signed/mok/), but in case not generate one:

$ mkdir ~/mok && cd ~/mok
$ echo 'HOME                    = .
RANDFILE                = $ENV::HOME/.rnd
[ req ]
distinguished_name      = req_distinguished_name
x509_extensions         = v3
string_mask             = utf8only
prompt                  = no

[ req_distinguished_name ]
countryName             = DE
stateOrProvinceName     = Berlin
localityName            = Berlin
0.organizationName      = claudiuvursache
commonName              = Custom Machine Owner Key
emailAddress            = claudiu@ursache.io

[ v3 ]
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer
basicConstraints        = critical,CA:FALSE
extendedKeyUsage        = codeSigning
nsComment               = "OpenSSL Generated Certificate"' > openssl.cnf
$ openssl req -config ./openssl.cnf \
        -new -x509 -newkey rsa:2048 \
        -nodes -days 36500 -outform DER \
        -keyout "MOK.priv" \
        -out "MOK.der"

As soon as you have a key to import, issue an import request for the Machine Owner Key using mokutil:

$ man mokutil 2&>1 | grep -A1 NAME | grep -v ^N
       mokutil - utility to manipulate machine owner keys
$ sudo mokutil --import MOK.der
input password:
input password again:

Write down the password somewhere. The next step is finalizing the key enrollment using the UEFI program MokManager, which is part of shim. A reboot is required:

$ sudo reboot

After the reboot you will be prompted by MokManager: Image of the program MokManager

Select Enroll MOK: Image of a screen of the program MokManager

Then select Continue: Image of a screen of the program MokManager showing a multi-option selector

When asked to enroll the key, select yes: Image of a screen of the program MokManager showing a yes/no selector

Enter the password you provided to mokutil in the previous step: Image of a screen of the program MokManager showing a password prompt

Finally, reboot: Image of a screen of the program MokManager showing the message ‘reboot’

Now if you check mokutil, you will see the enrolled key:

$ sudo mokutil -l | grep 'Custom Machine' | grep Subject
    Subject: C=DE, ST=Berlin, L=Berlin, O=claudiuvursache, CN=Custom Machine Owner Key/emailAddress=claudiu@ursache.io

And you can use the key to sign the kernel module:

$ /usr/lib/linux-kbuild-$(uname -r | awk -F'.' '{print $1"."$2}')/scripts/sign-file sha256 ~/mok/MOK.priv ~/mok/MOK.der ./hwkm.ko

To confirm that everything worked, load the kernel module, unload it, and then grep dmesg for the appropriate log line:

$ sudo insmod hwkm.ko
$ sudo rmmod hwkm.ko
$ sudo dmesg | grep hwkm
[  408.293551] hwkm: loading out-of-tree module taints kernel.
[  408.294224] hwkm loaded.
[  422.427946] hwkm unloaded.

With that, you’ve successfully signed a kernel module on Debian in 2023.

References

https://wiki.debian.org/SecureBoot

https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/signing-a-kernel-and-modules-for-secure-boot_managing-monitoring-and-updating-the-kernel

https://www.kernel.org/doc/html/v4.15/admin-guide/module-signing.html

https://blog.hansenpartnership.com/adventures-in-microsoft-uefi-signing/

https://docs.oracle.com/cd/F22978_01/tutorial-uefi-secureboot-module/

https://www.rodsbooks.com/efi-bootloaders/secureboot.html#initial_shim

https://www.funtoo.org/UEFI_Secure_Boot_and_SHIM

https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/