Secure Boot/GRUB
This page explains how to set up GRUB to work with Secure Boot.
Understanding The Boot Chain
Before we begin the installation, we need to understand the boot chain: what happens in between powering on a device and being prompted to login.
# (1)-> +------+ --(2)-> +------+ --(4)---> +---------------------+ /-----> +----------------+
# | UEFI | | GRUB | | | GRUB-related files/ | | | Kernel Modules |
# +------+ <-(3)-- +------+ <-(5)---- | Initramfs/ | | /---- +----------------+
# ^ | | | Microcode | | |
# | | | +---------------------+ | | /-> +-------------+
# | | | | | | | Init system |
# | | `-> +--------+ --(7)--------/ | | +-------------+
# | | | Kernel | | |
# | `------ +--------+ <-(8)----------/ |
# | | | |
# `-------(6)------------------/ `---(9)------------/
Step 1 | The device powers on, starting the UEFI. |
Step 2 | The UEFI chooses the GRUB executable as the secondary bootloader. |
Step 3 | The UEFI checks the signature of the GRUB executable before running it. The signature is made by the db private key and checked with the public key embedded in the UEFI. If the signature is good, then the UEFI runs the GRUB executable to continue the boot chain. |
Step 4 | The GRUB executable searches for external GRUB files; these files include the GRUB configuration file, GRUB modules, and other GRUB-related files required to boot; GRUB also searches for Microcodes, Initramfses, Kernels, and any other files required to boot. |
Step 5 | GRUB checks the signatures of all gathered files before they are loaded/ran. The signatures are made by the GPG private key and checked with the public key embedded in the GRUB executable. If all the signatures are good, then the files can be loaded/ran and the boot chain can continue. |
Step 6 | This is the step where the kernel would run and do its thing, but there is something that needs to happen first: the UEFI needs to check the signature of the kernel; this is because the kernel itself is an executable, and the UEFI will refuse to run anything not signed with the db key. This means that the kernel is a special file that needs to be signed by two keys: the GPG key, and the db key. If the UEFI determines the kernel's signature to be good, the boot chain can continue. |
Step 7 | The kernel searches for any external modules required to boot. |
Step 8 | The kernel checks the signatures of the modules before they are loaded. The signatures are made by the kernel's private key and checked with the public key embedded in the kernel. This key can be made automatically via the kernel itself or manually via openssl. If the modules' signatures are good, the boot chain can continue. |
Step 9 | The kernel starts the Init system. |
Installation
Kernel
For this guide, we will be configuring our kernel manually for those that wish to do so.
root #
emerge --ask sys-kernel/gentoo-sources
Installkernel
We will be using sys-kernel/installkernel to automatically generate the initramfs with Dracut and the GRUB configuration. To do this, ensure that USE flags dracut
and grub
are enabled when we install installkernel.
sys-kernel/installkernel dracut grub
root #
emerge --ask sys-kernel/installkernel
USE flags for sys-kernel/installkernel Gentoo fork of installkernel script from debianutils
dracut
|
Generate an initramfs or UKI on each kernel installation |
efistub
|
EXPERIMENTAL: Update UEFI configuration on each kernel installation |
grub
|
Re-generate grub.cfg on each kernel installation, used grub.cfg is overridable with GRUB_CFG env var |
refind
|
Install a Gentoo icon for rEFInd alongside the (unified) kernel image, used icon is overridable with REFIND_ICON env var |
systemd
|
Use systemd's kernel-install to install kernels, overridable with SYSTEMD_KERNEL_INSTALL env var |
systemd-boot
|
Use systemd-boot's native layout by default |
ugrd
|
Generate an initramfs using UGRD on each kernel installation |
uki
|
Install UKIs to ESP/EFI/Linux for EFI stub booting and/or bootloaders with support for auto-discovering UKIs |
ukify
|
Build an UKI with systemd's ukify on each kernel installation |
Setting the
dracut
and grub
USE flags should also install said tools as dependencies; if not, install them manually.
root #
emerge --ask sys-kernel/dracut sys-boot/grub
GRUB has the USE flag
secureboot
, but enabling this flag only generates a signed GRUB executable in /usr/lib/grub/grub-<arch>.efi.signed which is to be copied to where the GRUB executable is supposed to go. We will not be using this executable, but it's safe to enable the USE flag anyway.Sbsigntools
We will be using app-crypt/sbsigntools to sign executable files with the db private key.
root #
emerge --ask app-crypt/sbsigntools
Efitools
We will be using app-crypt/efitools to enroll the PK, KEK, db, and dbx keys into the UEFI.
root #
emerge --ask app-crypt/efitools
Key Creation
Before we install any tools, we can make the key pairs that will be used in the boot chain. Different keys are used depending on which step in the boot chain we're at. There are three types of keys: the ones for the UEFI, GRUB, and the Kernel.
UEFI
The UEFI uses four different types of keys for secure boot: PK, KEK, db, and dbx.
- PK - Platform Key - Composed of two parts, PKpub (the public key) and PKpriv (the private key), used to sign the KEK.
- KEK - Key Exchange Key - The key used to sign the Signatures and Forbidden Signatures database, there can be more than one.
- db - Signature Database - Contains lists of public keys, signatures, and hashes which are allowed as part of the boot chain.
- dbx - Forbidden Signature Database - The opposite of the signature database, public keys, signatures, and hashes which should never be allowed to boot.
There is only one PK key while there can be multiple KEK, db, and dbx keys.
Imagine the PK key as the root user of a machine -- an omnipotent user that decides who has the ability to allow/deny executable files.
Imagine a KEK key as an unprivileged user that can allow/deny executable files they decide.
An executable file signed by the db key will be allowed, while the dbx key will deny.
There can be multiple users (KEK) each having multiple keys to allow (db) and deny (dbx). All users must be approved (signed) by root (PK).
Making the Keys
We can put the keys in an appropriate path such as /etc/ssl/private. Make a directory under this path so that we can place our secure boot keys in it and set the correct permissions.
root #
mkdir -p /etc/ssl/private/secure_boot
root #
chmod 700 /etc/ssl/private/secure_boot
UEFI Secure Boot typically uses RSA-2048 and sha256, but some motherboards might support stronger algorithms.
The following command uses the
-noenc
option, meaning that the key will *NOT* be protected by a password; anyone with access to the key will be able to use it to sign files!
This is only safe if:
- The key is only readable by user root
- *AND* the key is stored in the encrypted root directory (/)! See Full Disk Encryption From Scratch.
We do this because:
- The commands that use the key will already need root privileges.
- Portage's FEATURES=pid-sandbox interfaces with dev-libs/openssl's passphrase prompt.
- No one wants to manage another password.
root #
for key_name in my_pk.pem my_kek.pem my_db.pem my_dbx.pem; do
openssl req -batch -new -noenc -sha256 -key <(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048) -x509 -out "/etc/ssl/private/secure_boot/$key_name" -keyout "/etc/ssl/private/secure_boot/$key_name"
done
Also set the correct permissions.
root #
for key_name in my_pk.pem my_kek.pem my_db.pem my_dbx.pem; do
chmod 400 "/etc/ssl/private/secure_boot/$key_name"
done
Making the ESLs
Next, we make the EFI signature lists.
root #
for key_name in my_pk.pem my_kek.pem my_db.pem my_dbx.pem; do
cert-to-efi-sig-list /etc/ssl/private/secure_boot/{"$key_name","${key_name%.*}".esl}
done
(Optional) Combining our Keys and the Factory Keys
We can make a backup of the factory keys (except the PK key) in the UEFI to later concatenate them with our keys. This is only required for tools like Shim to still work.
root #
for key_type in KEK db dbx; do
efi-readvar -v "$key_type" -o factory_"${key_type,,}".esl
done
If we want to include the factory esl files, we simply concatenate them with ours (except the PK key) and use the combined files from this point on.
root #
for key_type in kek db dbx; do
cat factory_"$key_type".esl my_"$key_type".esl >combined_"$key_type".esl
done
Signing the Keys
The PK and KEK esl files are signed by the PK key.
root #
sign-efi-sig-list -c /etc/ssl/private/secure_boot/my_pk.pem -k /etc/ssl/private/secure_boot/my_pk.pem PK /etc/ssl/private/secure_boot/my_pk.{esl,auth}
root #
sign-efi-sig-list -c /etc/ssl/private/secure_boot/my_pk.pem -k /etc/ssl/private/secure_boot/my_pk.pem KEK /etc/ssl/private/secure_boot/my_kek.{esl,auth}
The database esl files are signed by the KEK key.
root #
sign-efi-sig-list -c /etc/ssl/private/secure_boot/my_kek.pem -k /etc/ssl/private/secure_boot/my_kek.pem db /etc/ssl/private/secure_boot/my_db.{esl,auth}
root #
sign-efi-sig-list -c /etc/ssl/private/secure_boot/my_kek.pem -k /etc/ssl/private/secure_boot/my_kek.pem dbx /etc/ssl/private/secure_boot/my_dbx.{esl,auth}
Installing the Keys to the UEFI
Before running the command that installs the keys to the UEFI, the UEFI must be in "Setup Mode"; doing this varies across manufacturers, but the idea is the same. Reboot the machine and boot into the UEFI; there should be a page for "Security" and "Secure Boot" should be in it; there should be an option to clear the current PK, KEK, db, and dbx keys. Clear the keys, save the settings, and reboot.
If done correctly, we should get the following output after we reboot:
root #
efi-readvar
Variable PK has no entries Variable KEK has no entries Variable db has no entries Variable dbx has no entries Variable MokList has no entries
Finally, we install the keys to the UEFI.
root #
for key_type in PK KEK db dbx; do
efi-updatevar -f /etc/ssl/private/secure_boot/my_"${key_type,,}".auth "$key_type"
done
If the keys installed correctly, we should get the following output:
root #
efi-readvar
Variable PK, length 923 PK: List 0, type X509 Signature 0, size 895, owner 00000000-0000-0000-0000-000000000000 Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Variable KEK, length 923 KEK: List 0, type X509 Signature 0, size 895, owner 00000000-0000-0000-0000-000000000000 Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Variable db, length 923 db: List 0, type X509 Signature 0, size 895, owner 00000000-0000-0000-0000-000000000000 Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Variable dbx, length 923 dbx: List 0, type X509 Signature 0, size 895, owner 00000000-0000-0000-0000-000000000000 Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd Variable MokList has no entries
GRUB
GRUB will use it's own method of verifying files further in the boot chain via GPG.
GPG
GPG has a command line interface tool to make keys, but it also has a "batch" mode so we can put the command in a script. Make the following GPG parameter file:
The following file contains the
%no-protection
option, meaning that the key will *NOT* be protected by a password; anyone with access to the key will be able to use it to sign files!
This is only safe if:
- The key is only readable by user root
- *AND* the key is stored in the encrypted root directory (/)! See Full Disk Encryption From Scratch.
We do this because:
- The commands that use the key will already need root privileges.
- No one wants to manage another password.
# See warning!
%no-protection
# As of GRUB 2.12, only DSA and RSA keys are supported.
Key-Type: RSA
Key-Length: 4096
# GRUB only needs to verify signatures.
Key-Usage: sign
# Information to help identify this key.
Name-Real: grub
Name-Comment: This key is used to sign files for GRUB.
# (0) means to never expire.
Expire-Date: 0
%commit
Now make the key.
root #
gpg --batch --full-gen-key /root/my_grub_gpg_parameter
Export the public key so that it can be embedded into the GRUB executable.
root #
gpg -o /root/grub_gpg_public_key --export grub
Kernel
We can put the key in an appropriate path such as /etc/ssl/private. Make a directory under this path so that we can place our kernel key in it and set the correct permissions.
root #
mkdir -p /etc/ssl/private/kernel
root #
chmod 700 /etc/ssl/private/kernel
The kernel only supports a specific set of key types depending on the version.
-*- Cryptographic API ---> Search for <code>CONFIG_CRYPTO</code> to find this item.
Certificates for signature checking --->
Type of module signing key to be generated (<key type>) --->
Where "<key type>" is the type of key the kernel will generate if one is not provided.
As of Linux kernel version 6.12.1, the supported types are:
- RSA
- ECDSA
We will be creating an ECDSA key with the curve P-521 and hash SHA3-512 (the strongest curve and hash that the kernel supports). If these are not available, use the strongest possible for the kernel.
The following command uses the
-noenc
option, meaning that the key will *NOT* be protected by a password; anyone with access to the key will be able to use it to sign files!
This is only safe if:
- The key is only readable by user root
- *AND* the key is stored in the encrypted root directory (/)! See Full Disk Encryption From Scratch.
We do this because:
- The commands that use the key will already need root privileges.
- Portage's FEATURES=pid-sandbox interfaces with dev-libs/openssl's passphrase prompt.
- No one wants to manage another password.
root #
openssl req -batch -new -noenc -utf8 -sha3-512 -key <(openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521) -x509 -outform PEM -out /etc/ssl/private/kernel/my_kernel_key.pem -keyout /etc/ssl/private/kernel/my_kernel_key.pem
Also set the correct permissions.
root #
chmod 400 /etc/ssl/private/kernel/my_kernel_key.pem
Set Variables
Portage
Secure Boot
On packages with the secureboot
global USE flag, this flag can be enabled to automatically sign any EFI binaries installed by the package. When this flag is enabled the SECUREBOOT_SIGN_KEY and SECUREBOOT_SIGN_CERT environment variables must be used to specify the path (or pkcs11 URI) of the db key and certificate to use for signing in PEM format.
USE="... secureboot ..."
SECUREBOOT_SIGN_KEY="/etc/ssl/private/secure_boot/my_db.pem"
SECUREBOOT_SIGN_CERT="/etc/ssl/private/secure_boot/my_db.pem"
Update all packages with the secureboot
USE flag now enabled.
root #
emerge --ask -uND @world
Kernel
In addition to the kernel itself, the kernel modules must also be signed to boot successfully with secure boot enabled. For this purpose, the modules-sign
global USE flag can be used in addition to the MODULES_SIGN_KEY and MODULES_SIGN_CERT environment variables.
MODULES_SIGN_HASH must be set to the same algorithm as the kernel's!
USE="... modules-sign ..."
MODULES_SIGN_KEY="/etc/ssl/private/kernel/my_kernel_key.pem"
MODULES_SIGN_CERT="/etc/ssl/private/kernel/my_kernel_key.pem"
MODULES_SIGN_HASH="sha3-512"
Update all packages with the modules-sign
USE flag now enabled; this will also sign the modules.
root #
emerge --ask -uND @world
To only update packages with out-of-tree kernel modules:
root #
emerge --ask @module-rebuild
Configuration
Kernel
There are many kernel options to increase security; for this page, we will only be going over what is needed to get secure boot working. For additional kernel settings for secure boot, see Handbook:AMD64/Installation/Kernel.
Ensure that MODULE_SIG_KEY is set to where we stored our kernel key!
[*] Enable loadable module support ---> Search for <code>CONFIG_MODULES</code> to find this item. -*- Module signature verification Search for <code>CONFIG_MODULE_SIG</code> to find this item. [*] Require modules to be validly signed Search for <code>CONFIG_MODULE_SIG_FORCE</code> to find this item. [*] Automatically sign all modules Search for <code>CONFIG_MODULE_SIG_ALL</code> to find this item. Hash algorithm to sign modules (SHA3-512) ---> Search for <code>CONFIG_MODULE_SIG_SHA3_512</code> to find this item. -*- Cryptographic API ---> Search for <code>CONFIG_CRYPTO</code> to find this item. Certificates for signature checking ---> (/etc/ssl/private/kernel/my_kernel_key.pem) File name or PKCS#11 URI of module signing key Search for <code>CONFIG_MODULE_SIG_KEY</code> to find this item.
Usage
Kernel
After we've made all the changes to our kernel, we can compile it.
root #
cd /usr/src/linux[version]
root #
make -j$(nproc) && make -j$(nproc) modules_install && make install
Then sign the kernel with the db key for the UEFI to verify.
root #
sbsign --cert /etc/ssl/private/secure_boot/my_db.pem --key /etc/ssl/private/secure_boot/my_db.pem /boot/vmlinuz-<version>-gentoo --output /boot/vmlinuz-<version>-gentoo
GRUB
We need to make and sign the GRUB executable. For the installation, we will need three parameters in addition to the ones that we already use to install GRUB to our machine. For more information on how to install GRUB for a specific machine, see GRUB. The three parameters are as follows:
--disable-shim-lock
-- Disable the use of Shim as we are not using it.--pubkey
-- Embed the GPG public key to verify signatures of files signed by the GPG private key.--modules
-- Embed GRUB modules needed to verify signatures of files signed by the GPG private key.
All available GRUB modules are in /boot/grub/<architecture>
root #
grub-install --disable-shim-lock --pubkey=/root/grub_gpg_public_key --modules="pgp gcry_sha512 gcry_rsa" [other parameters for machine]
Now sign the GRUB executable with the db key for the UEFI to verify.
root #
sbsign --cert /etc/ssl/private/secure_boot/my_db.pem --key /etc/ssl/private/secure_boot/my_db.pem /path/to/grub.efi --output /path/to/grub.efi
Next, we need to sign all the files GRUB needs to verify; we can do this with a script.
Signing the files GRUB needs to verify must come after signing the kernel because the kernel embeds the db signature, which would change the GRUB detached signature!
sign_file_for_grub()
{
# Sign all files that GRUB will use in the boot chain; see 'info grub' section 19.2. The list of files to sign include:
# - GRUB-related files (configs, environment, modules).
# - kernel.
# - initramfs.
# - CPU microcode.
local valid_file=''
for valid_file in $(find /boot -regextype posix-extended -regex '.*(\.cfg|\.lst|\.mod|vmlinuz.*gentoo(-r[[:digit:]]+)?(\.old)?|init.*(-r[[:digit:]]+)?\.img(\.old)?|grubenv|-uc\.img)$'); do
# Delete the old signature.
[[ -f "$valid_file".sig ]] && rm "$valid_file".sig
# Make a new detached signature for a file that GRUB will verify on boot.
gpg --batch -bu grub --digest-algo SHA512 "$valid_file" || break
done
}
sign_file_for_grub "$@"
root #
chmod +x /root/sign_file_for_grub
root #
/root/sign_file_for_grub
Verify
At this point, secure boot is set up; the only thing left to do is reboot into the UEFI and ensure that secure boot is enabled.
root #
reboot
After we reboot and make it past GRUB and the login, we can check if secure boot is enabled by running the following command:
root #
dmesg | grep -i secure
[ 0.00123] [ T0] Secure boot enabled
Automation
For future kernel installations, the signing process can be fully automated when we install our kernel.
my_install_kernel()
{
local -r CURRENT_PATH="$(pwd -P)"
# Check to see if we are in the correct directory.
if [[ -z "$(echo "$CURRENT_PATH" | grep -E '^/usr/src/linux-')" ]]; then
echo "This needs to run in /usr/src/linux[...]!"
return
fi
# Get the Linux version (and the revision if available) so that we know which 'vmlinuz' to sign with 'sbsign'. Older kernels will already have the
# embedded signature.
local -r LINUX_VERSION="$(echo "$CURRENT_PATH" | sed -E 's/^\/usr\/src\/linux-([[:digit:].]+).*/\1/g')"
local -r LINUX_REVISION="$(echo "$CURRENT_PATH" | grep -E -- '-r[[:digit:]]+$' | sed -E 's/.*-r([[:digit:]]+)$/\1/g')"
# Compile and install the kernel via sys-kernel/installkernel as usual.
make -j$(nproc) && make -j$(nproc) modules_install && make install || return
# The newly compiled kernel needs to be signed with the UEFI 'db' key.
sbsign --cert "/etc/ssl/private/secure_boot/my_db.pem" --key "/etc/ssl/private/secure_boot/my_db.pem" "/boot/vmlinuz-${LINUX_VERSION}-gentoo${LINUX_REVISION:+-r"$LINUX_REVISION"}" --output "/boot/vmlinuz-${LINUX_VERSION}-gentoo${LINUX_REVISION:+-r"$LINUX_REVISION"}"
sign_file_for_grub
}
sign_file_for_grub()
{
# Sign all files that GRUB will use in the boot chain; see 'info grub' section 19.2. The list of files to sign include:
# - GRUB-related files (configs, environment, modules).
# - kernel.
# - initramfs.
# - CPU microcode.
local valid_file=''
for valid_file in $(find /boot -regextype posix-extended -regex '.*(\.cfg|\.lst|\.mod|vmlinuz.*gentoo(-r[[:digit:]]+)?(\.old)?|init.*(-r[[:digit:]]+)?\.img(\.old)?|grubenv|-uc\.img)$'); do
# Delete the old signature.
[[ -f "$valid_file".sig ]] && rm "$valid_file".sig
# Make a new detached signature for a file that GRUB will verify on boot.
gpg --batch -bu grub --digest-algo SHA512 "$valid_file" || break
done
}
my_install_kernel "$@"
root #
chmod +x /root/my_install_kernel
root #
/root/my_install_kernel
Troubleshooting
GRUB
Error: prohibited by secure boot policy.
Ensure that all needed external GRUB modules in /boot/grub/<architecture> are signed by the GPG key. This error happens when secure boot is enabled in the UEFI and GRUB loads successfully, but GRUB can't load any of its modules.
Error: shim_lock protocol not found.
Ensure that --disable-shim-lock
is used. GRUB uses Shim by default, so we need to tell GRUB to disable it because we are using our own keys for secure boot.
root #
grub-install --disable-shim-lock ...
Error: you need to load the kernel first.
Ensure the kernel is signed by the db UEFI key *THEN* signed by the GRUB GPG key; the db signature will be embedded into the kernel while the GPG signature will be detached.
Error: verification requested but nobody cares: (<drive>,<partition>)/grub/<architecture>/<grub module>.mod.
Ensure that the pgp GRUB module is embedded into the GRUB executable; GRUB needs this module embedded because this is the module that verifies other modules and files.
root #
grub-install --modules="pgp" ...
Error: loading initial key: bad signature
Ensure that the GPG key generated is supported by GRUB and that the public key is exported correctly.
root #
gpg -o /path/to/grub_gpg_public_key --export grub
Ensure the GPG public key is embedded into the GRUB executable with --pubkey
.
root #
grub-install --pubkey=/path/to/grub_gpg_public_key ...
Removal
If secure boot is no longer wanted, it can be disabled in the UEFI; the other signature checking performed by GRUB and the kernel can be kept.
See also
- Handbook:AMD64/Installation/Kernel
- Secure Boot — an enhancement of the security of the pre-boot process of a UEFI system.
- GRUB — a multiboot secondary bootloader capable of loading kernels from a variety of filesystems on most system architectures.
- Full Disk Encryption From Scratch — a guide which covers the process of configuring a drive to be encrypted using LUKS and btrfs.
External resources
- Gentoo forum on GRUB and Secure Boot -- The Gentoo forum post that resulted in this page being made.