Switching on Secure Boot in Debian
My main desktop machine is an AMD Ryzen-2400G on an ASUS PRIME B350M-A motherboard. It came with secure boot disabled and as the first action I installed Debian on it. Back in early 2019, Debian 10 (i.e. Buster) was stable and based on an 4.19 kernel. I knew that the integrated Radeon Vega GPU of this SoC required a more recent kernel, so I used Debian testing (to become Bullseye) right from the beginning. It took me a bit of fiddling until two monitors worked correctly, but after that it became my main machine.
I did read about enabling secure boot back then, but it seemed the implementation for GNU/Linux systems had not been defined properly, and so I left it disabled. Now that I understand the process better, I want to use that additional level of security against unwanted software on my main desktop. This blog post will explain not only the basics but also follow through to the nitty-gritty of key handling. This will be in the context of Debian that I run on my machine, but we will also compare this to an Ubuntu laptop to get an idea on how the distributions differ in handling of this topic
Secure Boot Introduction
To understand the implementation, we will take a quick look at the intent of the whole secure boot process to understand the big picture. Equipped with this understanding, we can take a look at how this maps to the tools available in Debian.
The Intent
The idea behind secure boot is to only allow operating system code to execute on your machine originating from a trusted party. Note that secure boot does not extend to user space, i.e. application programs. We believe that operating systems can confine these, so they cannot do bad things. "Bad things" in this context are usually keyboard sniffers, data exfiltrators and other malware. So even with secure boot enabled, we can execute arbitrary programs in GNU/Linux, but we are sure that the operating system is not compromised. If you want to further confine applications, then you will need additional techniques like sand-boxing or SELinux but this is beyond this blog post.
Fundamental for the definition of trust is the transitivity of signature chains. Because we cannot ourselves evaluate every single piece of code for its trustworthiness, we delegate this evaluation to other parties. This trust will then transitively extend to pieces of code trusted by those parties. Every piece of software comes with its own certificate and those trust relationships are expressed by signatures on these certificates. So to understand the secure boot flow, we will have to understand not only the software but also the set of certificates and signatures at a given time. While secure boot is functional, this set really is state of the process, so it can and will change over time and requires its own attention. The origin of this transitive process is called the root of trust and of course plays a special role. The chain of trust can potentially be compromised at any location, but if the root of trust is compromised, the whole secure boot flow breaks down.
Boot Flow
The root of trust for a regular x86 PC is the Unified Extensible Firmware Interface (UEFI) flashed on the motherboard in a flash chip. The UEFI image and the keys in its tables are considered as trusted. UEFI implements a secure boot chain where every chain member is required to be trusted before it is executed. Before passing control to the next component, the integrity of this next component needs to be ensured, before passing transfer to it.
Note that there is also the measured boot mode of execution. In
this mode, the chain links will be "measured" against known good
values and execution stops if the measurements derive from those
values. In practice this is implemented by a TPM
and a set of
Configuration Registers
, but this is not the topic of this post.
So let's look at the boot flow and the key tables involved:
In order to understand this flow, we need to consider the key tables
involved and look at their contents on this machine. The mokutil
binary (from the mokutil
package) allows us to look at this data. We
will look closer at what a MOK (Machine Owner Key) is later in this
document.
PK
The Platform Keys (PK) represent the manufacturer of the platform:
dzu@krikkit:~$ sudo mokutil --pk | grep '\(^\[key\|CN\)'
[key 1]
Issuer: CN=ASUSTeK MotherBoard PK Certificate
Subject: CN=ASUSTeK MotherBoard PK Certificate
dzu@krikkit:~$
KEK
Only owners of Key Exchange Keys (KEK) are allowed to modify the key database:
dzu@krikkit:~$ sudo mokutil --kek | grep '\(^\[key\|CN\)'
[key 1]
Issuer: CN=ASUSTeK MotherBoard KEK Certificate
Subject: CN=ASUSTeK MotherBoard KEK Certificate
[key 2]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation KEK CA 2011
[key 3]
Issuer: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
Subject: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
dzu@krikkit:~$
Again we see a key by the motherboard manufacturer, a Microsoft key and a Canonical key. I am pretty sure that the first two keys were there already when purchasing the machine, but I am not too sure where the Canonical key comes from. It may have been added to the database by booting Ubuntu a long time ago. Those three parties are therefore able to change the keys for secure boot without you noticing.
DB
The Database (DB) of keys (and hashes) contains keys to validate later stages in the boot process:
dzu@krikkit:~$ sudo mokutil --db | grep '\(^[ \t]*\[\|CN\)'
[key 1]
Issuer: CN=ASUSTeK MotherBoard SW Key Certificate
Subject: CN=ASUSTeK MotherBoard SW Key Certificate
[key 2]
Issuer: CN=ASUSTeK Notebook SW Key Certificate
Subject: CN=ASUSTeK Notebook SW Key Certificate
[key 3]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation UEFI CA 2011
[key 4]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011
[key 5]
Issuer: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
Subject: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
[key 6]
[SHA-256]
dzu@krikkit:~$
The Microsoft Corporation Third Party Marketplace Root certificate
is especially noteworthy. It is included in practically all
commercial UEFI releases and the GNU/Linux ecosystem decided to use a
signature with this key to bootstrap its own key management. As it is
impractical for Microsoft to sign every bootloader update
(e.g. GRUB
), it was decided to introduce a very small, easily
audited, piece of software into the regular boot flow. This piece of
software is the shim depicted in the above flow chart.
DBX
The Revocation List (DBX) of keys (and hashes) contains entries that are explicitly revoked because of security problems. Usually this list consists of hashes to exclude single problematic versions of programs rather than whole signature keys that have been revoked.
So when a security problem becomes known, securing machines is done by pushing the hash of the affected software into the DBX tables. Of course this should only happen after a new (secured) version of the software has been installed. Otherwise, the boot chain will break, and the system will become unbootable. This seems to be a theoretical problem but the BlackLotus malware has shown that this is also a practical problem. Microsoft acknowledged in 2023 that a proper fix for this malware takes at least a year because they are afraid to render systems unbootable if they did not receive software updates before pushing out the hashes to revoke the problematic software.
Here are some entries to show how they look like:
dzu@krikkit:~$ sudo mokutil --dbx | head -5
[key 1]
[SHA-256]
80b4d96931bf0d02fd91a61e19d14f1da452e66db2408ca8604d411f92659f0a
f52f83a3fa9cfbd6920f722824dbe4034534d25b8507246b3b957dac6e1bce7a
c5d9d8a186e2c82d09afaa2a6f7f2e73870d3e64f72c4e08ef67796a840f0fbd
dzu@krikkit:~$
State of Secure Boot
To check, enable or disable secure boot, again mokutil
can be
used. Here we see that it is enabled on my machine:
dzu@krikkit:~$ sudo mokutil --sb-state
SecureBoot enabled
dzu@krikkit:~$
Theoretical Summary
Data Structures (state)
This is a summary of the relevant data structures involved in the secure boot flow.
Key | Verifies | Update verified by | Note |
---|---|---|---|
PK | New PK, New KEK, New db/dbx | PK | Platform key |
KEK | New db/dbx | PK | Key Exchange Key |
db | UEFI Image | PK/KEK | Authorized Image Database |
dbx | UEFI Image | PK/KEK | Forbidden Image Database |
Control Flow
Loading an UEFI Image (in our case Shim), follows this procedure:
MOK
With the keys and certificates we looked at until now, our system
would actually not trust a kernel that we have compiled ourselves. Not
even kernel modules that we compile locally will be loaded as long as
the Linux kernel is configured in lockdown mode and this will be the
case by default when secure boot is enabled in UEFI. So kernel
modules compiled by dkms
will not be loaded on the machine. In
practice this usually means that proprietary Nvidia drivers
distributed in "source" form, compiled by dmks
, will not load
without further steps.
Creating a unique MOK (Machine Owner Key), enrolling it in DB and signing kernel modules with it solves this problem. A MOK can have a passphrase to protect its usage. Specifying an empty pass phrase and relying on file permissions to only allow root means that any malware running with root rights will be able to sign malware and execute it on your machine. I will thus use a passphrase, but this means that it will not be possible for automatic updates to install and successfully sign kernel modules. Instead, a manual step is required where I need to provide the passphrase manually.
Creating And Enrolling A MOK
Creating a signing key is pretty standard with the OpenSSL tool set, so
I will not describe it in great detail. Here is a transcript to show
how it works to create the key below /var/lib/shim-signed/mok
. The
location is not really important, but it seems that many examples use
this place, so I'll simply stick with it.
dzu@krikkit:~$ sudo -i
root@krikkit:~# mkdir -p /var/lib/shim-signed/mok
root@krikkit:~# cd /var/lib/shim-signed/mok
root@krikkit:/var/lib/shim-signed/mok# openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -days 36500 -subj "/CN=Detlev Zundel/"
....+...........+.+..+.+..+...+....+.................+...............+......+............+...+...+.+.........+..+....+.....+.+........+......+...+...+.......+...+.........+......+...+.........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+....+..+....+.....+...+.+.....+....+...+..+..........+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*...+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
..+..+.............+......+.....+....+..+...+..........+..+...+....+..+.+...+.....+......+.+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*..+...+......+..+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*.+......+.+...............+........+................+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
root@krikkit:/var/lib/shim-signed/mok# openssl x509 -inform der -in MOK.der -out MOK.pem
root@krikkit:/var/lib/shim-signed/mok# mokutil --import MOK.der
input password:
input password again:
root@krikkit:/var/lib/shim-signed/mok# mokutil --list-new
[key 1]
SHA1 Fingerprint: 01:a0:f6:64:31:b6:b8:6e:78:61:29:27:57:c5:af:1f:c8:1f:e7:d4
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
3c:65:a3:6f:71:a4:cd:8c:6d:04:49:03:cb:11:84:5a:96:b7:5b:0a
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Detlev Zundel
Validity
Not Before: Aug 19 22:15:05 2022 GMT
Not After : Jul 26 22:15:05 2122 GMT
Subject: CN=Detlev Zundel
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a9:19:d6:90:c6:63:b8:4a:b2:4b:2a:bc:66:e1:
54:fc:0e:df:3e:19:aa:57:2c:e7:52:aa:64:ad:ae:
48:9d:5e:76:40:f9:98:05:2d:fb:2a:22:7c:59:65:
15:3a:da:4e:24:90:24:8c:32:fe:c7:47:d3:53:2f:
a0:db:5d:b8:02:63:cf:18:e5:c6:c0:82:f1:cf:d4:
dc:c1:84:d9:a9:a2:1b:d6:65:2c:cd:58:44:a6:e4:
f0:37:86:64:1d:9f:ad:7a:49:29:ec:29:20:54:8d:
e7:b8:b2:e9:34:d5:f7:5f:06:8e:be:08:a2:6c:6d:
f6:bf:0c:81:dc:97:e9:f6:b8:8d:77:96:c1:5a:16:
fb:38:a6:cc:6c:c7:69:67:36:6a:af:5f:27:c5:fc:
aa:9b:16:75:8f:ff:d4:55:e6:d7:1d:79:f1:b2:15:
2b:53:a5:03:f3:16:45:3d:78:3f:20:89:09:b3:db:
34:4d:00:c2:e6:8f:fd:8c:00:5a:20:1c:13:aa:ba:
7f:69:ab:18:30:3b:8e:58:17:2d:9a:8d:5d:da:5c:
89:61:ef:03:46:78:6e:a6:3f:4a:50:85:a6:3f:a7:
a4:c8:55:a3:60:d5:28:d7:92:b7:ca:94:a1:82:9b:
d8:c9:9f:7e:11:4c:e4:86:31:d7:78:3f:76:d6:dc:
3a:f7
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
EC:8A:FD:29:F8:74:D7:41:C5:ED:3C:C9:41:62:B6:FD:C1:B7:58:55
X509v3 Authority Key Identifier:
EC:8A:FD:29:F8:74:D7:41:C5:ED:3C:C9:41:62:B6:FD:C1:B7:58:55
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
a1:07:ea:50:a6:16:06:d8:1f:0c:c2:d7:18:a6:b3:4a:7c:e2:
e3:82:1b:7f:c6:22:c2:75:0f:15:1e:9a:94:2b:3f:3d:0e:8f:
fd:ab:70:b8:13:fd:c2:44:20:59:28:39:b3:ab:37:31:0b:88:
f7:82:43:27:07:6e:a5:a5:85:0a:24:d5:63:d0:7e:ee:a0:ec:
e3:38:af:a1:83:ee:ce:18:ec:0e:18:77:38:f9:2b:07:f7:ed:
f6:fe:fe:69:fe:ba:09:fd:b6:65:f3:ac:a2:39:e7:32:50:96:
5f:79:bd:ed:7e:f1:36:c2:75:6e:0b:a9:f3:11:45:8c:10:4b:
42:4b:e7:5e:4e:f5:90:a4:1d:ed:e4:f1:50:53:b1:65:15:d8:
6b:a7:6a:46:b7:69:0f:8a:b4:7f:d0:d3:2e:47:88:af:4e:1c:
e4:a7:99:0c:22:f5:27:7f:a3:57:8d:0b:cb:f2:85:a4:a5:d9:
49:50:94:20:2e:70:41:e1:8f:1a:ec:da:5a:a2:68:fc:aa:05:
85:09:89:a0:b3:80:f0:4e:f8:3e:08:a2:c6:4e:16:b4:81:b2:
0f:24:d5:89:fb:7a:c9:25:97:d2:86:69:12:6b:9b:5c:1b:a4:
5d:fb:eb:fa:0e:b0:35:b5:51:29:0c:7c:36:70:1b:83:d7:78:
66:38:2a:b6
root@krikkit:/var/lib/shim-signed/mok#
The actual enrollment is taking place on the next reboot, when UEFI
will start the MOKManager
UEFI application. This will prompt you
for the password you defined in the mokutil --import
call. The idea
behind this is that only a real person can enroll a key. Even if some
malware called mokutil --import
, the key is not yet enrolled but
only prepared to be enrolled by the MOKManager
. Hopefully the user
will notice on the next reboot that there is something fishy going on
if the machine boots into MOKManager
and can then simply decline the
new enrollment.
Checking New MOK Key
Having done the actual enrollment on a subsequent boot, we can finally make sure that our key is now present:
dzu@krikkit:~$ sudo mokutil --list-enrolled | grep '\(^\[key\|CN\)'
[key 1]
Issuer: CN=Debian Secure Boot CA
Subject: CN=Debian Secure Boot CA
[key 2]
Issuer: CN=Detlev Zundel
Subject: CN=Detlev Zundel
dzu@krikkit:~$
More On Kernel Modules
The Linux documentation has a section Kernel module signing facility explaining the possible handling of module signatures at run time. Here are the relevant settings for my current Debian kernel:
dzu@krikkit:~$ grep CONFIG_MODULE_SIG /boot/config-`uname -r`
CONFIG_MODULE_SIG_FORMAT=y
CONFIG_MODULE_SIG=y
# CONFIG_MODULE_SIG_FORCE is not set
# CONFIG_MODULE_SIG_SHA1 is not set
# CONFIG_MODULE_SIG_SHA224 is not set
CONFIG_MODULE_SIG_SHA256=y
# CONFIG_MODULE_SIG_SHA384 is not set
# CONFIG_MODULE_SIG_SHA512 is not set
CONFIG_MODULE_SIG_HASH="sha256"
CONFIG_MODULE_SIG_KEY_TYPE_RSA=y
# CONFIG_MODULE_SIG_KEY_TYPE_ECDSA is not set
dzu@krikkit:~$
The standard modinfo
command will show us signatures on kernel
modules. Here is a sample call showing a kernel module signed by my
MOK key:
dzu@krikkit:/lib/modules/5.19.0-1-amd64/updates/dkms$ /sbin/modinfo v4l2loopback
filename: /lib/modules/5.19.0-1-amd64/updates/dkms/v4l2loopback.ko
license: GPL
author: Vasily Levin, IOhannes m zmoelnig <zmoelnig@iem.at>,Stefan Diewald,Anton Novikovet al.
description: V4L2 loopback video device
depends: videodev
retpoline: Y
name: v4l2loopback
vermagic: 5.19.0-1-amd64 SMP preempt mod_unload modversions
sig_id: PKCS#7
signer: Detlev Zundel
sig_key: 3C:65:A3:6F:71:A4:CD:8C:6D:04:49:03:CB:11:84:5A:96:B7:5B:0A
sig_hashalgo: sha512
signature: 22:95:5A:13:C8:60:6F:EB:1A:C9:93:00:9B:1F:B7:E4:FD:7F:9F:86:
6B:DE:99:98:12:77:39:54:9F:34:C4:3D:8B:6C:1F:D2:F3:66:CA:52:
95:B4:66:5E:14:89:A7:5F:DC:A9:27:63:FE:19:8A:EB:F1:4D:67:2B:
2C:21:0D:88:7E:70:A6:51:76:E7:D3:1E:9D:C6:FC:83:77:11:EA:9A:
8F:B7:62:6E:FE:06:22:8C:44:57:4E:A8:43:20:2D:AE:5D:A6:24:4D:
2B:B7:08:40:B5:E6:43:13:58:ED:3A:4B:53:96:F4:61:93:0F:10:03:
DD:8A:C8:DA:AB:C7:D2:D4:62:43:95:1A:DB:9C:2A:ED:B7:00:42:F1:
22:08:B7:33:4F:F1:1C:78:59:1A:8A:6F:E6:8F:29:06:DD:D4:A7:11:
58:E0:88:05:E5:43:24:49:39:2F:BC:C1:70:DB:5D:7B:F7:6B:CE:4C:
80:BD:05:88:2C:6A:13:F3:EC:09:2A:11:EF:5C:56:D6:33:9E:EE:FB:
04:C5:C1:63:92:86:95:88:28:5E:3F:A5:A5:1E:8D:5A:51:E2:A0:34:
51:6F:C7:23:71:78:E6:21:35:24:D1:66:70:C5:F3:0E:DD:A9:DD:08:
21:3E:00:51:C6:8A:9D:E9:AC:9A:52:B5:8D:A5:BE:5B
parm: debug:debugging level (higher values == more verbose) (int)
parm: max_buffers:how many buffers should be allocated (int)
parm: max_openers:how many users can open loopback device (int)
parm: devices:how many devices should be created (int)
parm: video_nr:video device numbers (-1=auto, 0=/dev/video0, etc.) (array of int)
parm: card_label:card labels for every device (array of charp)
parm: exclusive_caps:whether to announce OUTPUT/CAPTURE capabilities exclusively or not (array of bool)
parm: max_width:maximum frame width (int)
parm: max_height:maximum frame height (int)
dzu@krikkit:/lib/modules/5.19.0-1-amd64/updates/dkms$
Signing Modules
The kernel documentation shows us how to use the kernel provided
script sign-file
(compiled from scripts/sign-file.c
in any Linux
source tree) to sign modules. Probably for historical reasons, Ubuntu
is distributing this binary renamed to kmodsign
and so Ubuntu
documentation gives this basic template to sign a kernel-module:
KBUILD_SIGN_PIN="xxxx" kmodsign sha512 MOK.priv MOK.der module.ko
In practice, we usually have a few modules to sign, so it makes sense to batch the calls in a simple shell loop:
root@zarniwoop:~# cd /var/lib/shim-signed/mok/
root@zarniwoop:/var/lib/shim-signed/mok# for f in /lib/modules/5.4.0-126-generic/updates/dkms/*.ko ; do \
KBUILD_SIGN_PIN="xxxx" kmodsign sha512 MOK.priv MOK.der $f ; done
root@zarniwoop:/var/lib/shim-signed/mok#
Of course, we can take this approach somewhat further and package it up in a script that will do all of this and even some more:
#!/bin/bash
#
# sign-kernel-modules: Sign previously unsigned kernel modules for
# secure boot. Only the dkms modules need to be handled as the
# upstream modules are signed correctly.
#
set -e
shopt -s nullglob
usage() {
echo "usage: $(basename "$0") [-h] [-r <kver>]" >&2
echo " Signs all previously unsigned kernel modules below" >&2
echo " /lib/modules/<rev>/updates/dkms" >&2
echo " If no revision is specified with -r, then all installed versions" >&2
echo " will be processed." 2>&2
exit 1
}
HELP=no
KVER='*'
while getopts hr: OPTION
do
case $OPTION in
r) KVER=$OPTARG
;;
h) HELP=yes
;;
*) echo "Unknown option: $OPTION"
;;
esac
done
shift $(( OPTIND - 1 ))
if [ "yes" = "$HELP" ]; then
usage
fi
MOKDIR=/var/lib/shim-signed/mok/
KMODSIGN=/usr/bin/kmodsign
if [ ! -x $KMODSIGN ]; then
KMODSIGN=/usr/src/linux-headers-$(uname -r)/scripts/sign-file
if [ ! -x "$KMODSIGN" ]; then
echo "No usable 'kmodsign' binary found - aborting" >&2
echo "You should try installing a kernel headers package," >&2
echo "like 'linux-headers-amd64'" >&2
exit 1
fi
echo "Seems to be a Debian system, i.e. using KMODSIGN=$KMODSIGN"
fi
MODINFO=/sbin/modinfo
get_x509_subject() {
# In older versions, there are spaces around the equal sign ...
openssl x509 -in $1 -subject -noout | sed 's/subject=CN \?= \?//'
}
get_ko_signer() {
$MODINFO $1 | grep signer: | sed 's/^signer:[ ]\+//'
}
SUBJECT=$(get_x509_subject $MOKDIR/MOK.der)
cd /lib/modules || exit
for rev in $KVER
do
if [ ! -d "$rev" ]; then
echo "Revision $rev not found. Exiting." >&2
exit 1
fi
echo Processing "$rev"
for module in "${rev}"/updates/dkms/*.ko "${rev}"/updates/dkms/*.ko.xz
do
# Check if module is signed by correct key already
SIGNER=$(get_ko_signer $module)
if [ "$SIGNER" = "$SUBJECT" ]; then
echo "$module" already signed with correct certificate
continue
fi
if [ -n "$SIGNER" ]; then
echo Module signed by $SIGNER - will be overwritten
fi
if [ -z "$KBUILD_SIGN_PIN" ]
then
read -rs -p "Enter MOK passphrase: " KBUILD_SIGN_PIN
export KBUILD_SIGN_PIN
echo
fi
if [[ "$module" =~ .xz$ ]]
then
sudo unxz "$module"
sudo --preserve-env=KBUILD_SIGN_PIN \
$KMODSIGN sha512 $MOKDIR/MOK.priv $MOKDIR/MOK.der "${module%%.xz}"
sudo xz "${module%%.xz}"
else
sudo --preserve-env=KBUILD_SIGN_PIN \
$KMODSIGN sha512 $MOKDIR/MOK.priv $MOKDIR/MOK.der "$module"
fi
echo Successfully signed module "$module"
done
done
This script can be run any time and will make sure that newly compiled
kernel modules are signed by the key located at
/var/lib/shim-signed/mok/MOK.der
.
As this script serves me well now for some time, I put it onto Codeberg for your convenience.
Kernel signatures
Now that we know how to sign kernel modules, we also need to
understand how the kernel itself is signed to be loaded the GRUB. If
not already installed, we will need the sbsigntool
package for this
to work:
dzu@krikkit:~$ sudo apt install sbsigntool
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
sbsigntool
0 upgraded, 1 newly installed, 0 to remove and 43 not upgraded.
Need to get 68.4 kB of archives.
After this operation, 429 kB of additional disk space will be used.
Get:1 http://deb.debian.org/debian bookworm/main amd64 sbsigntool amd64 0.9.4-3.1 [68.4 kB]
Fetched 68.4 kB in 0s (806 kB/s)
Selecting previously unselected package sbsigntool.
(Reading database ... 603575 files and directories currently installed.)
Preparing to unpack .../sbsigntool_0.9.4-3.1_amd64.deb ...
Unpacking sbsigntool (0.9.4-3.1) ...
Setting up sbsigntool (0.9.4-3.1) ...
Processing triggers for man-db (2.10.2-3) ...
dzu@krikkit:~$
The sbverify
tool gives us an easy tool to show the signatures on
EFI applications. As the shim, GRUB and the kernel are of this
format, we can use it to show the signatures:
dzu@krikkit:~$ sudo sbverify --list /boot/efi/EFI/debian/shimx64.efi
warning: data remaining[809336 vs 934240]: gaps between PE/COFF sections?
signature 1
image signature issuers:
- /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
image signature certificates:
- subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher
issuer: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
- subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
issuer: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation Third Party Marketplace Root
dzu@krikkit:~$
dzu@krikkit:~$ sudo sbverify --list /boot/efi/EFI/debian/grubx64.efi
signature 1
image signature issuers:
- /CN=Debian Secure Boot CA
image signature certificates:
- subject: /CN=Debian Secure Boot Signer 2022 - grub2
issuer: /CN=Debian Secure Boot CA
dzu@krikkit:~$
dzu@krikkit:~$ sbverify --list /boot/vmlinuz-6.1.0-1-amd64
signature 1
image signature issuers:
- /CN=Debian Secure Boot CA
image signature certificates:
- subject: /CN=Debian Secure Boot Signer 2022 - linux
issuer: /CN=Debian Secure Boot CA
dzu@krikkit:~$
Ubuntu
In comparison to the examples above, let's look at a Ubuntu installation on an HP laptop:
dzu@zarniwoop:~$ sudo mokutil --pk | grep '\(\[key\|CN=\)'
[sudo] Passwort für dzu:
[key 1]
Issuer: C=US, O=HP Inc., CN=HP Inc. PK 2016 CA
Subject: CN=HP UEFI Secure Boot PK 2017, OU=CODE-SIGN, C=US, O=HP Inc.
dzu@zarniwoop:~$ sudo mokutil --kek | grep '\(\[key\|CN=\)'
[key 1]
Issuer: C=US, O=HP Inc., CN=HP Inc. KEK 2016 CA
Subject: CN=HP UEFI Secure Boot KEK 2017, OU=CODE-SIGN, C=US, O=HP Inc.
[key 2]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation KEK CA 2011
dzu@zarniwoop:~$ sudo mokutil --db | grep '\(\[key\|CN=\)'
[key 1]
Issuer: C=US, O=HP Inc., CN=HP Inc. DB Key 2016 CA
Subject: CN=HP UEFI Secure Boot DB 2017, OU=CODE-SIGN, C=US, O=HP Inc.
[key 2]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011
[key 3]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation UEFI CA 2011
dzu@zarniwoop:~$ sudo mokutil --dbx | grep '\(\[key\|CN=\|\[SHA\)'
[key 1]
[SHA-256]
[key 2]
[SHA-256]
[key 3]
[SHA-256]
[key 4]
[SHA-256]
dzu@zarniwoop:~$
It should be obvious now, which keys are the same, and which ones are different.
New Debian Behavior
In the meantime, Debian changed its secure boot implementation to
automatically generate a passphrase-less MOK key at
/var/lib/dkms/mok.key
, enroll that and from then use it
automatically in kernel upgrades. As I pointed out previously, this
opens the possibility for malware executing with root privileges to
install malware in the system, so I did not enroll this key at all.
For your information here is a transcript from such an upgrade
process:
linux-image-6.0.0-2-amd64 (6.0.3-1) wird eingerichtet ...
I: /vmlinuz.old is now a symlink to boot/vmlinuz-5.19.0-2-amd64
I: /initrd.img.old is now a symlink to boot/initrd.img-5.19.0-2-amd64
I: /vmlinuz is now a symlink to boot/vmlinuz-6.0.0-2-amd64
I: /initrd.img is now a symlink to boot/initrd.img-6.0.0-2-amd64
/etc/kernel/postinst.d/dkms:
dkms: running auto installation service for kernel 6.0.0-2-amd64:Sign command: /usr/lib/linux-kbuild-6.0/scripts/sign
-file
Signing key: /var/lib/dkms/mok.key
Public certificate (MOK): /var/lib/dkms/mok.pub
Certificate or key are missing, generating self signed certificate for MOK...
Building module:
Cleaning build area...
make -j8 KERNELRELEASE=6.0.0-2-amd64 KERNEL_DIR=/lib/modules/6.0.0-2-amd64/build all...
Signing module /var/lib/dkms/v4l2loopback/0.12.7/build/v4l2loopback.ko
Cleaning build area...
v4l2loopback.ko:
Running module version sanity check.
- Original module
- No original module exists within this kernel
- Installation
- Installing to /lib/modules/6.0.0-2-amd64/updates/dkms/
depmod...
.
/etc/kernel/postinst.d/initramfs-tools:
The script from above handles this situation also just fine as can be seen from this transcript:
dzu@krikkit:~$ sign-kernel-modules
Seems to be a Debian system, i.e. using KMODSIGN=/usr/src/linux-headers-6.0.0-6-amd64/scripts/sign-file
Processing 5.10.0-2-amd64
Processing 5.19.0-1-amd64
5.19.0-1-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 5.19.0-2-amd64
5.19.0-2-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.0.0-2-amd64
6.0.0-2-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.0.0-3-amd64
6.0.0-3-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.0.0-4-amd64
6.0.0-4-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.0.0-5-amd64
6.0.0-5-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.0.0-6-amd64
6.0.0-6-amd64/updates/dkms/v4l2loopback.ko already signed with correct certificate
Processing 6.1.0-1-amd64
Module signed by DKMS module signing key - will be overwritten
Enter MOK passphrase:
[sudo] Passwort für dzu:
Successfully signed module 6.1.0-1-amd64/updates/dkms/v4l2loopback.ko
dzu@krikkit:~$
Revoking Previously Trusted Binaries
In section DBX we saw that binaries can be revoked by adding their
signatures to the DBX list. In GNU/Linux, this is maintained by the
Linux Vendor Firmware Service (fwupd) project. I have to admit that
the command line options and outputs of the fwupdmgr
and fwupdtool
tools seem very erratic to me, so I settled on this invocation to show
the feed for the DBX table:
dzu@krikkit:~$ fwupdmgr get-devices --json | jq '.Devices | map(select(.Name == "UEFI dbx"))' [ { "Name": "UEFI dbx", "DeviceId": "362301da643102b9f38477387e2193e57abaa590", "ParentDeviceId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "CompositeId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "InstanceIds": [ "UEFI\\CRT_D7F66BE77CEF858C174BF4338A99263C8795B74E02026411F5F532F716AE3263", "UEFI\\CRT_D7F66BE77CEF858C174BF4338A99263C8795B74E02026411F5F532F716AE3263&ARCH_X64", "UEFI\\CRT_A1117F516A32CEFCBA3F2D1ACE10A87972FD6BBE8FE0D0B996E09E65D802A503", "UEFI\\CRT_A1117F516A32CEFCBA3F2D1ACE10A87972FD6BBE8FE0D0B996E09E65D802A503&ARCH_X64", "UEFI\\CRT_ED1FE72CB9CA31C9AF5B757AFCD733323D675825032E6CED7FE1AE9EB767998C", "UEFI\\CRT_ED1FE72CB9CA31C9AF5B757AFCD733323D675825032E6CED7FE1AE9EB767998C&ARCH_X64" ], "Guid": [ "fda6234b-adcb-5105-8515-9af647d29775", "f8ff0d50-c757-5dc3-951a-39d86e16f419", "c6682ade-b5ec-57c4-b687-676351208742", "f8ba2887-9411-5c36-9cee-88995bb39731", "7d5759e5-9aa0-5f0c-abd6-7439bb11b9f6", "0c7691e1-b6f2-5d71-bc9c-aabee364c916" ], "Summary": "UEFI revocation database", "Plugin": "uefi_dbx", "Protocol": "org.uefi.dbx", "Flags": [ "internal", "updatable", "registered", "needs-reboot", "usable-during-update", "only-version-upgrade", "signed-payload" ], "VendorId": "UEFI:Linux Foundation", "Version": "77", "VersionLowest": "77", "VersionFormat": "number", "Icons": [ "computer" ], "InstallDuration": 1, "Created": 1700174583 } ] dzu@krikkit:~$
As we can see, the Linux Foundation hosts services to push firmware
updates for non-Windows systems to GNU/Linux systems. In Debian you
will get this functionality out of the box with the help of the
fwupd
Systemd service:
dzu@krikkit:~$ systemctl status fwupd.service ● fwupd.service - Firmware update daemon Loaded: loaded (/lib/systemd/system/fwupd.service; static) Active: active (running) since Thu 2023-11-16 23:43:06 CET; 48min ago Docs: https://fwupd.org/ Main PID: 1046574 (fwupd) Tasks: 5 (limit: 55169) Memory: 36.1M CPU: 3.066s CGroup: /system.slice/fwupd.service └─1046574 /usr/libexec/fwupd/fwupd Nov 16 23:43:02 krikkit systemd[1]: Starting fwupd.service - Firmware update daemon... Nov 16 23:43:06 krikkit systemd[1]: Started fwupd.service - Firmware update daemon. dzu@krikkit:~$
So once this is running, the daemon will check periodically for updates and install them if possible. In this way the system will automatically receive revocations through the Linux Foundation. Of course this firmware upgrade ecosystem is desirable for many more things than only the UEFI certificates, so I will revisit this topic in another blog post, hopefully soon.
Beware of BIOS upgrades
After upgrading the BIOS on my machine, Linux was working just as well
as ever, so it took me a while to find out there was something wrong.
But when I tried some nerdy stuff with gstreamer
and v4l2-loopback
I noticed the v4l2-loopback
device was missing, so probably the
module did not load correctly. Checking the kernel logs quickly
showed the problem:
dzu@krikkit:~$ journalctl -b-6 -t kernel -g v4l2loopback Okt 14 11:02:34 krikkit kernel: v4l2loopback: loading out-of-tree module taints kernel. Okt 14 11:02:34 krikkit kernel: v4l2loopback: module verification failed: signature and/or required key missing - tainting kernel Okt 14 11:02:34 krikkit kernel: v4l2loopback driver version 0.12.7 loaded dzu@krikkit:~$
It seemed my MOK has been wiped by the BIOS upgrade or the subsequent Load Optimized Defaults that I executed from the setup. With the commands from section Checking New MOK Key, I was able to see that this was really the case. So I repeated the enrollment procedure once more and then v4l2-loopback was loaded again by the kernel.
Summary
It is my hope that this article is able to convey basic information on secure boot for GNU/Linux systems. You should be able to understand how things are supposed to work and hopefully fix problems if this is not the case. I can tell you that it took me a very long time to write this article and "fix" my own shortcomings in understanding the topic 🤓
Links
Update 2023-11-16
Added information on handling DBX in Linux like a firmware and about consequences of a BIOS upgrade / reset.
Update 2024-02-15
The script to sign kernel modules is now hosted on Codeberg and was
extended to also handle Debian Trixie. Beginning with Linux 6.6 the
kernel modules are now compressed with xz by default so the script
needs also to look for .ko.xz
files.
Update 2024-02-22
Fix the handling of .ko.xz
files. Although modinfo
understand the
compressed module format, the signing file silently corrupts the file
on disk. Be sure to uncompress, sign and compress the file in this
new scenario.
Update 2024-05-02
Fix script for changed output of openssl. See changelog in git repo for details.
Comments
Comments powered by Disqus