Skip to main content

Linux server with UEFI Secure Boot and LKRG

UEFI Secure Boot is an useful control to prevent trojanizing of a server and strongly recommended whenever you actually run a physical machine, either as a standalone server or host for virtual machines. On its own it’s not particularly difficult to configure with mainstream Linux distributions thanks to the fact that signing keys for distributions like Ubuntu are already distributed along with any modern BIOS. There’s one particular scenario where some customisations are required — when you run a Linux kernel in Secure Boot mode and want to load additional kernel modules.

Requirements for a vanilla Ubuntu installation with Secure Boot are rather minimal: Secure Boot just has to be enable in Standard mode in BIOS, the latter should be password-protected for administrator access. Standard mode means a pre-defined set of vendor keys are hardwired in the BIOS and used to verify signature on the bootloader (grub) and then subsequently kernel. Kernel booted in Secure Boot mode is in kernel_lockdown(7) mode, which means no unsigned kernel modules may be loaded. That greatly limits the risk of an attacker loading kernel-level rootkits and obtaining persistence in the system. When you use a vanilla kernel from a mainstream Linux distribution, that’s all you need.

My servers however also use LKRG (Linux Kernel Runtime Guard) which needs to be compiled and installed as a kernel module. By default, locked down kernel will refuse to load such module, and the proper way of loading it is to get it signed. For that purpose UEFI offers a Custom mode of Secure Boot, in which the system owner is allowed to modify the database of public keys stored in BIOS for verification of new kernels. The utility used to manage this database is mokutil, where MOK stands for Machine Owner Key. Entering the mode involves a few reboots initially, and the process is intentionally designed in a way that requires hands-on presence of an administrator at least when enabling it.

First, the BIOS needs to be reconfigured into the Secure Boot Custom mode. When system stards, the administrator needs to generate a new X.509 self-signed certificate and private key that will be used to sign new modules. First let’s create an openssl.cnf file that will contain basic information about the system — it’s only used internally, but if you have a few servers it helps when it contains actually useful information as it will help you identity specific keys e.g. when you revisit the server’s BIOS in a year’s time:

HOME                    = .
[ req ]
distinguished_name      = req_distinguished_name
x509_extensions         = v3
string_mask             = utf8only
prompt                  = no

[ req_distinguished_name ]
countryName             = GB
0.organizationName      = My Organisation
commonName              = LKRG Secure Boot Signing
emailAddress            = info@example.com

[ v3 ]
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer
basicConstraints        = critical,CA:FALSE
extendedKeyUsage        = codeSigning,1.3.6.1.4.1.311.10.3.6,1.3.6.1.4.1.2312.16.1.2
nsComment               = server.example.com

Now we’re ready to generate the signing key and self-signed certificate:

$ openssl req -config ./openssl.cnf -new -x509 -keyout signing_key.pem -newkey rsa:2048 -nodes -days 36500 -outform DER -out signing_key.der

While BIOS import uses DER format, Linux kernel build process wants PEM, so we need to convert and keep both:

$ openssl x509 -inform DER -in signing_key.der -outform PEM -out signing_key.x509

The certificate is just a regular, self-signed X.509 certificate:

$ openssl x509 -in signing_key.x509 -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            6d:17:da:2f:66:16:a6:69:4c:5e:62:d5:52:68:d3:41:0e:48:d7:e9
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = GB, CN = LKRG Secure Boot Signing, emailAddress = info@example.com
        Validity
            Not Before: Oct 30 13:21:46 2022 GMT
            Not After : Oct  6 13:21:46 2122 GMT
        Subject: C = GB, CN = LKRG Secure Boot Signing, emailAddress = info@example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b5:62:6b:a2:a3:a2:79:a0:6a:e2:b2:d0:4b:73:
                    ...
                    14:95
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                DB:A1:48:63:17:5E:6F:C3:64:FE:90:19:1E:79:A7:E2:3F:0E:C0:BC
            X509v3 Authority Key Identifier: 
                keyid:DB:A1:48:63:17:5E:6F:C3:64:FE:90:19:1E:79:A7:E2:3F:0E:C0:BC

            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Extended Key Usage: 
                Code Signing, 1.3.6.1.4.1.311.10.3.6, 1.3.6.1.4.1.2312.16.1.2
            Netscape Comment: 
                prol.krvtz.net
    Signature Algorithm: sha256WithRSAEncryption
         08:0b:b7:9e:a3:af:72:78:ad:bd:f0:3e:67:55:16:b2:a5:f4:
         ...
         29:0f:ab:c4

Now we use mokutil to import the signing certificate into the BIOS database:

$ sudo mokutil --import signing_key.der

The utility will ask for a password. The only purpose of the password is to ensure an authorised person will be importing the key into BIOS, and the password will be only used once so it doesn’t have to be extremely complex but you at least have to remember it until the next reboot. After that’s done, we can check if the key is scheduled for enrollment:

$ mokutil --list-new

Now it will just sit in EFI waiting for reboot. After you reboot, BIOS will for a moment display a screen offering to perform MOK enrollment, usually with a timeout, so you need to choose that option in time:

MOK enrollment wizard in UEFI BIOS

The wizard will offer to view the certificate, which should be the same as you generated a moment ago. It will then ask for confirmation for enrollemn, then it will ask for the password you specified when running mokutil and it will finally enroll the key. Then you just reboot the system into Linux again.

MOK enrollment confirmation in UEFI BIOS

Once again use mokutil to confirm whether the key is correctly loaded into BIOS:

$ sudo mokutil --test-key signing_key.der 
signing_key.der is already enrolled

Once that is done, you can proceed with building any Linux kernel modules. In this example, we’ll use LKRG. Compilation of kernel modules required kernel header files to be installed:

$ sudo apt install linux-headers-generic

Now we’re ready to compile LKRG:

$ git clone https://github.com/openwall/lkrg.git
$ cd lkrg
$ make

If you try to load the compiled module now, it will fail as it’s yet unsigned. Module compilation workflow expects the signing keys to be present in certs/ subdirectory of the kernel source tree:

$ sudo cp signing_key.* /usr/src/linux-headers-$(uname -r)/certs/

Now we’re ready for signing the module, which happens as part of the installation workflow:

$ sudo make install
$ sudo systemctl start lkrg

The module should load without any problems now, as it’s properly signed:

● lkrg.service - Linux Kernel Runtime Guard
     Loaded: loaded (/etc/systemd/system/lkrg.service; enabled; vendor preset: enabled)
     Active: active (exited) since Tue 2022-11-01 17:18:34 UTC; 2 days ago
       Docs: https://lkrg.org
   Main PID: 569 (code=exited, status=0/SUCCESS)
      Tasks: 0 (limit: 18455)
     Memory: 0B
     CGroup: /system.slice/lkrg.service

Moreover, you will be able to see the actual signature placed with your key on the module:

$ sudo modinfo lkrg
filename:       /lib/modules/5.4.0-131-generic/extra/lkrg.ko
license:        GPL v2
description:    pi3's Linux kernel Runtime Guard
author:         Adam 'pi3' Zabrocki (http://pi3.com.pl)
srcversion:     28E35D3E70D354955A6ACC3
depends:        
retpoline:      Y
name:           lkrg
vermagic:       5.4.0-131-generic SMP mod_unload modversions 
sig_id:         PKCS#7
signer:         LKRG Secure Boot Signing
sig_key:        6D:17:DA:2F:66:16:A6:69:4C:5E:62:D5:52:68:D3:41:0E:48:D7:E9
sig_hashalgo:   sha512
signature:      5A:C3:C3:EE:CD:A1:35:EE:CC:55:B7:C6:8D:E2:3C:EB:DA:04:52:47:
		...
		3D:D4:78:75:49:14:7B:BF:B1:E4:96:B2:8D:96:47:19
parm:           log_level:log_level [3 (issue) is default] (uint)

Find me on Fediverse, feel free to comment! See how