For the last couple of years, I’ve carried a couple of USB drives with me: one with a LUKS encrypted volume holding GPG keys, an Arch Linux LiveUSB system and some other useful things, and one holding the /boot partition and LUKS header for decrypting and booting up my laptop. I’ve been meaning to merge the two into one, and finally got around to doing it. This post is the first in a series of three where I will lay out what I wanted to do, and how I did it.

I call the project - and the result thereof - Fulla, after the Norse goddess who carries the goddess Frigg’s ashen chest and in whom Frigg confides her secrets.

The result

At the end of this series of posts, I will have shown you how to move your /boot partition and, optionally, LUKS headers to en encrypted USB storage device, making it a USB key in the true sense of the word. You can share this /boot between multiple systems and use the same bootloader as a gateway to all of them, and also keep a bootable Linux system on the same drive.

Let’s start off with how I’ve done this and what this allows me to do.

Design

My setup looks like this. The USB key consists of three major parts:

  1. an Arch Linux system installed on LVM on top of a LUKS encrypted partition, whose /boot partition is used as the shared /boot for other systems as well,
  2. a separate LUKS encrypted partition for sensitive data - which will allow usage of the embedded system without exposing the sensitive contents - and
  3. GRUB with multi-boot configuration, including the embedded system.

With this setup, I’ve moved the /boot partition and the full disk encryption LUKS header for my laptop to the USB key. In this way the USB key is required for unlocking the laptop, making it a kind of 2-factor authentication. I also always have a bootable Linux system with me, which I can use for recovery when I break one of my systems or as a makeshift airgapped machine where I can use my GPG master key. And, since the whole thing is encrypted, I can be reasonably assured that noone can tamper with the USB key if I leave it out of sight for a while.

How to

The procedure is pieced together from various articles on the Arch Wiki, with this article by Pavel Kogan as a major guideline.

Throughout the steps below, /dev/sde will refer to the USB key. If following these steps for your own setup, adjust the paths accordingly. I’m also writing this as an Arch Linux user, but the procedure should be applicable to other distributions as well.

Step 0: Secure wipe

First off, let’s do a secure wipe of the drive - that is, fill it with random data. An easy way to do this is to use plain dm-crypt encryption as a pseudo-random generator:

# cryptsetup open --type plain /dev/sde container --key-file /dev/random
# dd if=/dev/zero of=/dev/mapper/container status=progress

This will take a while. For my 8 GB USB 2.0 drive it took somewhere around 30 minutes.

Step 1: Partitioning and encryption setup

With our shiny new wiped drive, it’s time to get started. I want to use GPT for no particular reason other than newer is cooler, and I also want to be able to boot the system on my BIOS-only EEEPC netbook, so that means a BIOS boot partition is required. That means I’ll have four partitions on my 8 GB (7.2 GiB) drive:

  1. /dev/sde1: BIOS boot partition: 1 MiB
  2. /dev/sde2: /boot and Linux system partition (LVM): 4 GiB
  3. /dev/sde3: Sensitive data partition: 1 GiB
  4. /dev/sde4: Unsensitive general purpose storage: ~2.2 GiB (leftovers)

Use your favourite partitioning tool to set these up; I used gdisk. Note that if you need the BIOS boot partition you’ll need to set the correct partition type, which is EF02 in gdisk.

Next, we’ll set up LUKS encryption headers. I’ll simply go with the cryptsetup default settings - they should be good enough if you have a recent version of cryptsetup, which you probably do if you run Arch. Here you’ll need to come up with two strong passphrases for the two encrypted volumes.

# cryptsetup luksFormat /dev/sde2
# cryptsetup luksFormat /dev/sde3
# cryptsetup luksOpen /dev/sde2 fulla
# cryptsetup luksOpen /dev/sde3 eskir

Next, we need some filesystems:

# pvcreate /dev/mapper/fulla
# vgcreate fulla /dev/mapper/fulla
# lvcreate -L 500M -n boot fulla
# lvcreate -L 2G -n root fulla
# lvcreate -L 1G -n home fulla

# mkfs.ext3 /dev/mapper/fulla-boot
# mkfs.ext4 /dev/mapper/fulla-root
# mkfs.ext4 /dev/mapper/fulla-home

# mkfs.ext4 -L "Removable storage" /dev/mapper/eskir

So, this is what the setup looks like at the end of step 1:

# lsblk -f
NAME                  FSTYPE      LABEL           UUID                                   MOUNTPOINT
sde
├─sde1
├─sde2                crypto_LUKS                 221710ed-d6d3-4b83-bcc5-ef5307eadf21
│ └─fulla             LVM2_member                 j40SO3-6xJ1-IOXH-58C7-lpNL-Zrqg-WKViEz
│   ├─fulla-boot      ext3                        f5909d93-e2f6-46ee-bd11-592bbea5f5fd
│   ├─fulla-root      ext4                        7994b2a9-6d69-4bb7-baf4-3bba6b86b7eb
│   └─fulla-home      ext4                        bd4e1b21-cdda-4995-be76-a1c93386bd67
├─sde3                crypto_LUKS                 e2990758-6a2b-40e8-957c-a6a4ee46d291
│ └─eskir             ext4                        7764923a-9fa9-46ec-a721-cdc2c8dd3ff2
└─sde4                ext4        Removable storag b4187b5f-549c-49c1-818f-9a110b44f41e

In the next section I’ll go through installing Arch Linux on the fulla partition. Refer to your distribution’s documentation if you want another distro, or feel free to skip the section if you don’t need a bootable Linux system on the USB key.

Step 2: Install Arch Linux

This part is pretty straightforward: I simply follow the usual Arch Linux installation guide, keeping in mind the tweaks mentioned in Installing Arch Linux on a USB key. At this time I won’t bother disabling the ext4 journal, though, since I don’t plan to put the system to heavy or even daily use.

Some notable deviations from the usual installation instructions are:

  • I leave network configuration out, since the main intended use of the system is as a makeshift airgapped environment for working with the sensitive data. I would probably be better off with Tails, but right now I’m more interested in just getting this to work at all and don’t want to spend the time getting acquainted with a new distro.
  • I hold off building the initramfs (mkinitcpio) until I’ve configured it to work as I need, as explained in the next subsection.

For the remainder of this section, we’ll be working in the chroot environment, with / as the filesystem root of the embedded system.

Configure GRUB and mkinitcpio

There are a few things that need to happen with mkinitcpio.conf for the embedded system:

  • As documented on the Arch wiki, we need the block hook right after the udev hook.
  • We need the lvm2 and encrypt hooks for our LVM on LUKS setup.

As for GRUB, we need to tweak the configuration to allow use of encrypted /boot and /root filesystems. This is excellently documented in Pavel Kogan’s article. What we need is the following settings in /etc/default/grub:

GRUB_CMDLINE_LINUX="cryptdevice=UUID=221710ed-d6d3-4b83-bcc5-ef5307eadf21:lvm"
GRUB_ENABLE_CRYPTODISK=y

As you can see, the UUID here is the one for the /dev/sde2 device in the lsblk output in the previous section. Also, to save us the hassle of having to input the passphrase twice - once for GRUB and once for the Linux kernel - when booting the embedded system, we’ll set up a keyfile in the encrypted /boot partition which the kernel will use to unlock the encrypted volume. Again, these steps are taken from Kogan’s article.

# dd bs=512 count=4 if=/dev/urandom of=/boot/keyfile-fulla.bin
# cryptsetup luksAddKey /dev/sde2 /boot/keyfile-fulla.bin

Configure mkinitcpio to include the keyfile in the initramfs by adding it to FILES in mkinitcpio.conf:

FILES="/boot/keyfile-fulla.bin"

Lastly, set the cryptkey kernel parameter in /etc/default/grub:

GRUB_CMDLINE_LINUX="cryptdevice=UUID=221710ed-d6d3-4b83-bcc5-ef5307eadf21:lvm cryptkey=rootfs:/boot/keyfile-fulla.bin"

The rootfs in the cryptkey value is a magic string that makes the encrypt hook resolve the part after the : as within the initramfs, as opposed to resolving the part before : as a block device to use as the root.

With that, we’re all set. Time to build the initramfs and install GRUB:

# mkinitcpio -p linux
# grub-mkconfig -o /boot/grub/grub.cfg
# grub-install --target=i386-pc /dev/sde

Next steps

With that we now have a couple of encrypted volumes and, optionally, an embdedded Linux system set up on the USB key, ready to be filled with /boot files and LUKS headers from other systems. The next few steps will get a bit more involved, as we’ll need to modify the encrypt hook and hand-edit the GRUB configuration. This will all be covered in the next post.