/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() {
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
#+end_src
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
[[https://codeberg.org/dzu/sign-kernel-modules][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:
#+begin_src console
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:~$
#+end_src
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:
#+begin_src console
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:~$
#+end_src
#+begin_src console
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:~$
#+end_src
#+begin_src console
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:~$
#+end_src
* Ubuntu
In comparison to the examples above, let's look at a Ubuntu
installation on an HP laptop:
#+begin_src console
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:~$
#+end_src
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:
#+begin_src console
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:
#+end_src
The script from above handles this situation also just fine as can be
seen from this transcript:
#+begin_src
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:~$
#+end_src
* Revoking Previously Trusted Binaries
In section [[*DBX][DBX]] we saw that binaries can be revoked by adding their
signatures to the DBX list. In GNU/Linux, this is maintained by the
[[https://fwupd.org/][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:
#+begin_export html
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:~$
#+end_export
As we can see, the [[https://www.linuxfoundation.org/][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:
#+begin_export html
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:~$
#+end_export
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:
#+begin_export html
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:~$
#+end_export
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][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
- https://wiki.debian.org/SecureBoot#Enrolling_your_key
- https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/
- https://blog.hansenpartnership.com/the-meaning-of-all-the-uefi-keys/
- https://ericchiang.github.io/post/tpm-keys/
* 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 [[https://codeberg.org/dzu/sign-kernel-modules][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.