Secure Boot/GRUB
This article explains how to set up GRUB to work with Secure Boot.
Understanding The Boot Chain
Before the installation, it's advised to understand what a boot chain is: 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)------------/
Installation
Kernel
This guide will cover a manually configured and compiled kernel using the sys-kernel/gentoo-sources package.
root #emerge --ask sys-kernel/gentoo-sourcesInstallkernel
The sys-kernel/installkernel package will be used in this guide to automatically generate the initramfs with Dracut and the GRUB configuration. To do this, the USE flags dracut and grub should be installed before emerging installkernel.
/etc/portage/package.use/installkernelSet USE flagssys-kernel/installkernel dracut grub
root #emerge --ask sys-kernel/installkernelUSE 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/grubGRUB 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. This executable won't be used in this guide, but it's safe to enable the USE flag anyway.Sbsigntools
The app-crypt/sbsigntools package will be used to sign executable files with the db private key.
root #emerge --ask app-crypt/sbsigntoolsEfitools
The app-crypt/efitools package will be used to enroll the PK, KEK, db, and dbx keys into the UEFI.
root #emerge --ask app-crypt/efitoolsKey Creation
Now, the necessary key pairs that will be used in the boot chain should be made. 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
The keys should be put in an appropriate path such as /etc/ssl/private. Make a directory under this path, where the secure boot keys should be inserted, while setting up 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"
doneAlso 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"
doneMaking 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
doneIf 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
doneSigning 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-readvarVariable 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"
doneIf the keys installed correctly, we should get the following output:
root #efi-readvarVariable 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 its 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.
/root/my_grub_gpg_parameter# 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_parameterExport the public key so that it can be embedded into the GRUB executable.
root #gpg -o /tmp/grub_gpg_public_key --export grubKernel
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.pemAlso set the correct permissions.
root #chmod 400 /etc/ssl/private/kernel/my_kernel_key.pemSet 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.
/etc/portage/make.confUSE="... 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 @worldKernel
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!
/etc/portage/make.confUSE="... 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 @worldTo only update packages with out-of-tree kernel modules:
root #emerge --ask @module-rebuildConfiguration
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 installThen 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>-gentooGRUB
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=/tmp/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!
- We can put this file in /etc/bash/bashrc.d because Gentoo uses this directory to source system-wide Bash files.
99is prefixed to the name of the file because files are sourced lexicographically; meaning that our file will be sourced last -- preventing other files from overriding ours.
.bashis suffixed to the name of the file because any file in /etc/bash/bashrc.d that is to be sourced must end with.bashas per the logic in /etc/bash/bashrc -- a file sourced by Bash for non-login shells.
/etc/bash/bashrc.d/99_my_install_kernel.bash# GRUB sign. This function signs files for GRUB needed for boot. # # NOTE: # * Note on 'File_to_sign': # 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. # # * Note on finding files to sign: # The name of the kernel and initramfs files found in /boot can vary depending on what package and USE flags are used to install the kernel; we need # to be less restrictive with our regular expression in the 'find' command because of this. Since we don't know how the kernel and initramfs names # end, we simply have to include all file names that contain the words "vmlinuz" or "init"; this has the unwanted effect of including files that end # with ".sig". # # To fix this, we use 'sed' to filter out the files that end with ".sig". If we didn't do this, we would eventually get files that end in # ".sig.sig.sig.sig..." (a never-ending loop of detached signatures of detached signatures). # # * Note on deleting old signatures: # This is not the same as deleting ".sig" files from the 'File_to_sign' array; this is deleting the detached signatures of files we know are not # signatures themselves. sign_file_for_grub() { # See note on 'File_to_sign'. local-aFile_to_sign=() echo"Signing all files required for boot with the GRUB GPG key..." # See note on finding files to sign. File_to_sign=($(find/boot-regextypeposix-extended-regex'.*(\.cfg|\.lst|\.mod|vmlinuz.*|init.*|grubenv|-uc\.img|\.efi)$'|sed-En'/\.sig$/! p')) # Delete the old signatures; see note on deleting old signatures. rm-f"${File_to_sign[@]/%/.sig}" # Now that all the old signatures are deleted, we can make new detached signatures for the files that GRUB will verify on boot. parallelgpg--batch-bu"grub"--digest-algoSHA512:::"${File_to_sign[@]}" return0 }
Source the file /etc/profile; this file is typically sourced on Bash login shells (like when we first login to a booted machine), but we can also do it manually. For those that are curious, we can open this file in a text editor and follow the logic to see how our file gets sourced.
root #source /etc/profile
root #sign_file_for_grubVerify
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 #rebootAfter 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. We can append the following to our already existing /etc/bash/bashrc.d/99_my_install_kernel.bash file:
/etc/bash/bashrc.d/99_my_install_kernel.bash# Kernel install. This function handles various tasks to ensure proper kernel installation. # # NOTE: # * Note on finding the newest kernel: # Get the path of the newest modified kernel; to do this, we use 'find'. The following 'find' syntax can be read as: # - Find all files in the /boot directory # - **AND** that have a modified time newer than our timestamp # - **AND** whose file name contains the word 'vmlinuz' # - **AND** whose file name does not end with '.old' # - **AND** whose file name does not end with '.sig'. # # There should only be one file with all these conditions met. my_install_kernel() { # The name of the kernel file found in /boot can vary depending on what package and USE flags are used to install the kernel. Since we cannot use the # name of the file to reliably determine the newly compiled kernel, we can use the modification date instead. localdate_before_install="" localnewest_kernel="" # Check to see if we are in the correct directory. if!pwd-P|grep-E'^/usr/src/linux'1>/dev/null then echo"This needs to run in /usr/src/linux[...]!" return1 fi # Compile the kernel and its modules. make-j$(nproc)||return1 # If external kernel modules (such as NVIDIA or ZFS) are installed on the system, they must be rebuilt against the newly compiled kernel to ensure # compatibility. emerge-an@module-rebuild||return1 # Install the kernel modules. make-j$(nproc)modules_install||return1 # Take a timestamp now to know the newest modified kernel in /boot later. date_before_install="$(date--iso-8601=seconds)" # Install the kernel via sys-kernel/installkernel as usual. makeinstall||return1 # See note on finding the newest kernel. newest_kernel="$(find/boot-newermt"$date_before_install"-name'*vmlinuz*'!-name'*.old'!-name'*.sig')" # The newly compiled kernel needs to be signed with the UEFI 'db' key. echo"Signing $newest_kernel with the UEFI db key..." sbsign--cert"/etc/ssl/private/secure_boot/my_db.pem"--key"/etc/ssl/private/secure_boot/my_db.pem""$newest_kernel"--output"$newest_kernel" # Sign/resign files with the GRUB GPG key in case any of them were modified. sign_file_for_grub return0 }
root #source /etc/profile
root #my_install_kernel
If we need to reinstall the GRUB executable, we can append the following:
/etc/bash/bashrc.d/99_my_install_kernel.bashmy_grub_install() { # We need to export the public key so we can embed it in the GRUB executable. gpg-o"/tmp/grub_gpg_public_key"--export"grub" grub-install--disable-shim-lock--pubkey=/tmp/grub_gpg_public_key--modules="pgp gcry_sha512 gcry_rsa"[otherparametersformachine] rm"/tmp/grub_gpg_public_key" # Sign the GRUB executable with the UEFI 'db' key so the UEFI can verify it for secure boot. echo"Signing the GRUB executable with the UEFI db key..." 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" # Sign/resign files with the GRUB GPG key in case the GRUB GPG key changed. sign_file_for_grub return0 }
root #source /etc/profile
root #my_grub_installTroubleshooting
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 grubEnsure 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.