The flow
In order to decrypt the root filesystem, the kernel uses a initial ram disk (initramfs). The initramfs provides an temporary filesystem from which extra kernel modules can be loaded, it also contains a set of scripts used to boot the system including scripts to decrypt the user’s root filesystem.
This initramfs image is a file stored un-encrypted next to the kernel image. However, unlike the kernel image, it is not signed by the kernel publisher as the iniramfs is generated locally and can be modified by the user. Thus, anyone with physical access to the user’s drive can inject a malicious initramfs that would log the user’s passphrase and thus make FDE useless.
How to fix it
We can bundle the kernel and initramfs together in a single binary and sign this binary locally. Thus, modifying the initramfs would prevent the system from booting.
In practice
On systems using mkinitcpio
or dracut
see this article: https://wiki.archlinux.org/title/Unified_kernel_image#Preparing_a_unified_kernel_image.
On Ubuntu
To create the unified EFI binary:
sudo add-apt-repository ppa:snappy-dev/image
sudo apt-get -y install ubuntu-core-initramfs
sudo ubuntu-core-initramfs create-efi --unsigned --output "kernel.efi.unsigned" \
--cmdline "$(cut -f 2- -d' ' /proc/cmdline)" \
--kernel "/boot/vmlinuz" \
--kernelver "$(uname -r)" \
--initrd "/boot/initrd.img"
Create and enroll a new MOK:
# You can let everything as default, or customize the fields, it doesn't matter
openssl req -new -x509 -newkey rsa:2048 \
-nodes -days 36500 -outform DER \
-keyout "MOK.priv" \
-out "MOK.der"
openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem
sudo mokutil --import MOK.der
Restart your system and follow the instructions to enroll the key.
Sign the Kernel EFI:
sudo sbsign --key MOK.priv --cert MOK.pem kernel.efi.unsigned --output kernel.efi
Move it do the ESP (or to the /boot partition), example:
sudo mv kernel.efi /boot/efi/EFI/ubuntu
Add a new boot entry to boot on this kernel, example (make sure to point change --disk
and --part
to your ESP):
sudo efibootmgr --create --disk /dev/vda --part 15 --label "Ubuntu $(uname -r)" --loader "\EFI\ubuntu\shimx64.efi" -u "\EFI\ubuntu\kernel.efi"
Next
To make it persistent:
Make the kernel hook for ubuntu-core-initramfs
use your keys:
sudo mkdir /etc/custom-mok/
sudo mv MOK.priv MOK.pem /etc/custom-mok/
and modify the hook itself at /etc/kernel/postinst.d/ubuntu-core-initramfs
:
ubuntu-core-initramfs create-efi --key /etc/custom-mok/MOK.priv --cert /etc/custom-mok/MOK.pem --kernelver $version
create two new hooks:
cat /etc/kernel/postinst.d/zz-update-efi-boot
#!/bin/sh
set -e
version="$1"
command -v efibootmgr >/dev/null 2>&1 || exit 0
part_dev="$(blkid | grep 'LABEL_FATBOOT="UEFI"' | cut -f1 -d':')"
disk="/dev/$(lsblk -ndo pkname ${part_dev})"
part_name=${part_dev##/dev/}
part_num="$(cat /sys/class/block/${part_name}/partition)"
# Let's not duplicate the entry if it already exist
efibootmgr | grep -q "$version" && exit 0
echo "adding new EFI boot entry"
efibootmgr -q --create --disk "$disk" --part "$part_num" --label "Ubuntu $version" --loader "\EFI\ubuntu\shimx64.efi" -u "\EFI\ubuntu\kernel.efi-$version"
cat /etc/kernel/postrm.d/zz-update-efi-boot
#!/bin/sh
set -e
version="$1"
command -v efibootmgr >/dev/null 2>&1 || exit 0
BOOT_NUM=$(efibootmgr | grep "$version" | cut -f1 -d'*' | sed 's/Boot\(.*\)/\1/')
if [ -f "$BOOT_NUM" ]; then
exit 0
fi
efibootmgr -q -b "$BOOT_NUM" -B
and make sure it they are executable.