All posts by Håvard Moen

Full UEFI secure boot on Fedora using signed initrd and systemd-boot

Also posted on Medium.

“Security Circus” by Alexandre Dulaunoy is licensed under CC BY-SA 2.0.

Although secure boot support has been in place in most distros for a while, most, if not all, still have a missing part in the chain, namely the missing signing of the initrd image. This allows an attacker to tamper with the initrd image without detection. Lennart Pottering wrote a very good blog post about the issue and outlined a solution that can be implemented.

Until measures as outlined there or similar are implemented in the distros, there a couple of solutions ready to set up now. They do however require a little work from the end user.

Option one is to encrypt the boot partition. This works, however you need to use grub and grub still only supports luks version 1. Also you need to decrypt the boot partition using a password or passphrase, as this is the only method grub supports, not other methods such as fido2 or tpm, which is supported by systemd-cryptenroll.

The second option is the one I’m going to describe, that is to sign and verify the initrd image. That way, it does not matter that the boot partition is unencrypted, as there is no secrets in the initrd image, and any tampering will break the signature and the boot process.


In order to sign and validate the initrd image, you will first need to set up and install your own secure boot signing key. To ease setup and use, we use sbctl. First install required packages (if you don’t want to install asciidoc and go on your system, you can use toolbox)

sudo dnf install asciidoc golang

Download the sbctl release file, unpack and install (change version as needed)

VERSION=0.9
cd /tmp
curl -L "https://github.com/Foxboron/sbctl/releases/download/${VERSION}/sbctl-${VERSION}.tar.gz" | tar zxvf -
cd "sbctl-${VERSION}"
make
sudo make install

and generate the key by running

sudo sbctl create-keys

If your computer supports it, you can install the key by running

sudo sbctl enroll-keys

If successfull, reboot. If this fails, you will need to manually install the key. This can vary from system to system, but in most cases the following will work

  • Runsudo openssl x509 -in /usr/share/secureboot/keys/db/db.pem -outform DER /boot/efi/EFI/fedora/DB.cer
  • Reboot into BIOS. There will usually be an option under secure boot to add your own key to the DB database from file, where you can browse the EFI partition and find the DB.cer file in EFI/fedora

After adding the key, sudo sbctl status should now a checkmark for installed and secure boot, similar to

Installed:	✔ Sbctl is installed
Owner GUID: a9fbbdb7-a05f-48d5-b63a-08c5df45ee70
Setup Mode: ✔ Disabled
Secure Boot: ✔ Enabled

The next step is setting up systemd-boot and automatic signing of the initrd on kernel upgrades. First we need to change the efi mount from /boot/efi to /efi

sudo umount /boot/efi
sudo mkdir /efi
sudo sed -i `s-/boot/efi-/efi-' /etc/fstab
sudo mount /efi
sudo ln -s /efi /boot/efi

The last command is because some packages such as fwupd expects to find the efi mount at /boot/efi

Setup systemd-boot and sign it by running

sudo bootctl install
sudo sbctl sign /efi/EFI/systemd/systemd-bootx64.efi

You might want to edit /efi/loader/loader.conf and add timeout 3 or similar, in order to get a prompt to choose images when booting.

To make sure systemd-boot keeps being signed on updates, we will need the sbsigntoolspackage installed

sudo dnf install sbsigntools

Then run sudo systemctl edit systemd-boot-update.service and add the following

[Service]
ExecStart=/bin/sh -c 'sbverify --cert /usr/share/secureboot/keys/db/db.pem /efi/EFI/systemd/systemd-bootx64.efi || sbctl sign /efi/EFI/systemd/systemd-bootx64.efi'

Next, we add dracut configuration for creating a combined and signed file containing efi stub loader, kernel and initrd. Edit /etc/dracut.conf.d/local.conf and add

uefi=yes
uefi_secureboot_cert=/usr/share/secureboot/keys/db/db.pem
uefi_secureboot_key=/usr/share/secureboot/keys/db/db.key
dracut_rescue_image=no

The reason we disable the rescue image, is that this is very big and will most likely fill your EFI partition.

The last piece is to add a kernel install script to change the systemd-boot entry to load only the combined image. Edit /etc/kernel/install.d/99-use-signed-image.install with the content

#!/bin/bash
COMMAND="$1"
KERNEL_VERSION="$2"
ENTRY_DIR_ABS="$3"
if ! [[ $COMMAND == add ]]; then
exit 1
fi
MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"
BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT"
if [[ -f /etc/kernel/tries ]]; then
read -r TRIES </etc/kernel/tries
if ! [[ "$TRIES" =~ ^[0-9]+$ ]] ; then
echo "/etc/kernel/tries does not contain an integer." >&2
exit 1
fi
LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION+$TRIES.conf"
else
LOADER_ENTRY="$BOOT_ROOT/loader/entries/$MACHINE_ID-$KERNEL_VERSION.conf"
fi
sed -i "/^initrd/d" "${LOADER_ENTRY}"
sed -i "/^linux/s/linux$/initrd/" "${LOADER_ENTRY}"

Make the script executable by running sudo chmod 755 /etc/kernel/install.d/99-use-signed-image.install

Everything should now be up and running, the last thing to do is re-run kernel install for the current kernel in order to get a new updated and signd initrd image by running

sudo /bin/kernel-install -v  add $(uname -r) /lib/modules/$(uname -r)/vmlinuz

Reboot and you should have a boot that is signed all the way. Run sudo bootctl status to verify. Current Boot Loader should say systemd-boot and under Default Boot Loader Entry the linux line should point to initrd (this is the combined and signed initrd).

You can now remove the grub boot entry, so that there is no way to boot using the unsigned initrd image. Run sudo efibootmgr -v to list current boot entries. Then run sudo efibootmgr -B -b XX where XX is the number, for instance 0003

Finally, edit /etc/fwupd/uefi_capsule.conf and set EnableGrubChainLoad=false

Baconday 2021

This year was our 10 year anniversary, so we had re-runs of some of the dishes from the last 10 years (with some twists). For the canapes, we this year did a contest.

Running cec-client with Raspberry PI on Debian or Ubuntu 64-bit

Using cec-client with a raspberry pi on debian or ubuntu does not work out of the box, as the cec-client in the debian and ubuntu repositories has not been compiled with support for the propriary raspberry libraries. In addition, these libraries are 32-bit only. This is a guide in how to get this working.

First you need to set up a 32-bit chroot environment. Install debootstrap and then run

debootstrap --variant=buildd --arch=armhf bullseye /opt/cec-client/

You will then need to download the raspberry pi libraries. Unpack /opt/vc from the tarball into /opt/cec-client (replace url with newer version if needed):

cd /tmp
curl -L https://github.com/raspberrypi/firmware/archive/1.20210108.tar.gz | tar zxf -
cp -r firmware*/opt/ /opt/cec-client/

Add the raspberry pi lib to ldconfig by running

echo "/opt/vc/lib" > /opt/cec-client/etc/ld.so.conf.d/rpi.conf

You can now compile libcec and cec-utils:

chroot /opt/cec-client
ldconfig
apt-get update
apt-get install cmake libudev-dev libxrandr-dev python3-dev swig git 
cd /tmp/ 
git clone https://github.com/Pulse-Eight/platform.git
mkdir platform/build
cd platform/build
cmake .. 
make 
make install
cd /tmp/
git clone https://github.com/Pulse-Eight/libcec.git
mkdir libcec/build
cd libcec/build
cmake -DRPI_INCLUDE_DIR=/opt/vc/include -DRPI_LIB_DIR=/opt/vc/lib ..
make -j4 
make install
ldconfig
cd /tmp/
mkdir libcec/src/cec-client/build/
cd libcec/src/cec-client/build/
make
make install

In order to run cec-client inside the chroot, you will also need access to devices. You can do this by mounting this up before running chroot, but an easier and better way is to use systemd. Many of the examples you will find of how to use cec-client adds the -s flag to run a single command, but without this, cec-client runs until told to quit and listens on commands on stdin. We will use this and let systemd set up a fifo for commands for us. Create the socket and service files:

[Unit]
Description=CEC client socket

[Socket]
ListenFIFO=/run/cec.fifo

[Install]
WantedBy=sockets.target
[Unit]
Description=CEC client
After=network.target

[Service]
Type=simple
Restart=no
RootDirectory=/opt/cec-client
ExecStart=/usr/local/bin/cec-client -d 1
ExecStop=/bin/bash -c "echo q > /run/cec.fifo"
StandardInput=socket
StandardOutput=journal
MountAPIVFS=yes

[Install]
WantedBy=multi-user.target

Then reload systemd, enable and start:

systemctl daemon-reload
systemctl enable cec-client.socket
systemctl start cec-client.socket

You can now send cec-client commands by writing to /run/cec.fifo, for instance to turn on the tv with address 0.0.0.0 run

echo 'on 0.0.0.0' > /run/cec.fifo

Baconday 2019

Baconday 2018