How to use Secure Boot with your own keys
How to use Secure Boot with your own keys
What is secure boot?
All modern computers come with a UEFI instead of a traditional BIOS. One of the facilities which this firmware provides is secure boot, meaning that only authorised software can be booted. In the generation of PCs that came with Windows 8, secure boot could be disabled inside the UEFI setup program, but some Windows 10 PCs no longer allow this and it was never allowed on ARM devices.
How then can Linux be booted on such computers? There are basically three possibilities.
- Switch secure boot off. This is the simplest solution but not always possible (see above) or practical. For example, you may need to dual-boot with Windows, which is often a requirement for school work, and which requires secure boot to be active.
- Use intermediate software like Red Hat's shim program which is used by a number of distros. This is a binary signed by Microsoft with the key they use for foreign software, which means that it will boot with secure boot enabled. shim will then chainload GRUB and booting can proceed as normal. shim uses a stored user-defined key called a Machine Owner Key or MOK to sign both GRUB and the kernels that GRUB boots, preserving the chain of trust. However many distros do not include shim or the mokmanager program needed to work with MOKs, and they must be obtained from elsewhere.
- Edit the UEFI's own key database in order to add a personal key, and then use it to sign your bootloader, which does not necessarily need to be GRUB. This is the subject of the present article.
Modifying your computer's UEFI variables is potentially dangerous. It could leave your computer unbootable. The method described here has been used successfully by its author on multiple laptop and desktop computers and it worked without problems, but you use it at your own risk. To reduce the risks somewhat, the original firmware keys will be kept alongside the new ones, so that Windows and everything signed with Microsoft keys will still work. Some computers have device firmware signed with the Microsoft key which will not work if that key is removed from the database.
You will need to obtain and install two additional packages: efitools and sbsigntools.
Start by saving the current keys, using efi-readvar from efitools:
- efi-readvar -v PK -o old_PK.esl
- efi-readvar -v KEK -o old_KEK.esl
- efi-readvar -v db -o old_db.esl
- efi-readvar -v dbx -o old_dbx.esl
PK is the top-level Platform Key supplied by the manufacturer. It is used to sign the Key Exchange Key or KEK, which is then used to sign the keys stored in the key database (db) and the blacklist (dbx). This chain of trust makes it impossible for malware to insert a new key. Only someone physically present with access to the UEFI setup program can do so.
Note that these are public cryptographic keys (see HOWTO Create ssh keys). The corresponding private keys that are actually used for signing software are not stored anywhere on the machine, where they could potentially be read by bad actors.
Clearing the existing keys
To do this, you must reboot into the UEFI firmware. Your bootloader may give you an option to do this; if not, you will have to use your computer's "magic key" to break into the UEFI's setup mode.
Start by removing the default Secure Boot keys. How exactly this is done depends on the firmware. You may need to explore the menu system of your UEFI setup program. Removing the keys will automatically switch Secure Boot into its "Setup" mode. Here again there is often an option to save the default keys, The firmware may also have an option to restore default keys, but it never hurts to have more backups!
Now boot back into Linux.
You can check that secure variables are empty:
efi-readvar should output:
- Variable PK has no entries
- Variable KEK has no entries
- Variable db has no entries
- Variable dbx has no entries
Creating new keys
You must start by creating a GUID (a randomly generated code). There are a number of programs that can do this. A well-known one is uuidgen from dbus.
uuidgen --random > guid.txt
Create a new Playtform Key (PK) keypair, key-exchange (KEK) keypair and signing (DB) keypair:
- openssl req -new -x509 -newkey rsa:2048 -subj "/CN=My Platform Key/" -keyout pk.key -out pk.crt -days 3650 -nodes -sha256
- openssl req -new -x509 -newkey rsa:2048 -subj "/CN=My Key Exchange Key/" -keyout kek.key -out kek.crt -days 3650 -nodes -sha256
- openssl req -new -x509 -newkey rsa:2048 -subj "/CN=My Signature DB Key/" -keyout db.key -out db.crt -days 3650 -nodes -sha256
Now create the "EFI signature list" version of PK by using the .crt file alongside the GUID. The efi-updatevar program requires this format.
cert-to-efi-sig-list -g "$(< guid.txt)" pk.crt pk.esl
Sign it with created private key:
sign-efi-sig-list -k pk.key -c pk.crt PK pk.esl pk.auth
Create the KEK and DB keys in the same way:
- cert-to-efi-sig-list -g "$(< guid.txt)" kek.crt kek.esl
- cert-to-efi-sig-list -g "$(< guid.txt)" db.crt db.esl
Now create combined lists containing the new key plus the default keys:
- cat old_KEK.esl kek.esl > combined_KEK.esl
- cat old_db.esl db.esl > combined_db.esl
Installing the keys
- efi-updatevar -e -f old_dbx.esl dbx
- efi-updatevar -e -f combined_db.esl db
- efi-updatevar -e -f combined_KEK.esl KEK
- efi-updatevar -f pk.auth PK
Note that the keys must be added in reverse order of significance: the database keys first, then the Key Exchange Key and finally the Platform Key. This is because setting a Platform Key locks the secure boot setup and should automatically return you to user mode.
You can verify with efi-readvar, that all keys are installed.
Note: Instead of using efi-updatevar, you could boot into UEFI setup and load your keys there, but some firmware is picky about key formats. Some will accept .esl, others require DER format. efi-updatevar is better tempered.
Finally sign your boot loader:
for example, in Slackware: sbsign --key db.key --cert db.crt --output /boot/efi/EFI/Slackware/elilo-x86_64.efi /boot/elilo-x86_64.efi
elilo-x86_64.efi is name of the EFI boot loader that Slackware generally uses. In this example /boot/elilo-x86_64.efi is the unsigned elilo binary and the signed version is put into /boot/efi/EFI/Slackware/elilo-x86_64.efi
You can now reboot and enable Secure boot and your system should boot normally.
Securing the kernel using the GRUB bootloader
Because elilo doesn't support secure boot, your kernel is not booted in secure mode and therefore the kernel and initrd do not need to be signed. If you consider this a security risk and want to boot the kernel in secure mode, you will need to use GRUB as your bootloader. GRUB is in any case the bootloader that most distros use by default. When GRUB is booted using secure boot, it will detect this and won't load an unsigned kernel. To override this behaviour, you can put the following into grub.cfg:
But it is better to make use of GRUB's ability to boot securely by properly signing your kernel. GRUB supports only GPG signatures, so you will have to use GPG to sign the kernel and initrd and also any GRUB modules that you will need. You can use an existing gpg key or create a new one with gpg --gen-key ...
- Export the GPG key:
- gpg --export <KEY_ID> gpg.key
- Add this to the begining of your grub.cfg:
- set check_signatures=enforce
- export check_signatures
- Sign grub.cfg:
- gpg --default-key <KEY_ID> --detach-sign /boot/grub/grub.cfg
- Create single grub binary by embedding the modules that are required for your hardware configuration into GRUB itself:
- grub-mkstandalone --disable-shim-lock --format x86_64-efi --fonts="dejavusansmono" --modules "part_gpt part_msdos fat ext2 gcry_sha512 gcry_rsa password_pbkdf2 pbkdf2 echo normal linux font all_video search search_fs_uuid reboot sleep" --pubkey gpg.key --output "bootx64.efi" "boot/grub/grub.cfg=/boot/grub/grub.cfg" "boot/grub/grub.cfg.sig=/boot/grub.cfg.sig"
- Sign it:
- sbsign --key db.key --cert db.crt --output /boot/efi/EFI/Slackware/bootx64.efi bootx64.efi
If your distro often updates grub.cfg, then it is better to embed some simple initial configuration file and load grub.cfg from there, so that the GRUB binary does not need to be regenerated and resigned every time.
set check_signatures=enforce export check_signatures search --no-floppy --fs-uuid --set=root <EFI-UUID|or where config is> configfile /grub.cfg echo Boting failed, rebooting the system in 10 seconds... sleep 10 reboot
Call it grub.init.cfg and embed this file with grub-mkstandalone.
Now every time kernel is updated it needs to be signed:
gpg --default-key <KEY_ID> --detach-sign /boot/efi/EFI/Slackware/bzImage gpg --default-key <KEY_ID> --detach-sign /boot/efi/EFI/Slackware/initrd.gz
And that's it! Your kernel should now boot in secure mode.
dmesg | grep -i secure
[ 0.006890] Secure boot enabled
But to really "secure" your system you may want to add a password to GRUB, otherwise anyone who has physical access can just go to the GRUB command line and enter:
Setting a password prevents this.