Now that The Linux Foundation is a member of the UEFI.org group, I’ve been working on the procedures for how to boot a self-signed Linux kernel on a platform so that you do not have to rely on any external signing authority.
After digging through the documentation out there, it turns out to be relatively simple in the end, so here’s a recipe for how I did this, and how you can duplicate it yourself on your own machine.
We don’t need no stinkin bootloaders!
When building your kernel image, make sure the following options are set:
1 2 3 4 5 6 7 8 9 10
The first two options here enable EFI mode, and tell the kernel to build itself as a EFI binary that can be run directly from the UEFI bios. This means that no bootloader is involved at all in the system, the UEFI bios just boots the kernel, no “intermediate” step needed at all. As much as I love gummiboot, if you trust the kernel image you are running is “correct”, this is the simplest way to boot a signed kernel.
As no bootloader is going to be involved in the boot process, you need to ensure that the kernel knows where the root partition is, what init is going to be run, and anything else that the bootloader normally passes to the kernel image. The option listed above, CONFIG_CMDLINE should be set to whatever you want the kernel to use as the command line.
Also, as we don’t have an initrd passed by the bootloader to the kernel, if you want to use one, you need to build it into the kernel itself. The option CONFIG_INITRAMFS_SOURCE should be set to your pre-built cpio initramfs image you wish to use.
Note, if you don’t want to use an initrd/initramfs, don’t set this last option. Also, currently it’s a bit of a pain to build the kernel, build the initrd using dracut with the needed dracut modules and kernel modules, and then rebuild the kernel adding the cpio image to the kernel image. I’ll be working next on taking a pre-built kernel image, tearing it apart and adding a cpio image directly to it, no need to rebuild the kernel. Hopefully that can be done with only a minimal use of libbfd
After setting these options, build the kernel and install it on your boot partition (it is in FAT mode, so that UEFI can find it, right?) To have UEFI boot it directly, you can place it in /boot/EFI/boot/bootx64.efi, so that UEFI will treat it as the “default” bootloader for the machine.
Lather, rinse, repeat
After you have a kernel image installed on your boot partition, it’s time to test it.
Reboot the machine, and go into the BIOS. Usually this means pounding on the F2 key as the boot starts up, but all machines are different, so it might take some experimentation to determine which key your BIOS needs. See this post from Matthew Garrett for the problems you might run into trying to get into BIOS mode on UEFI-based laptops.
Traverse the BIOS settings and find the place where UEFI boot mode is specified, and turn it the “Secure Boot” option OFF.
Save the option and reboot, the BIOS should find the kernel located at boot/EFI/boot/bootx64.efi and boot it directly. If your kernel command line and initramfs (if you used one) are set up properly, you should now be up and running and able to use your machine as normal.
If you can’t boot properly, ensure that your kernel command line was set correctly, or that your initramfs has the needed kernel modules in it. This usually takes a few times back and forth to get all of the correct settings properly configured.
Only after you can successfully boot the kernel directly from the BIOS, in “insecure” mode should you move to the next step.
Keys to the system
Now that you have a working kernel image and system, it is time to start messing with keys. There are three different types of UEFI keys that you need to learn about, the “Platform Key” (known as a “PK”), the “Key-Exchange Keys” (known as a “KEK”), and the “Signature Database Key” (known as a “db”). For a simple description of what these keys mean, see the Linux Foundation Whitepaper about UEFI Secure boot, published back in 2011. For a more detailed description of the keys, see the UEFI Specification directly.
For a very simple description, the “Platform Key” shows who “owns and controls” the hardware platform. The “Key-Exchange keys” shows who is allowed to update the hardware platform, and the “Signature Database keys” show who is allowed to boot the platform in secure mode.
If you are interested in how to manipulate these keys, replace them, and do neat things with them, see James Bottomley’s blog for descriptions of the tools you can use and much more detail than I provide here.
To manipulate the keys on the system, you need the the UEFI keytool USB image from James’s website called sb-usb.img (md5sum 7971231d133e41dd667a184c255b599f). dd the image to a USB drive, and boot the machine into the image.
Depending on the mode of the system (insecure or secure), you will be dropped to the UEFI console, or be presented with a menu. If a command line, type KeyTool to run the keytool binary. If a menu, select the option to run KeyTool directly.
Save the keys
First thing to do, you should save the keys that are currently on the system, in case something “bad” ever happens and you really want to be able to boot another operating system in secure mode on the hardware. Go through the menu options in the KeyTool program and save off the PK, KEK, and db keys to the USB drive, or to the hard drive, or another USB drive you plug into the system.
Take those keys and store them somewhere “safe”.
Clear the machine
Next you should remove all keys from the system. You can do this from the KeyTool program directly, or just reboot into the BIOS and select an option to “remove all keys”, if your BIOS provides this (some do, and some don’t.)
Create and install your own keys
Now that you have an “empty” machine, with the previous keys saved off somewhere else, you should download the sbsigntool and efiutil packages and install them on your development system. James has built all of the latest versions of these packages in the openSUSE build system for all RPM and DEB-based Linux distros. If you have a Gentoo-based system, I have checked the needed versions into portage, so just grab them directly from there.
The efitools README is a great summary of how to create new keys, and here is the commands it says to follow in order to create your own set of keys:
1 2 3 4 5 6 7 8
The option -subj can contain a string with whatever name you wish to have for your key, be it your company name, or the like. Other fields can be specified as well to make the key more “descriptive”.
Then, take the PK key you have created, turn it into a EFI Signature List file, and add a GUID to the key:
Where my random guid is any valid guid you wish to use (I’ve seen some companies use all ‘5’ as their guid, so I’d recommend picking something else a bit more “random” to make look like you know what you are doing with your key…).
Now take the EFI Signature List file and create a signed update file:
For more details about the key creation (and to see where I copied these command lines from), see James’s post about owning your own Windows 8 platform.
Take these files you have created, put them on a USB disk, run the KeyTool program and use it to add the db, KEK, and PK keys into the BIOS. Note, apply the PK key last, as once it is installed, the platform will be “locked” and you should not be able to add any other keys to the system.
Fail to boot
Now that your own set of keys is installed in the system, flip the BIOS back into “Secure boot” mode, and try to boot your previous-successful Linux image again.
Hopefully it should fail with some type of warning, the laptop I did this testing on provides this “informative” graphic:
Sign your kernel
Now that your kernel can’t boot, you need to sign it with the db key you placed in your bios:
Take the bzImage.signed file and put it back in the boot partition, copying over the unsigned /boot/EFI/boot/bootx64.efi file.
Now, rebooting the machine should cause the UEFI bios to check the signatures of the signed kernel image, and boot it properly.
I’ve recorded a video of a Gateway laptop booting a signed kernel, with my own key, here. The demo tries to boot an unsigned kernel image that is on the hard disk, but it fails. I plug in a signed kernel that is on the USB disk, and it properly boots.
I did the test with a CoreOS image as it provides a very small self-contained Linux system that allows for easy testing/building from a development machine.
Now that you have full control over your system, running only a Linux kernel image that you sign yourself, a whole raft of possibilities open up. Here’s a few that I can think off of the top of my head:
- Linux signed system self-contained in the kernel image (with initramfs) booting into ram, nothing on the disk other than the original kernel image.
- Signed kernel image initramfs validates the other partitions with a public key to ensure they aren’t tampered before mounting and using them (ChromeOS does this exact thing quite well). This passes the “chain of trust” on to the filesystem image, giving you assurances that you are running code you trust, on a platform you trust.
- Combine signed kernel images with TPM key storage to unlock encrypted partitions.
If you are interested in these types of things, I’ll be at the Linux Plumbers Conference in a few weeks, where a bunch of people will be discussing secure boot issues with Linux. I’ll also be at LinuxCon North America, Europe, and Korea if you want to talk about UEFI and Linux issues there.