Matt White

Matt White

developer

Matt White

developer

| blog
| categories
| tags
| rss

LUKS-Encrypted Arch with systemd-boot and SecureBoot

When I was getting started with Arch linux, I quickly learned that there were a lot of tricky parts that no amount of studying the installation guide could get me through. I ran into a lot of problems that I could only sort through by reading other people’s system writups.

I just ran through a complete system rebuild today and figured it was time to do my own writup.

Live USB

Create and boot from an Arch live USB.

SSH (Optional)

If you have another system you can SSH in from, it’ll allow you to copy/paste commands.

Create a password for the root user on the live USB

passwd

Enable the ssh service

systemctl start sshd.service

You can find the ip address of the system:

ip addr show

The output can be a bit verbose. You can try to narrow it down by piping to grep.

ip addr show | grep "state UP"

Use that ip address to ssh in from a different system:

ssh root@<ip-address-you-found>

Check the Boot Mode

cat /sys/firmware/efi/fw_platform_size

For my system, the output is 64, which indicates that the system is in UEFI mode in 64-bit. A 32 would indicate UEFI mode in 32-bit.

If the output is anything else, you’ve got to go messing with your motherboard.

Create the Partitions

List Disks and Partitions

To identify the disk(s) you want to use during the installation:

lsblk

Example output:

NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
sdb             8:0    0 465.8G  0 disk  
sda             8:16   0   9.1T  0 disk 

For our purposes, let’s use sdb as the target disk.

Create the Partitions

Using gdisk for partitioning.

gdisk /dev/sdb

Create 3 partitions:

  • Partition 1 - Code EA00 - XBOOTLDR partition - 512MB
  • Partition 2 - Code EF00 - EFI System Partition - 256MB
  • Partition 3 - Code 8309 - Linux LUKS - All the remaining space

Since gdisk is interactive, I have to show this awkward collection of key presses. Each use of [Enter] below indicates that I’m taking the default value.

# Create a new GUID Partition Table (GPT)
o
Y

# Create the XBOOTLDR partition
n
[Enter]
0
+512M
ea00

# Create the EFI partition
n
[Enter]
[Enter]
+256M
ef00

# Create the LUKS partition
n
[Enter]
[Enter]
[Enter]
8309

# Persist the changes
w
Y

Setup LUKS

Use luks to encrypt and open the 3rd partition:

cryptsetup luksFormat \
    --use-random \
    -S 1 \
    -s 512 \
    -h sha512 \
    -i 5000 \
    /dev/sdb3
cryptsetup open /dev/sdb3 cryptlvm

Setup Volumes

First, create the physical volume. I use the name cryptlvm, but the name is arbitrary.

pvcreate /dev/mapper/cryptlvm

Create a volume group:

vgcreate vg /dev/mapper/cryptlvm

Now create logical volumes for swap, root, and home.

Use the appropriate amount of space for root, which is where installed packages will live by default. 128G is enough for me right now, but for you it may be too small.

lvcreate -L 8G vg -n swap
lvcreate -L 128G vg -n root
lvcreate -l 100%FREE vg -n home
NOTE the case-sensitivity of arguments. The ‘-l’ is not a typo for ‘-L’.

Format the Partitions

Using FAT32 for the efi and boot partitions - they need to be readable by the firmware.

mkfs.fat -F32 /dev/sdb1
mkfs.fat -F32 /dev/sdb2

Use the old reliable ext4 for root and home.

mkfs.ext4 /dev/vg/root
mkfs.ext4 /dev/vg/home
mkswap /dev/vg/swap

Mounting

Mount the root partition.

mount /dev/vg/root /mnt

Create the nested mount points.

mkdir /mnt/home
mkdir /mnt/boot
mkdir /mnt/efi

And mounting

mount /dev/vg/home /mnt/home
mount /dev/sdb1 /mnt/boot
mount /dev/sdb2 /mnt/efi
swapon /dev/vg/swap

At this point, lsblk should produce output that looks similar to this:

NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
sdb             8:0    0 465.8G  0 disk  
├─sdb1          8:1    0   512M  0 part  /boot
├─sdb2          8:2    0   256M  0 part  /efi
└─sdb3          8:3    0 465.2G  0 part  
  └─cryptlvm  254:0    0 465.2G  0 crypt 
    ├─vg-swap 254:1    0     8G  0 lvm   [SWAP]
    ├─vg-root 254:2    0   128G  0 lvm   /
    └─vg-home 254:3    0 329.2G  0 lvm   /home

Setup Initial Installation

Packages

Alter the mirror list, if you have any preferences.

vim /etc/pacman.d/mirrorlist

Install any essential programs.

pacman -Sy archlinux-keyring
pacstrap /mnt \
    base \
    linux \
    linux-firmware \
    base-devel \
    git \
    sudo \
    archlinux-keyring \
    lvm2 \
    mkinitcpio \
    vim \
    dhcpcd \
    openssh

Configure fstab

This will copy your current setup for all the mounted volumes/partitions/etc and save it to fstab so that it can be setup automatically on boot.

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

Chroot Into the Installation

arch-chroot /mnt

Update Root Password

passwd

Create User and Basic Configuration

Create a user and set the password.

useradd -m matt
passwd matt

Add the user to the wheel group, which will determine who has access to sudo.

gpasswd -a matt wheel

Run visudo, which will open a file where you can add wheel users to sudoers. Uncomment this relevant section:

## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL) ALL

Setting the Clock

timedatectl set-ntp true

Adjust to your own time zone as-needed.

ln -sf /usr/share/zoneinfo/America/Denver /etc/localtime
hwclock --systohc

Locale

Uncomment en_US.UTF-8 UTF-8 in /etc/locale.gen (or whatever line applies to you) and generate locale:

locale-gen

And similar for locale configuration:

echo "LANG=en_US.UTF-8" >> /etc/locale.conf

Networking

echo "myhostname" >> /etc/hostname

Update /etc/hosts

127.0.0.1	localhost
::1		ip6-localhost
127.0.1.1	myhostname.localdomain	myhostname

Enable dhcpcd and ssh so everything works on the system’s first reboot

systemctl enable dhcpcd
systemctl enable sshd

.local Hostname Resolution

I have a ton of machines on my local network that I need to access on a regular basis. Local hostname resolution helps avoid hardcoding IP addresses everywhere.

Setup local hostname resolution with Avahi.

pacman -Sy avahi nss-mdns

Update hosts in /etc/nsswitch.conf to add mdns. The main thing is to make sure mdns_minimal [NOTFOUND=return] gets added somewhere between files and dns. Resolution will check sources in order. We don’t want it querying dns for local requests.

hosts: mymachines resolve [!UNAVAIL=return] files myhostname mdns_minimal [NOTFOUND=return] dns
note that myhostname and mymachines are actual values, not placeholders

Enable avahi

systemctl enable avahi-daemon

Initramfs

Create a Keyfile

A keyfile lets us bypass unnecessary passwords on boot. Create one and add it as a LUKS key.

mkdir /root/secrets
chmod 700 /root/secrets
head -c 64 /dev/urandom > /root/secrets/crypto_keyfile.bin
chmod 600 /root/secrets/crypto_keyfile.bin
cryptsetup -v luksAddKey -i 1 /dev/sdb3 /root/secrets/crypto_keyfile.bin

Find the partition’s UUID value (and keep it on hand, we need it a few times). It will be one of these:

blkid | grep crypto_LUKS

Add this info to /etc/crypttab:

cryptlvm       UUID=<YOUR UUID> /root/secrets/crypto_keyfile.bin luks

Update mkinitcpio

We need to modify /etc/mkinitcpio.conf and add some hooks. lvm2 is required since this enables the initramfs to properly manage LVM volumes during the boot process. encrypt is required since we’re using LUKS. filesystems is required for ext4, but should already be there.

There’s a section for HOOKS, which should end up looking similar to this:

HOOKS=(base udev autodetect keyboard modconf block encrypt lvm2 filesystems fsck)

Create the initramfs Image

mkinitcpio -p linux

Add Microcode Support

You can check your processor with

lscpu

Based on the results, install either amd-ucode or intel-ucode

pacman -S <your-ucode-type>

Note this value, which is used in the bootctl configuration, below.

Configure the Bootloader

So far I prefer systemd-boot to GRUB, which I was never able to get to cooperate with SecureBoot or LUKS2.

Create an entry at /boot/loader/entries/arch.conf:

title Arch Linux
linux /vmlinuz-linux
initrd /<YOUR-UCODE-TYPE>.img
initrd /initramfs-linux.img
options cryptdevice=UUID=<UUID-OF-ROOT-PARTITION>:cryptlvm root=/dev/vg/root rw cryptkey=rootfs:/root/secrets/crypto_keyfile.bin

Configure /efi/loader/loader.conf:

default  arch.conf
timeout  5
console-mode max
editor   no

You may want a lower timeout value; it dictates how many seconds the loader selection screen shows on boot.

Sanity Check

Run bootctl with no params. It should tell you if your configuration is obviously broken.

Restart the System

Exit chroot and reboot.

exit
umount -a /mnt
reboot

The new installation should be able to boot via systemd-boot without the live usb.

Secure Boot

Boot up normally without the live USB.

Run as root: sudo su

Check the Current Status

Install sbctl:

pacman -S sbctl

Check if everything is ready by running:

sbctl status

These are the states we’re looking for. Setup mode, no secure boot, sbctl installed.

Installed:	    ✓ sbctl is installed
Setup Mode:	    ✗ Enabled
Secure Boot:	✗ Disabled
This can still go fine if sbctl doesn’t realize that its installed.

Disable Secure Boot and Enter Setup Mode

If the status isn’t what we want, we need to update the firmware settings.

systemctl reboot --firmware

When your system boots into the firmware, disable secure boot and enable setup mode/remove existing keys.

Unfortunately some motherboards make this quite hard to find.

You’ll want to set your UEFI firmware supervisor (administrator) password in the firmware settings; that way an attacker can’t simply boot into UEFI setup utility and turn off SecureBoot.

Don’t use the same UEFI firmware supervisor password as your encryption password for LUKS. On some old systems, the supervisor password can be recovered as plaintext from the EEPROM chip.

Restart.

Enroll the Keys

When you’re booted back into the new system:

sbctl create-keys

Enroll the keys

sbctl enroll-keys -m

Assuming that went well, we need to sign certain EFI files. To identify the files we need to sign, we use sbctl verify.

The lazy way I use:

sbctl verify | grep '✗' | cut -d ' ' -f 2 | xargs -n 1 sbctl sign -s

Need to sign these as well.

sbctl sign -s /boot/vmlinuz-linux

sbctl sign -s \
    -o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed \
    /usr/lib/systemd/boot/efi/systemd-bootx64.efi

Run sbctl verify again to ensure everything is kosher.

$ sbctl verify
Verifying file database and EFI images in /efi...
✓ /boot/vmlinuz-linux is signed
✓ /efi/EFI/BOOT/BOOTX64.EFI is signed
✓ /efi/EFI/systemd/systemd-bootx64.efi is signed
✓ /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed is signed

Turn on SecureBoot

Back to firmware settings.

systemctl reboot --firmware

Turn on Secure Boot in the settings and save. If all is well, your system should boot successfully.

Check if Secure Boot Worked

Now to check if everything worked out. sbctl status can help:

$ sbctl status
Installed:	✓ sbctl is installed
Owner GUID:	<some-guid>
Setup Mode:	✓ Disabled
Secure Boot:	✓ Enabled
Vendor Keys:	microsoft

And we can check the efi variable:

od -An -t u1 /sys/firmware/efi/efivars/SecureBoot-<some-long-string-of-numbers>

If it worked, the last number should be 1. Example output: 6 0 0 0 1

If this process didn’t work and you didn’t miss anything, the wiki is the only hope.

Learn by doing.