Secure your boot process: UEFI + Secureboot + EFISTUB + Luks2 + lvm + ArchLinux

This tutorial isn’t a basic setup how-to in a way you will learn how to install Arch Linux, neither is intended to replace the Installation Guide, This is a guide for those who want a laptop with data-at-rest encryption and a verified boot process using SecureBoot.

I’ll not be arrogant saying that this setup is “tampering-proof” since this also depends on your firmware manufacturer, but I believe that this is a notebook setup with good enough security.

What is this blogpost about

  • Arch Linux setup overview.
  • Basic secureboot explanation.
  • Luks2+lvm setup for encrypted partitions at boot time.
  • /home disk setup with crypttab.
  • EFISTUB to make Linux “it’s own bootloader” avoiding the entire /boot to be mounted on your ESP.

Secureboot drama

UEFI is the new standard for boot and firmware management and it isn’t perfect, but is a natural answer to the old BIOS standard that has it’s limitations and is not aging well considering those limits and all workarounds involved to break them. You can find more information here. BIOS standard first appeared in IBM computers in 1976 and should (hopefully) die soon.

To keep words/concepts best alligned with what is correct, i’ll not call your basic computer program BIOS but firmware from now on during this reading.

I bought a laptop and wanted to encrypt all my data and thought: “Hey, i can do a full disk encryption but what if someone tamper my MBR/Boot Sector? So, using secureboot whas the best alternative (even with efi having a plain FAT32 partition on it’s standard).

There’s a lot of drama around secureboot, most of it related to Microsoft and the way they demand deploying their keys on OEM vendor equipments. That doesn’t mean that secureboot is bad.

Pretty simple “explain like i’m five” secureboot concept: A Root of Trust combination with keys and certificates. Using SecureBoot your firmware will check if the operating system you are trying to boot and your bootloader are trusted by you. On each boot-up UEFI firmware will inspect what you are trying to boot and if it’s not trusted a security violation will be triggered.

There are four main EFI “variables” used to create a basic secureboot Root of Trust environment:

  • PK: The Platform Key, the master one, the ring to rule them all. The holder of a PK can install a new PK and update the KEK.
  • KEK: Key Exchange Key is a secondary key used to sign EFI executables directly or a key used to signd the db and dbx databases.
  • db: The signature databse is a list with all allowed signing certificates or criptografy hashes to allowed binaries. We will use THIS db key to sign our Linux Kernel.
  • dbx: The dark side of the db. Inverse db. “not-good-db”. You name it. It’s the list containing all keys that are not allowed.

I would like to highlight the following points of this setup:

  • Your signing keys are stored inside an encrypted disk.
  • Kernel signing will only happend after you have booted and running an already signed kernel.
  • Most notebooks today don’t have an exposed CMOS to keep configurations but instead they have nvram modules that will store firmware and configurations.
    • So, put a passord on your firmware to avoid secureboot being disabled.
    • Putting a password on your firmware will almost invalidate any chance of boot option change or secureboot disable.
    • In my case there is no “reset bios password” option and losing it will require contacting Lenovo to replace the main board.
  • Even with secureboot disabled, and attacker will not be able to decrypt your root partition without knowing your password
  • If you are worried about keys being accessed after booting, you have other issues to solve and disk encryption + secureboot will not be the answer.
  • Modern processors have aes-ni instuction that will help on disk decryption avoiding high cpu usage for this task.
  • Using a key to unlock luks /home partition is a way to increase convenience without sacrificing security. That key is stored inside another encrypted partition so, there is no much to worry about.
  • lvm inside a luks container gives you a lot of flexibility. This will also remove any visibility if someone steal you equipment since lvs will be inside one container.
    • Less error prone setup while manipulating UUIDs is also an implicit feature.

With that in mind, lets install ArchLinux, first boot it and create the Root of Trust of your notebook.

Installing Arch Linux

There’s a plenty of “efi how-tos” for Arch Linux on the internet, and some of the instructions here will be just an overview of what you dear reader will have to execute

Step 01: Download Arch Linux here and write it to a pendrive using dd bs=4M if=path/to/archlinux.iso of=/dev/sdx status=progress oflag=sync wheresdx is your pendrive. If you are using Windows to create your bootable pendrive Win32 Disk Imager will help you.

Step 02: Configure your firmware to boot using UEFI, but keep secure boot disabled. Allow boot from usb and change it to be your first boot device. These instructions are pretty much vendor dependent and can change depending on your equipment.

Step 03: Boot Arch Linux live usb, and after getting a shell change your keybord layout with the following command: loadkeys br-abnt2

After that, connect to your wifi using wifi-menu -o your_device. There is an issue with the latest Arch Linux iso(06/2020) and wifi-menu is not working as expected. If you are using ethernet just ignore this step. Enable ntp sync with timedatectl set-ntp true.

Step 04: Create luks2 containers and lvm2 volumes on the first disk. On my laptop i have 2 drives: sda is a ssd while sdb is a spinning disk. Use cgdisk /dev/sda and create a 256MB(i’m using 512MB but noticed that is way too much) partition for EFI (ESP) code ef00 and the rest of your disk space create a partition with code 8309(Linux Luks).

Create your luks container and open it. Default block cipher and block encyption mode should be good enough so there is no need of changing it with -c parameter:

cryptsetup -y -v --use-random luksFormat /dev/sda2
cryptsetup luksOpen /dev/sda2 crypt

Create your lvm infraesturucture on top of it. I’ll create swap and root logical volumes

pvcreate /dev/mapper/crypt
vgcreate vg0 /dev/mapper/crypt
lvcreate --size 4G vg0 --name swap
lvcreate --size 30G vg0 --name root

Format your ESP, root and swap partitions/volumes

mkfs.vfat -F32 /dev/sda1
mkfs.ext4 /dev/mapper/vg0-root
mkswap /dev/mapper/vg0-swap

Step 05: Now create a luks2 container without lvm on it cause we will use the full disk just for /home and automatically map/mount it using crypttab+fstab here. Instead of typing a password 2 times during boot(one for root, another for home), we will just type the password for the root partition and host a key inside this encrypted partition to open the home luks container. Using a key to open a luks device has the same risks as typing a password and storing it into your ram. cgdisk /dev/sdb and create an all-disk partition using 8309 partition code.

cryptsetup -y -v --use-random luksFormat /dev/sdb1
cryptsetup luksOpen /dev/sdb1 crypthome
mkfs.ext4 /dev/mapper/crypthome

Step 06: Mount all and start to install:

mount /dev/mapper/vg0-root /mnt
mkdir /mnt/efi
mkdir /mnt/home
mount /dev/sda1 /mnt/efi
mount /dev/mapper/crypthome /mnt/home
pacstrap /mnt base base-devel vim efibootmgr linux linux-firmware lvm2 mkinitcpio networkmanager intel-ucode git efitools wget python

Do not install intel-ucode if you are using an AMD processor.

Step 07: Create your fstab and change root to your new system

genfstab -U /mnt >> /mnt/etc/fstab
arch-chroot /mnt

Step 08: Change your fstab /home mount point to use the device mapper name. If you try to mount it using UUID it will fail cause it needs to be decrypted first.

# /home
/dev/mapper/crypthome	/home     	ext4      	rw,relatime	0 2

Create a key to automatically open the home luks container. Change the “secretfolder” path example as you please.

mkdir /root/secretfolder
chmod 700 /root/secretfolder
dd bs=512 count=4 if=/dev/urandom of=/root/secretfolder/crypto_keyfile.bin

Find the UUID of your home luks container(sdb1 not crypthome) and add it to your /etc/crypttab. Crypttab columns are: mapping name(crypthome), luks partition UUID, key path and luks. You can check disks UUID by issuing blkid /dev/yyy where yyy could be a partition or a disk. In this case, use sdb1.

crypthome UUID=29d3555d-cccc-yyyy-xxxx-xxxxxxxxxxxx /root/secretfolder/crypto_keyfile.bin luks

Step 09: Configure the rest of the system. Remember, this is just an overview of the Arch Linux installation and the focus here is on the secureboot aspect of this setup. In this step we will configure locale, localtime, keymap, hostname and user. I’m configuring a setup for a Brazilian Portuguese user so, change this info to reflect your language.

ln -s /usr/share/zoneinfo/America/Sao_Paulo /etc/localtime
hwclock --systohc
echo LANG=pt_BR.UTF-8 > /etc/locale.conf
echo KEYMAP=br-abnt2 > /etc/vconsole.conf
echo hostname_i_want > /etc/hostname

Uncomment the pt_BR.UTF-8 UTF-8 line inside /etc/locale.gen and generate this localization with:

locale-gen

Create a password for root, and the basic info for your user(change myuser to your login):

passwd
useradd -m -G wheel,users myuser
passwd myuser

Step 10: Edit your mkinitcpio.conf and include the HOOKS keyboard, keymap, lvm2 and resume. Include ext4 on MODULES and change COMPRESSION to cat. You can check my config file here. Recreate your initd:

mkinitcpio -p linux

You may have noticed the i915 module on my mkinitcpio.conf. That’s how you avoid video flickering during the boot process if you are a user of an Intel Integrated graphics card.

Step 11: Lets check if your motherboard is able to handle EFI entries using the following command:

bootctl status| grep -i "sets"
       ✓ Boot loader sets ESP partition information

If this option is marked with or a green square you will be able to set boot information on your motherboard directly. Otherwise, you’ll have to rely on a bootloader like systemd-boot. You could obviously use the default EFI fallback option (EFI/BOOT/BOOTX64.EFI) but the sofware we will use to automatically create the EFISTUB and sign it will not put your STUB on that location.

You can’t use grub here cause it does not support luks2 volumes yet (maybe 2.06 will do).

Step 12(optional): Install systemd-boot as a first boot measurement for efi boot:

bootctl --esp-path=/efi install

We will use this bootloader for the very first time to keep things simpler and to have the flexibility to boot KeyFile when we were ready to deploy our efi Root of Trust.

After signing our first kernel and after we are sure that we will not be locked outside we can get rid of systemd-boot just by removing the /efi/EFI/systemd/systemd-bootx64.efi and /efi/EFI/BOOT/BOOX64.EFI and changing the boot order with bootctl.

Create /efi/loader/loader.conf:

default signed.conf
editor no

Create /efi/loader/entries/signed.conf

title Arch
efi /BOOT/Arch/linux-signed.efi

Create /efi/loader/entries/keytool.conf

title KeyTool
efi /BOOT/KeyTool.efi

Copy KeyTool:

cp /usr/share/efitools/efi/KeyTool.efi /efi/BOOT/KeyTool.efi

Step 13: Manually create your EFISTUB containing OS release data, command line options, kernel and initrd:

Create /boot/cmdline.txt

cryptdevice=UUID=08be6138-ffff-vvvv-dddd-tttttttttttt:lvm:allow-discards resume=/dev/mapper/vg0-swap root=/dev/mapper/vg0-root rw quiet i915.fastboot=1

cryptdevice is your luks2 partition. I’m using UUID here but you could use =/dev/sda2 device path if you like but remember, UUIDs are better to make your system less error prone to disk changes. resume parameter is not needed if you will not use hibernation. root device needs to point to your lvm2 mapping name, rw and quiet are pretty much standard boot options and i915.fastboot=1 is used for that flickering behavior I’ve mentioned above.

Cd to your /boot dir and create the efi:

cd /boot
objcopy \
    --add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \
    --add-section .cmdline="cmdline.txt" --change-section-vma .cmdline=0x30000 \
    --add-section .linux="vmlinuz-linux" --change-section-vma .linux=0x40000 \
    --add-section .initrd="initramfs-linux.img" --change-section-vma .initrd=0x3000000 \
    /usr/lib/systemd/boot/efi/linuxx64.efi.stub /efi/BOOT/Arch/linux-signed.efi

Step 13.1(optional): If you didn’t ran into step 12, you could just copy /efi/BOOT/Arch/linux-signed.efi to the default EFI boot path /efi/EFI/BOOT/BOOX64.EFI to have a first boot, and you could create manually a boot entry to your KeyTool efi. systemd-boot is just a little helper during your setup.

Reboot.

If something goes wrong here, you can boot your Live usb Arch Linux and open your luks2 partitions with cryptSetup luksOpen and remap all your volumegroups with lvscan -a. Nothing to worry. You should be prompted luks root device password after your system start.

Creating your Root of Trust

There is no need to reinvent the wheel so, we will use a pretty good script from mr. Roderick W. Smith aka rodsbooks.com. Check out his page for tons of informations related to EFI and other Linux related stuff. Access http://www.rodsbooks.com/efi-bootloaders/controlling-sb.html#creatingkeys download the script directly:

su - root
cd /root
mkdir keys
cd keys
wget https://www.rodsbooks.com/efi-bootloaders/mkkeys.sh

Let’s explain the contents on this script:

  • openssl commands will basically create key and certificate pairs for PK, KEK and DB on X509 format
  • python line will randomly create a GUID for our secureboot assets (think it like a name to our keys)
  • cert-to-efi-sig-list is the software that will convert X509 certificates(openssl ones) to a format that EFI can understand
  • touch will crate an empty noPK file since we do not have a list of revoked keys
  • sign-efi-sig-list commands will sign PK with itself, sign noPK using PK, sign KEK using PK and finally sign DB using KEK creating all your root trust.

After taking a look at the script, just run it :)

chmod +x mkkeys.sh
./mkkeys.sh

Install sbupdate software from AUR. Use an AUR helper (best choice is yay from Blackarch repository) or build it by manually.

Edit /etc/sbupdate.conf to change your signed EFISTUB output location and to also sign your systemd-boot efi for now. Add your boot parameters on the CMDLINE_DEFAULT line.

#KEY_DIR="/etc/efi-keys"
ESP_DIR="/efi"
OUT_DIR="BOOT/Arch"
#SPLASH="/usr/share/systemd/bootctl/splash-arch.bmp"
#BACKUP=1
EXTRA_SIGN=('/efi/EFI/BOOT/BOOTX64.EFI' '/efi/EFI/systemd/systemd-bootx64.efi')
CMDLINE_DEFAULT="cryptdevice=UUID=08be6138-f43a-4270-b5a3-f93562e7ab19:lvm:allow-discards resume=/dev/mapper/vg0-swap root=/dev/mapper/vg0-root rw quiet i915.fastboot=1"

Copy all keys and certs to /etc/efi-keys

cp /root/keys/*.key /etc/efi-keys
cp /root/keys/*.crt /etc/efi-keys

sbupdate package will create some hooks to trigger EFISTUB generation and signing after each Linux package variant (linux, linux-lts, linux-zen…) update. The efi file output will always be named <linuxpackagename>-signed.efi. You can test this behavior by updating/reinstalling your linux package with pacman -S linux. There is no problem if you sign an efi file with secureboot disabled.

Or, you could manually sign your kernel with the following command:

sbsign --key /etc/efi-keys/DB.key --cert /etc/efi-keys/DB.crt --output /efi/BOOT/Arch/linux-signed.efi /efi/BOOT/Arch/linux-signed.efi

This will sign /efi/BOOT/Arch/linux-signed.efi and output to /efi/BOOT/Arch/linux-signed.efi. Remember to sign your systemd-boot files too.

Deploying your Root of Trust

This step will be higly dependent on what hardware you are using. Some notebooks allow you to import your keys into firmware just by entering firmware setup, accessing the “Security” tab and pointing the files hosted on a FAT32 usb drive. If your firmware does not have that option, you will have to copy your keys into a USB drive(or into your ESP since its FAT32) and deploy them. To copy to your ESP partition:

copy /root/keys/*.esl /efi/BOOT
copy /root/keys/*.auth /efi/BOOT

You will need to be sure that Secure boot is in “setup mode” not “user mode”. Some notebooks also have this option explicitly described, but others like mine will trigger setup mode as soon as you hit “remove oem keys”. After enabling setup mode, boot into KeyTool.efi entry. Backup the existing keys if you like, but I see no need to do it.

Now, add your keys following this order:

  • Select the db entry, hit “Add new key”, and point to DB.esl
  • Select the kek entry, hit “Add new key” and point to KEK.esl
  • Finally, add the Platform Key(PK), select “Replace Keys” and point to PK.auth
  • Exit KeyTool and reboot
  • Enable Secure Boot
  • Obviously remove all keys that are hosted on your ESP partition and KeyTool.efi(from now on, KeyTool will not work since is not signed and Secure Boot should be in user mode). Use a removal tool like wipe or blackarch/secure-delete to reduce the chances of recovery, or even dd to create a big file with the size of the available space of your ESP partition.

Making Linux it’s own bootloader

If your motherboard lets you create boot entries(Step 11), create an Arch Linux entry:

efibootmgr --create --disk /dev/sda --part 1 --label "ArchLinux" --loader "BOOT\Arch\linux-signed.efi" --verbose

Update: ran into some issues with this word parameter syntax while trying to reproduce this environment on kvm. Solved with:

efibootmgr -c -d /dev/sda -p 1 --label "ArchLinux" -l "BOOT\Arch\linux-signed.efi" --verbose

This command is pretty self-explained: ESP disk, ESP partition, label and efi location.

Now, lets check the boot order

[root@walhala ~]# efibootmgr
BootCurrent: 0000
Timeout: 0 seconds
BootOrder: 0000,0002,2001,2002,2003
Boot0000* Linux Boot Manager
Boot0002* ArchLinux
Boot2001* EFI USB Device
Boot2002* EFI DVD/CDROM
Boot2003* EFI Network

Numbers could change in your setup but on my laptop systemd-boot is Boot0000. If you delete this entry, next in order will be 0002 so, there is no need to manually order your boot entries:

[root@walhala ~]# efibootmgr -b 0000 -B
BootCurrent: 0002
Timeout: 0 seconds
BootOrder: 0002,2001,2002,2003
Boot0002* ArchLinux
Boot2001* EFI USB Device
Boot2002* EFI DVD/CDROM
Boot2003* EFI Network

If ArchLinux isn’t your first boot option, reorder it by issuing efibootmgr -o 0002,2001,2020 with the corresponding boot numbers of your list.

Reboot and test. Now, you should have a fully functional secureboot+efistub+luks setup :). Double-check secure boot:

[root@walhala ~]# dmesg | grep -i secure
[    0.015260] Secure boot enabled

After that, you are free to remove any systemd-boot related files(line below) from your system and comment the following line on your sbupdate.conf configuration file

EXTRA_SIGN=('/efi/EFI/BOOT/BOOTX64.EFI' '/efi/EFI/systemd/systemd-bootx64.efi')

Now what?

Security isn’t a “one pill” medicine. There’s plenty of room for other tools to create a more secure environment:

  • Deploy tpm2 boot policies to create a Security Violation if boot options are changed
  • Use usbguard to avoid tricky devices like those usb drivers that are actually HIDs under-the-hood
  • Setup a vpn client to use on public wifi hostpots you don’t trust much
  • Create a fake windows installation partition before your root luks partition

Those are subjects to other blogposts :)

Be safe.