Raspberry Pi 5 with Arch Linux ARM and Encrypted Root

Until our Raspberry Pi 5 is fully supported by Arch Linux ARM we can get it running by "... removing U-Boot and replacing the mainline kernel with a directly booting kernel from the Raspberry Pi foundation". --source

This guide boots an encrypted root Arch Linux ARM on an SSD connected to a Raspberry Pi 5. The root partition is automatically unlocked from a key file on a USB key or you can enter the password with a keyboard. While it's not covered here, adding unlock via SSH should be the same as any other Arch install. Hopefully.

If you're looking for a guide to install to a SD card instead of the SSD you could try the incomplete notes section at the end of this file to build on a host that's not a Pi.

Tested with ArchLinuxARM-rpi-aarch64-latest.tar.gz from 2024-03-27.

Inspiration

These excellent guides:

Hardware

  • Raspberry Pi 5
  • SD card
  • NVMe connected via PCIe (tested with Pimoroni NVMe Base)
  • SSD (tested with Solidigm P44 Pro, 512GB)
  • USB key (to hold the encrypted root key file)

Preamble

We'll need Raspberry Pi OS Lite installed on the SD card to kick things off. They have a great install guide. I used the Raspberry Pi Imager so I could click buttons and add my public SSH key to the image easily.

Boot the Pi from the SD card and connect via SSH.

Run the following commands as root.

sudo su -

Install

We'll need to change the boot order to boot from NVMe and you can learn all about NVMe SSD booting from Jeff. Thanks Jeff.

My preferred order is 0xf461 which corresponds to (and is read from right to left):

  • 1 First, SD card (which will only used to rescue the system in the future - otherwise you'll have to disconnect the SSD to boot from an SD card)
  • 6 Second, NVMe
  • 4 Third, USB

Edit EEPROM:

rpi-eeprom-config --edit

BOOT_ORDER=0xf461
PCIE_PROBE=1

(Optional) For the lowest power state on halt because my Pi doesn't have a HAT and doesn't use GPIO pins:

POWER_OFF_ON_HALT=1
WAKE_ON_GPIO=0

Install required packages:

apt update
apt install libarchive-tools cryptsetup curl

# cryptsetup for luks encryption
# libarchive-tools for bsdtar
# curl for ... curl

Download Arch Linux ARM:

curl -L -o alarm.tar.gz 'http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-aarch64-latest.tar.gz'
curl -L -o alarm.tar.gz.sig 'http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-aarch64-latest.tar.gz.sig'

Verify file signature:

gpg --keyserver 'hkp://keyserver.ubuntu.com' --keyserver-options auto-key-retrieve --verify 'alarm.tar.gz.sig'

Mount the USB key and create a key file. My USB key already formatted as ext4:

mkdir -p /media/usb/
mount /dev/sda1 /media/usb/
dd bs=512 count=4 if=/dev/random of=/media/usb/unlock.key iflag=fullblock

Partition the target SSD with 2 partitions (Arch's install guide has a section on partitioning):

  • 512MiB boot: /dev/nvme0n1p1
  • Remaining space for root: /dev/nvme0n1p2

Format boot device:

mkfs.vfat -F 32 /dev/nvme0n1p1

Decide on your --pbkdf-* parameters:

By default cryptsetup will benchmark your host and use a memory-hard PBKDF algorithm that can require up to 4GB of RAM. If these settings exceed your Raspberry Pi's available RAM, it will make it impossible to unlock the partition. To work around this, set the --pbkdf-memory and --pbkdf-parallel arguments so when you multiply them, the result is less than your Pi's total RAM. --source

Setup luks and format root partition:

cryptsetup luksFormat --pbkdf-memory=1024000 --pbkdf-parallel=1 /dev/nvme0n1p2
cryptsetup luksAddKey /dev/nvme0n1p2 /media/usb/unlock.key
cryptsetup --key-file /media/usb/unlock.key open --type luks /dev/nvme0n1p2 root
mkfs.ext4 -m 1 /dev/mapper/root

Make a note of these device's UUID, we'll need them later (the SD card, mmcblk0, can be ignored):

lsblk -f

# NAME       FSTYPE UUID
# nvme0n1
# ├─nvme0n1p1
# │          vfat   aaaa-aaaa                            <- boot
# └─nvme0n1p2
#            crypto bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb <- encrypted root
#   └─root   ext4   cccccccc-cccc-cccc-cccc-cccccccccccc <- unlocked root
# sda
# └─sda1     ext4   dddddddd-dddd-dddd-dddd-dddddddddddd <- usb key

You can copy this text and fill it out unless your super power is memorizing UUIDs:

aaaa-aaaa = boot =
bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb = encrypted root =
cccccccc-cccc-cccc-cccc-cccccccccccc = unlocked root =
dddddddd-dddd-dddd-dddd-dddddddddddd = usb key =

Mount root device:

mount /dev/mapper/root /mnt/

Extract Arch Linux ARM's files:

bsdtar -xpf alarm.tar.gz -C /mnt/

Mount boot:

mount /dev/nvme0n1p1 /mnt/boot

Setup the chroot:

mount -t proc none /mnt/proc/
mount -t sysfs none /mnt/sys/
mount -o bind /dev /mnt/dev/
mount -o bind /dev/pts /mnt/dev/pts/

mkdir -p /mnt/run/systemd/resolve/
touch /mnt/run/systemd/resolve/resolv.conf
mount --bind /etc/resolv.conf /mnt/run/systemd/resolve/resolv.conf

Enter the chroot:

LANG=C chroot /mnt/ /bin/bash

Normal Arch init things (see the Pi 4 install guide):

pacman-key --init
pacman-key --populate archlinuxarm

Update the system and swap kernels:

pacman -R linux-aarch64 uboot-raspberrypi
pacman -Syu --overwrite "/boot/*" linux-rpi-16k

# Some packages provide hints on actions to take and you'll have to decide which ones need attention.
# mkinitcpio probably failed, we'll fix that soon.

Update your localization otherwise mkinitcpio fails:

vi /etc/locale.gen

# enable C.UTF-8
# enable your locale
vi /etc/locale.conf

LANG=<your locale>

Generate locales:

locale-gen

You might as well update your time zone while you're here:

ln -sf /usr/share/zoneinfo/Region/City /etc/localtime

Update mkinitcpio hooks:

vi /etc/mkinitcpio.conf

# Update HOOKS to add encrypt.
# Update HOOKS to remove kms (my Pi froze on boot with it enabled).
# Update HOOKS to remove microcode (not supported on this platform).
# Maybe update MODULES if your usb key uses a file system that isn't ext4.

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

Update fstab:

vi /etc/fstab

# <file system> <dir> <type> <options> <dump> <pass>
UUID=cccccccc-cccc-cccc-cccc-cccccccccccc / ext4 rw,relatime 0 1
UUID=aaaa-aaaa /boot vfat rw,relatime 0 0

Update linux command line by replacing root=/dev/mmcblk0p2:

vi /boot/cmdline.txt

# Replace root=/dev/mmcblk0p2 with this (and update the cryptkey file system parameter, ext4, to the file system of your usb key):
cryptdevice=UUID=bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:root cryptkey=UUID=dddddddd-dddd-dddd-dddd-dddddddddddd:ext4:/unlock.key root=/dev/mapper/root

Your full command line should look something like this:

cryptdevice=UUID=bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:root cryptkey=UUID=dddddddd-dddd-dddd-dddd-dddddddddddd:ext4:/unlock.key root=/dev/mapper/root rw rootwait console=serial0,115200 console=tty1 fsck.repair=yes

(Optional) Update Pi's configuration:

vi /boot/config.txt

# I'll be disabling things I don't need:
# - vc4-kms-v3d (try disable this if your Pi freezes on boot)
# - wifi
# - bluetooth

If you're looking to unlock using SSH, now is the time.

Run mkinitcpio:

mkinitcpio -P

# You should see: Initcpio image generation successful
# Without this image your Pi won't boot.

Add your public SSH key to root:

mkdir -p /root/.ssh && chmod 0700 /root/.ssh
echo "ssh-ed25519 [...]" | tee /root/.ssh/authorized_keys
chmod 0600 /root/.ssh/authorized_keys

(Optional) My Pi will be headless so I'll remove the default user that has an obvious password and disable root's password because I'll only be connecting via SSH:

userdel -r alarm
usermod -p '!' root

(Optional) Periodic pacman cache cleaner:

pacman -S pacman-contrib
systemctl enable paccache.timer

(Optional) Update your hostname:

vi /etc/hostname

Exit the chroot:

sync
history -c && exit

Power off the Pi which is easier than unmounting all the file systems by hand:

poweroff

Real life steps:

  1. Remove power
  2. Remove the SD card
  3. Apply power
  4. Wait
  5. Connect to your Pi using SSH as root

Enjoy!

Future

"When Arch Linux ARM starts supporting the Pi 5, the Pi Foundation’s kernel can be replaced with the mainline kernel by running: pacman -Syu linux-aarch64 uboot-raspberrypi

There will be warning that those packages conflict with package linux-rpi and whether you want it replaced. If you do, linux-rpi will be removed before installing the new packages. After that, your Arch Linux ARM installation should be the same as the official Arch Linux ARM Raspberry Pi image that supports the Pi 5."

--source

Troubleshooting

Unfortunately, plugging the Pi into a monitor and looking at the screen is the easiest debug method.

You might need to wait slightly longer for the Pi to boot due to unlocking the root partition.

Still nothing?

Power off, insert the SD card into the Pi, boot and SSH. Unlock and mount partitions then enter the chroot and start pressing buttons. Double check your UUID's and cmdline.txt. Perhaps you missed the UUID= when referencing the disk?

Maybe you'll need to remove the kms minitcpio HOOKS. Maybe you'll need to disable vc4-kms-v3d in the config.txt.

Miscellaneous Notes

You can build the Arch Linux ARM image on a Arch host computer, like x86_64 or in a VM (basic image, QCOW2). You can probably use other linux distro's with the right packages installed.

Install packages required for the chroot:

pacman -S qemu-user-static qemu-user-static-binfmt arch-install-scripts

Create files to temporarily hold the file systems:

fallocate -l 512MiB pi-boot
fallocate -l 4GiB pi-root

Mount the files as devices:

losetup -f pi-boot
losetup -f pi-root

Check which file was assigned to which loop device:

losetup -l

From there you can install to the loop devices.

You can transfer your boot and root partitions from the loop devices to a real device using:

rsync --archive --hard-links --acls --xattrs --one-file-system --numeric-ids --info="progress2" /mnt/src//* /mnt/dest/