Overview: How UEFI Secure Boot Works in Linux

Recently I’ve had to deal with issues booting using UEFI as outlined in my Linux Boot Failure! Debugging UEFI Boot Issues blog post, where I also go into detail about UEFI and the UEFI boot process. What I didn’t cover in that post was the Secure Boot component of UEFI booting, how it works, what it does, and how to ensure it is being used.

This blog entry will cover Secure Boot in some detail, particularly as it is implemented in Debian 12/Linux Mint LMDE 6 although a lot of the core details will be distribution agnostic.

What is Secure Boot?

Secure Boot is a mechanism of a computer’s firmware which ensures that only trusted and verifiable code is permitted to be launched by the firmware itself. In order to start an Operating System, a means of bootstrapping the system is needed typically provided by a bootloader such as the Windows Boot Manager or GRUB. With Secure Boot enabled, the firmware will check to ensure that it trusts the Extensible Firmware Interface (EFI) program, such as GRUB, before it launches it. If the EFI program it’s attempting to start is not trusted it will refuse to run it.

“Trust” in this scenario is established using Public Key Cryptography with a Public Key Infrastructure (PKI) to verify the authenticity and integrity of the EFI binary being launched. This trust can be established in one of two ways:

  • The EFI binary is signed by a private key, whose public key signature is present in the trusted Signature Database (db). The signature, or the SHA256 hash of the image, should also not be present in the Forbidden Signature Database (dbx).
  • The EFI binary is unsigned, but the SHA256 hash of the binary appears in the Signature Database (db) and not the Forbidden Signature Database (dbx).

To understand why using Secure Boot is advantageous, its useful to look back at what came before modern UEFI firmware, which was BIOS firmware. With the older BIOS firmware, identification and execution of a bootloader involved looking for bootable candidates within a disk’s Master Boot Record (MBR) and executing the first one it came across. No attempt was made to verify the integrity of the code or the identity of its authors. This led to the prevalence of boot sector viruses, such as Petya, whose goal was to hijack the boot process in order to load malware prior to the Operating System’s kernel gaining control of the computer. This made such malware particularly difficult to detect and deal with, as malicious code that has a foothold within the pre-boot process before the kernel starts can hide itself from the OS and any anti-malware efforts in ways that ordinarily wouldn’t be possible.

When UEFI Secure Boot is enabled malware infection in the way described above is almost impossible. Any attempt to execute a malicious bootloader would be refused as it would not be signed with a trusted cryptographic signature. Any attempt to tamper with a trusted bootloader would cause its checksum to no longer match with its signature and it would also not execute.

In addition to this, code executed higher up the chain by a EFI bootloader also has to be trusted in order to complete the booting process when Secure Boot is enabled. The Linux kernel itself, for example, has to have a valid signature as do all kernel modules the kernel attempts to load. In theory, this creates a foundation for a secure computing environment as no untrusted modules/drivers are able to execute on a SB-enabled system.

The Secure Boot Trust Chain

In order for a hierarchy of trust to be established to make Secure Boot possible, which involves verification of each step of the entire Secure Boot chain, several cryptographic keys need to exist. There are four main types of key involved in Secure Boot:

  • Platform Key (PK)
    • The PK sits at the top of the Secure Boot chain. The key is typically provided by your motherboard’s manufacturer. It is the trust anchor that underpins the entire chain of trust in the Secure Boot process. There can only be a single PK.
  • Key Exchange Key (KEK)
    • The KEK is used to sign updates to either the trusted database (db) or the forbidden database (dbx). There can be more than one KEK enrolled in a system. A typical setup may include two by default: one from your motherboard manufacturer, and the other from Microsoft. This limits updates of the db and dbx to those parties, preventing untrusted parties from manipulating the signatures that are permitted or denied. Only the party who controls the Platform Key (PK) can update Key Exchange Keys (KEKs).
  • Database (db) Key
    • The Signature Database (db) contains entries that allow EFI binaries to be trusted and executed. Entries in the db can either be a key used to sign trusted binaries, or a SHA256 hash of a permitted binary. Any update to the db must be signed with a trusted KEK. Two keys are commonly present in the db by default on most motherboards: the “Microsoft Corporation UEFI CA 2011” key used by Microsoft for signing third-party binaries, and the “Microsoft Corporation Windows Production PCA 2011” key used for signing its own Windows EFI images.
  • Forbidden Signature Database (dbx) Key
    • The Forbidden Signature Database (dbx) acts essentially as a blacklist. It can contain keys that if used to sign a binary, that binary won’t be permitted to run. It can also contain hashes, similar to the db, of binaries that are explicitly not permitted to execute. Keys and hashes in the dbx generally appear there if they’re well-known malware, or are revoked keys that may have previously been trusted.

There’s actually a fifth type of key too, which will become relevant to us later on. It should be noted, however, that this is a non-standard key type that isn’t strictly part of the Secure Boot specification itself:

  • Machine Owner Key (MOK)
    • A MOK is functionally the same as a db key, except they live in a separate MOK database. It is used by some third party bootloaders, such as Shim, as a means for users to create their own entries which permit EFI binaries to execute. Since manipulation of the Signature Database (db) directly isn’t possible without signing the update using the KEK private key, generally controlled by Microsoft, MOKs and the MOK database are a work-around to this problem that don’t involve taking full control of the Secure Boot process by replacing the PK and KEKs with your own. Entries in the MOK database, much like the Signature Database (db), can either be a key used for signing EFI binaries or a SHA256 hash of a binary permitted to run and can be manipulated by someone with physical access to the machine. MOKs allow for users to run things like custom compiled Linux kernels that would otherwise not be signed by their distribution’s key by signing it with their own key and enrolling the key into the MOK database.

Exploring Secure Boot in Debian/LMDE 6 with GRUB

In order to understand UEFI Secure Boot in more detail, it’s helpful to look at how it works out-of-the-box in a freshly installed Linux Mint LMDE 6 demonstration machine. For all intents and purposes, this is Debian 12 as all packages that we’ll be looking at come straight from the Debian Bookworm repositories.

First thing’s first, let’s ensure the demo system has been booted in Secure Boot mode by using the mokutil tool:

tiffany@lmde6:~$ mokutil --sb-state
SecureBoot enabled

Looking good! Now we know SB is enabled, let’s explore the minutia of how it’s implemented on this system to see how it’s able to “just work”™ straight out-of-the-box after installation.

EFI System Partition (ESP)

As with any system using UEFI firmware, whether Secure Boot is enabled or not, a dedicated EFI System Partition (ESP) is needed to store EFI binaries that the firmware uses for booting on a disk using the GUID Partition Table (GPT) standard.

Our example system has been installed using the “automatic” option for partitioning. Let’s take a look at the partitions the installer created:

root@lmde:~# lsblk 
sda      8:0    0   20G  0 disk 
├─sda1   8:1    0  286M  0 part /boot/efi
├─sda2   8:2    0    4G  0 part [SWAP]
└─sda3   8:3    0 15.7G  0 part /

We can see here that /dev/sda1 is our EFI System Partition with a mount point of /boot/efi. Let’s take a look at what it contains:

root@lmde6:~# tree /boot/efi
└── EFI
    └── debian
        ├── BOOTX64.CSV
        ├── fbx64.efi
        ├── grub.cfg
        ├── grubx64.efi
        ├── mmx64.efi
        └── shimx64.efi

3 directories, 6 files

The *.efi files here are EFI binaries that can be loaded by the UEFI firmware. Some of these are immediately recognizable by their name:

There are also a couple of utility EFI binaries present in the ESP:

  • mmx64.efi
    • This is the Machine Owner Key (MOK) Manager utility (MOKManager). In conjunction with the Shim bootloader, it enables a user to add custom keys that will be recognized as trusted for signing other binaries. Direct manipulation of the MOK database can only be done through booting this EFI image and no where else for security purposes, although additions or removals can be initiated through the mokutil user space application.
  • fbx64.efi
    • This is a simple fallback bootloader that is executed if there are no boot entries found in the UEFI variable that contains the boot order. This can happen if they’re manually wiped, or become cleared somehow. It will read the comma-separated variables file (BOOTX64.CSV) that should contain a sensible default boot entry, add a boot entry for what’s specified, then add it to the boot order list. This binary only gets called as a last resort to try to recover from an error state. It’s part of the Shim package.

UEFI Boot Variables

Multiple bootloaders from multiple Operating Systems can reside in the ESP. In order to determine which EFI images are available for booting and which order the firmware will attempt to boot them in, several UEFI variables exist within the NVRAM that are read by the firmware upon start.

In order for an EFI binary to be recognized as a valid execution candidate by the firmware it must have two things:

  1. It’s own UEFI variable within the NVRAM with the name of “BootXXXX”. The “XXXX” part will actually be a hexidecimal value. The value of this variable will contain a reference to the location of the .efi binary within the ESP that the firmware should run.
  2. Its own entry within the BootOrder variable. The BootOrder variable is a list containing references to each of the “BootXXXX” variables the system should consider as boot candidates. The firmware will go through this list in order, starting the first candidate it finds that it can successfully execute.

We can view UEFI variables related to booting by using the efibootmgr userspace application. Here’s the default boot variable setup on the example Linux Mint LMDE 6 installation:

root@lmde6:~# efibootmgr -v
BootCurrent: 0003
BootOrder: 0003,0000,0001,0002
Boot0000* EFI VMware Virtual SCSI Hard Drive (0.0)	PciRoot(0x0)/Pci(0x10,0x0)/SCSI(0,0)
Boot0001* EFI VMware Virtual SATA CDROM Drive (1.0)	PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x4,0x0)/Sata(1,0,0)
Boot0002* EFI Network	PciRoot(0x0)/Pci(0x11,0x0)/Pci(0x1,0x0)/MAC(000c298b91fc,0)
Boot0003* debian	HD(1,GPT,75db14f1-1b24-42d1-964b-21796972b5a3,0x1000,0x8f000)/File(\EFI\debian\shimx64.efi)

We can see here that the Mint installation has created a variable called “Boot0003” with the tag of “debian”. This is also referenced first in the BootOrder list.

Something to note is that the value of this variable containing the location of the EFI image to boot actually points to the Shim binary (shimx64.efi) and not the binary for GRUB (grubx64.efi).

What is Shim and why is it necessary?

Shim is designed to be a trivial, first-stage EFI bootloader that attempts to hand off execution to a second-stage bootloader such as GRUB. But why it is necessary? Why not just start GRUB directly?

We know that EFI binaries need to be signed by a signature within the trusted signatures database (db) in order for the firmware to execute them when Secure Boot is enabled.

As a result of Microsoft’s market share and them requiring UEFI Secure Boot be enabled on any “Microsoft certified” pre-built computer shipping Windows 8 and above, the vast majority of hardware will come with Microsoft’s signing keys set as trusted out of the box within the Signature Database (db). While already widely proliferated, they’ve also made Secure Boot capability a requirement for Windows 11 effectively meaning all PC hardware capable of running Windows into the future must have firmware that ships with Microsoft’s keys in the Signature Database.

While this is fine for running Windows, since Microsoft can just sign their own Windows Boot Manager, this poses a problem with other operating systems such as Linux since any bootloader used by a Linux distribution would need to be signed with a Microsoft key in order to be seen as “trusted” by UEFI firmware out of the box.

While Microsoft have agreed to sign third-party binaries in conjunction with Verisign, and have a process in place for requesting such, some of their signing requirements further complicate matters when it comes to Linux bootloaders, namely:

Code submitted for UEFI signing must not be subject to GPLv3 or any license that purports to give someone the right to demand authorization keys to be able to install modified forms of the code on a device. Code that is subject to such a license that has already been signed might have that signature revoked. For example, GRUB 2 is licensed under GPLv3 and will not be signed.

Microsoft UEFI Signing Requirements

In case it wasn’t obvious by the licensing requirement, they explicitly name GRUB as not being a valid candidate for being signed with Microsoft’s third party UEFI key. This means that out of the box, UEFI firmware will not be able to launch GRUB directly when Secure Boot is enabled as it will not be signed by a key in the trusted signature database.

This is the part where Shim comes in. Since it’s developed under a BSD license it isn’t disqualified from being signed with a Microsoft key. Furthermore, due to its trivial nature, it is designed with the aim of not having to be frequently updated thus minimizing the frequency that new Shim binaries will need to be signed.

We can use the sbverify tool, part of the sbsigntool package, to check what signature the shimx64.efi binary in our test system has been signed with:

tiffany@lmde6:~$ sbverify -l /boot/efi/EFI/debian/shimx64.efi 
warning: data remaining[823184 vs 948768]: 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

We can clearly see here the presence of the Microsoft signature on the Shim binary distributed with LMDE 6/Debian. The public key for this signature is the same key that resides in pretty much all trusted signature databases on motherboards across the world, permitting Shim to be booted by practically all Secure Boot enabled UEFI firmware.

This signed binary is installed by default on our demo system and is provided by the shim-signed Debian package in the Debian repos.

Shim’s verification system and distro signing keys

As mentioned, Shim’s goal is to launch GRUB. When Secure Boot is enabled, however, Shim will also take on the role of the root of trust for any subsequent EFI binary it hands off execution to in order to preserve the integrity of the Secure Boot process.

We know that GRUB cannot be signed by Microsoft, thus cannot be launched directly by most firmware. GRUB can, however, be signed by a distribution-specific key in order to verify its integrity and authenticity:

tiffany@lmde6:~$ sbverify -l /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

We can see here that the grubx64.efi binary is signed by the “Debian Secure Boot CA” key controlled by the Debian project team in our demonstration system.

Shim implements its own databases for trusted and denied keys that it will check binary signatures against, independent of the standard signature database (db) and forbidden signature database (dbx). This is where Machine Owner Keys (MOK) come in which were described earlier.

When building Shim from source, distributions are able to provide a DER-encoded public certificate via the VENDOR_CERT_FILE argument to the make tool. This will be enrolled within the Machine Owner Key (MOK) database. Enrolled MOKs can be used to verify binaries signed by the corresponding vendor’s private key, such as GRUB and the kernel distributed from the Debian repositories. This allows for the preservation of the integrity of the Secure Boot procedure while also removing the need for Microsoft to sign anything other than the initial Shim bootloader.

We can take a look at the enrolled MOKs using the mokutil application on our example Mint system:

tiffany@lmde6:~$ mokutil --list-enrolled
[key 1]
SHA1 Fingerprint: 53:61:0c:f8:1f:bd:7e:0c:eb:67:91:3c:9e:f3:e7:94:a9:63:3e:cb
        Version: 3 (0x2)
        Serial Number:
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=Debian Secure Boot CA
            Not Before: Aug 16 18:09:18 2016 GMT
            Not After : Aug  9 18:09:18 2046 GMT
        Subject: CN=Debian Secure Boot CA

The output is truncated for brevity, but we can see that the sole entry in the MOK database is the “Debian Secure Boot CA” certificate, as this is the certificate the Shim binary was built with when it was compiled by the Debian team.

Shim also provides a protocol that can be used by the second-stage bootloader, GRUB, to perform similar verification for the binaries that it loads, such as the Linux kernel and the kernel’s modules. The Secure Boot process not only provides a means to verify the trust of the bootloader binaries, but also the kernel and all of the kernel’s modules/drivers.

To see this in action, we can take a look at the signature on the Linux kernel image that Debian/LMDE ships with which shows it’s signed by the Debian Secure Boot CA key:

root@lmde6:~# sbverify --list /boot/vmlinuz-6.1.0-13-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

We can also take a look at the signature information for a common Linux kernel module like usb-common. We can see that this is signed by the Debian key too:

root@lmde6:~# modinfo usb-common
filename:       /lib/modules/6.1.0-13-amd64/kernel/drivers/usb/common/usb-common.ko
license:        GPL
retpoline:      Y
intree:         Y
name:           usb_common
vermagic:       6.1.0-13-amd64 SMP preempt mod_unload modversions 
sig_id:         PKCS#7
signer:         Debian Secure Boot CA
sig_key:        32:A0:28:7F:84:1A:03:6F:A3:93:C1:E0:65:C4:3A:E6:B2:42:26:43
sig_hashalgo:   sha256
signature:      3D:4F:7A:EE:D1:04:42:DB:34:6C:CB:59:44 [...] (truncated)

Kernels or kernel modules that aren’t signed by a trusted key will simply not be permitted to load. This can provide a challenge for those wishing to use a custom-compiled kernel or third-party drivers that aren’t distributed by their distro’s repositories, such as proprietary graphics drivers.

To allow their use, the user will need to generate their own public and private keypair to sign custom kernels or third-party kernel modules then enrol their custom public key into the Machine Owner Key (MOK) database so they can be recognized as trusted.

A more specialist solution could also involve taking full control of the entire Secure Boot process entirely by replacing the default Platform Key and Key Exchange Keys with your own, thus allowing the direct manipulation of the signatures database (db) and blacklist (dbx). This will give you explicit control of the whole process without third party involvement but isn’t for the fainthearted. Rod Smith has written an excellent article on this which is worth checking out.

Summarizing out-of-the-box Secure Boot with Shim & GRUB

Hopefully, it has become more clear how all of the components of UEFI Secure Boot come together after looking at how the example Debian/LMDE system works out-of-the-box with Secure Boot enabled.

Since GRUB can’t be signed by a Microsoft key trusted by all UEFI firmware, Shim is used as it is a trivial EFI application under a BSD license that can be signed by a Microsoft key. The Microsoft-signed Shim binary can then start GRUB which can go on to boot the kernel.

In order to verify GRUB and the kernel to preserve the goal of Secure Boot, these are signed by a distro-specific key that can be included with Shim so that Shim can verify these signed binaries against its own implementation of a trusted database. This works around the issue of Microsoft being unwilling to sign GRUB or any GPLv3-licensed code, and the problem of being unable to manually change the firmware’s trusted signatures database (db) without taking control of the Platform Key (PK) and Key Exchange Key (KEK).

Using Shim facilitates a very user-friendly, out-of-the-box solution to working around these Microsoft signing issues with minimal compromise to the goal of Secure Boot.

To be continued…

In a follow-up article to this, I’ll explore replacing the GRUB bootloader entirely with systemd-boot which, as the name implies, is a bootloader that is part of the wider systemd suite of software. In order to use systemd-boot with Secure Boot, I’ll be generating a custom keypair to sign the bootloader and enrolling the key using the MOK Manager. Stay tuned!


Your email address will not be published. Required fields are marked *