Set Up Secure Boot
Table of Contents
After installing Debian the first thing I’m going to do is set up Secure Boot. Usually, I’ll start with Secure Boot since some other setup and configuration requires the signing of kernel modules and I like keeping Secure Boot itself out of the equation when those steps come up.
Throughout this guide, I’ll be using my-debian
as the dirname for the EFI loader. However, you can name this whatever
you want it to be called.
First, we’ll install some packages that we need to manage the signing of the kernel and boot management utilities.
apt install sbsigntool efitools mokutil efibootmgr systemd-boot
Now we’re going to create the certificates that will be used to sign our kernel.
The MOK.der
will need to be added to your Secure Boot in UEFI settings.
- We create and go into the directory for the MOK keys.
- Generate the 4096 RSA certificates (you may need to change to 2048 depending on your system).
- Copy the
MOK.der
to the EFI partition (so we can register the certificate in the UEFI settings later).
# create the cert directory
mkdir -p /var/lib/shim-signed/mok/
cd /var/lib/shim-signed/mok/
# Generate a RSA 4096 key to sign the kernel
openssl req -nodes -new -x509 -newkey rsa:4096 -keyout MOK.priv -outform DER -out MOK.der -days 36500 -subj "/CN=Local Linux/"
openssl x509 -inform der -in MOK.der -out MOK.pem
# Copy the DER cert to the EFI partition to install on UEFI
cp /var/lib/shim-signed/mok/MOK.der /boot/efi/
You can check to see if systemd-ukify
is available with apt-cache
:
apt-cache search systemd-ukify
If it’s available, then install it from apt
:
apt-get install systemd-ukify
Otherwise, follow the manual installation instructions.
systemd-ukify
package is not yet available on the stable Debian version, so I am
manually pulling down the archive from Debian Sid. However, when it
becomes available directly through apt
you should install it that way instead of this manual way.This package can’t be installed directly as it requires a version of systemd
that isn’t installed.
Instead, just manually extract it to get the ukify Python script:
apt install binutils
ar x systemd-ukify_255.3-2_all.deb
tar -xvf data.tar.xz
mv ./usr/bin/ukify /usr/bin/
There is probably a missing Python package dependency that needs to be added:
apt install python3-pefile
Now that we have all the necessary tools installed, we can start setting up the Unified Kernel Module.
Get the root disk UUID from GRUB:
cat /boot/grub/grub.cfg | grep root=UUID=
Take the first line of the output, and you’ll use it in the next part.
As an alternative to finding it from the GRUB config, you can also locate the UUID by using fdisk
to find the disk and
get the UUID from /dev/disk/by-uuid
:
fdisk -l # locate the disk
ls -l /dev/disk/by-uuid/
Create a file to contain the command line arguments for the kernel /etc/kernel/cmdline
. This file will take the place
of adding command line options to the GRUB config (or the loader config if you’re coming from systemd-boot
):
root=UUID=<<UUID>> panic=0 ro quiet splash
panic=0
to this as this prevents a bad actor from corrupting the startup and falling back to an
emergency shell.Create the script /etc/kernel/postinst.d/zz-update-efistub
to update our EFI image(s) after the kernel changes:
#!/bin/bash
INITRD=/initrd.img
INITRD_NAME=$(basename $(readlink ${INITRD}))
UNAME=${INITRD_NAME//initrd.img-/}
ukify build \
--os-release @/usr/lib/os-release \
--cmdline @/etc/kernel/cmdline \
--linux /vmlinuz \
--initrd ${INITRD} \
--uname ${UNAME} \
--output "/boot/efi/EFI/my-debian/Linux.efi"
if [ $? -ne 0 ]; then
echo "Failed to build the unified kernel image"
exit 1
fi
sbsign \
--key /var/lib/shim-signed/mok/MOK.priv \
--cert /var/lib/shim-signed/mok/MOK.pem \
--output /boot/efi/EFI/my-debian/Linux.efi \
/boot/efi/EFI/my-debian/Linux.efi
if [ $? -ne 0 ]; then
echo "Failed to sign the unified kernel image"
exit 1
fi
zz
(this will make it the last script to run).We also need to run the script whenever the initramfs is updated:
mkdir -p /etc/initramfs/post-update.d
ln -s /etc/kernel/postinst.d/zz-update-efistub /etc/initramfs/post-update.d/zz-update-efistub
chmod +x /etc/kernel/postinst.d/zz-update-efistub
Now we need to run the script and make sure there are no errors in generating our unified kernel:
mkdir /boot/efi/EFI/my-debian
/etc/kernel/postinst.d/zz-update-efistub
Finally, we can add the bootloader to UEFI:
efibootmgr --disk /dev/nvme0n1 --create --label "Debian" --loader '\EFI\my-debian\Linux.efi'
Reboot and go into the UEFI setup to append the key to the db
keys under Secure Boot.
If everything was set up correctly when you go back into your system you should boot into to your newly signed Unified Kernel Image.
panic=0
, we aren’t going to be allowed, by the kernel, to drop into an emergency
shell if there’s a failure during the startup of the OS. Because of this we have to switch to another Linux distro,
decrypt and mount our disks, and manually fix the issues from there.If there is an issue booting after these steps, you’ll need to check the output from the shell to see what is going wrong. You can then look at the Chroot for Debugging guide for how to get into your Debian instance from either a live Linux distro or another Linux distro installed on your system.
Install DKMS to allow the signing of newly installed dynamic kernel modules in the future:
apt install dkms
Edit the /etc/dkms/framework.conf
and add the following two variables to sign dynamic kernel modules:
mok_signing_key="/var/lib/shim-signed/mok/MOK.priv"
mok_certificate="/var/lib/shim-signed/mok/MOK.der"
Debian will maintain the last kernel version that was installed on the system. This is useful when there are issues loading the current kernel version (usually after an update to the system). It’s also pretty straightforward to add this backup kernel, so we may as well.
Go back into the /etc/kernel/postinst.d/zz-update-efistub
script and add another ukify
and sbsign
to create the
Unified Kernel Image for the “old” Debian kernel image after the original one:
# Create Old Image
INITRD=/initrd.img.old
INITRD_NAME=$(basename $(readlink ${INITRD}))
UNAME=${INITRD_NAME//initrd.img.old-/}
ukify build \
--os-release @/usr/lib/os-release \
--cmdline @/etc/kernel/cmdline \
--linux /vmlinuz.old \
--initrd ${INITRD} \
--uname ${UNAME} \
--output "/boot/efi/EFI/my-debian-old/Linux.efi"
if [ $? -ne 0 ]; then
echo "Failed to build the unified kernel image (old)"
exit 1
fi
sbsign \
--key /var/lib/shim-signed/mok/MOK.priv \
--cert /var/lib/shim-signed/mok/MOK.pem \
--output /boot/efi/EFI/my-debian-old/Linux.efi \
/boot/efi/EFI/my-debian-old/Linux.efi
if [ $? -ne 0 ]; then
echo "Failed to sign the unified kernel image (old)"
exit 1
fi
Next we can create the EFI directory for the new image and run the script again to generate the new image:
mkdir -p /boot/efi/EFI/my-debian-old
/etc/kernel/postinst.d/zz-update-efistub
Finally, just add the new (old) image to UEFI:
efibootmgr --disk /dev/nvme0n1 --create --label "Debian Old" --loader '\EFI\my-debian-old\Linux.efi'
Now that everything should be working with Secure Boot we can clean up some of the things that we no longer need. In
general, we can get rid of GRUB (and it’s associated boot record), systemd-boot
, and even the original /boot
partition if we want to (since we don’t really need it separate from our root now that we have a Unified Kernel Image).
Now we can remove GRUB from the system:
apt remove --purge grub
apt autoremove --purge
In /boot/efi
we can also remove the old GRUB kernels that don’t seem to get cleaned up when removing GRUB from apt
:
# remove the one for your machine id
rm -rf /boot/efi/1474289c99204e339ed4ed9bc00f87cb/
rm -rf /boot/efi/EFI/debian
rm -rf /boot/grub
We can also remove the boot record for the old GRUB-based debian
bootloader now that we don’t use GRUB anymore. To do
so we will need to get the list of boot records:
efibootmgr
BootCurrent: 0004
Timeout: 1 seconds
BootOrder: 0004,0003,0000,0001,0002
Boot0000* Windows Boot Manager
Boot0002* debian
Boot0004* Debian
Find the one named debian
and remove it by its ID:
efibootmgr -b 2 -B
We can also remove the systemd-boot
bootloader that was added when we installed it:
bootctl remove
Running this command will cause the postinst.d
script that is added during the installation of systemd-boot
to be
removed, so it doesn’t get run when the initramfs is updated.
systemd-boot
for some of the utilities that are included with it. However, I’m not going to be using
systemd-boot
as the bootloader for my OS. If you want to use it, you just need to sign the bootloader whenever it is
updated using a post update script.Now that we use the Unified Kernel Module (which is installed on our EFI partition), we can also remove our existing
/boot
partition. To remove it, we can start by commenting out the mount of the /boot
partition in /etc/fstab
:
# /boot/efi was on /dev/nvme0n1p1 during installation
# UUID=<<UUID>> /boot ext2 defaults 0 2
Now, reboot your system. It may seem like the system won’t boot if you’ve had previous experience working on Linux with
the initrd.img
and vmlinuz
files living in your /boot
partition. However, as long as your UKI is set up properly
they won’t be needed to boot your system.
After you get back into your system, manually mount the old boot partition to /mnt
:
mount /dev/nvme0n1p3 /mnt
fdisk -l
to see what partition it is. Alternatively, you can find it by UUID by taking the UUID from the commented out
line in the /etc/fstab
and running ls -l /dev/disk/by-uuid/
to determine which disk the symlink points to.Now that it’s mounted at /mnt
we can just copy the config
, initrd.img
, and vmlinuz
files from the old boot
partition to the (now root disk) /boot
directory:
cp /mnt/config-* /mnt/initrd.img-* /mnt/vmlinuz-* /boot
Make sure the files all got copied over. Then you can unmount the partition and delete it using fdisk
if you’d like.
umount /mnt
Just to make sure everything is working correctly, I like to validate that rebuilding the initramfs works:
update-initramfs -u -k all
As long at that command works properly, you should no longer depend on the /boot
partition.
Another clean up task I like to perform is to move the /boot/efi
partition to /efi
. I think the partition map
tends to make a bit more sense when the core filesystem requirements are mounted directly on the root.
To start, update the /etc/fstab
to change the mount point:
# /boot/efi was on /dev/nvme0n1p1 during installation
UUID=<<UUID>> /efi ext2 defaults 0 2
Then, we just need to update the efistub script that we created earlier (/etc/kernel/postinst.d/zz-update-efistub
)
to use the new /efi
path. Anywhere in the script that currently says /boot/efi/EFI/
we just need to update it to
/efi/EFI/
.
I typically use lz4
compression due to its fast read time compared some other compression algorithms.
To start, make sure you have the lz4
package installed:
apt-get install lz4
Then, update the fields in /etc/initramfs-tools/initramfs.conf
to use lz4
for compression with level 9 (highest):
COMPRESS=lz4
...
COMPRESSLEVEL=9
Then update the kernel by running:
update-initramfs -u -k all
Doing so will save space on the EFI partition and (hopefully) speed up your boot time too.
Ukify can show a splash image during startup. However, I generally don’t add this to my kernel image and instead opt for installing Plymouth to display the boot splash screen.
Add an image to /etc/kernel/splash.bmp
for the ukify splash. Then, add the splash argument to ukify
in
/etc/kernel/postinst.d/zz-update-efistub
:
ukify
...
--splash /etc/kernel/splash.bmp
...
If you’ve managed to get all the way here, CONGRATULATIONS!!! You’ve successfully managed to change Debian from using (shim-signed) GRUB to using a signed Unified Kernel Image. This process is fairly complicated and involved to switch Debian over. However, I hope it wasn’t too painful to follow along. If you run into any issues along the way, feel free to leave a comment on this page, and I’ll try my best to help!
Also, now that all of this is done you should make sure to lock your UEFI using at least a supervisor password, so a malicious user can’t go into your settings and add a new key to the DB and use a malicious kernel image to boot into your system anyway. You should also be highly suspicious if your kernel image signature is no longer valid, as this is likely an indicator that someone is trying to replace your kernel with an untrusted one. Remember, our EFI partition is the only one that’s not encrypted, and therefore the only one that can be easily compromised by an attacker.