Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save intentionally-left-nil/b2e4e57274d2173ca488066725287d0f to your computer and use it in GitHub Desktop.
Save intentionally-left-nil/b2e4e57274d2173ca488066725287d0f to your computer and use it in GitHub Desktop.
Building a Secure Arch Linux Device

Building a Secure Arch Linux Device

Locking down a linux machine is getting easier by the day. Recent advancements in systemd-boot have enabled a host of features to help users ensure that their machines have not been tampered with. This guide provides a walkthrough of how to turn on many of these features during installation, as well as reasoning for why certain features help improve security.

The steps laid out below draw on a wide variety of existing resources, and in places I'll point to them rather than attempt to regurgitate full explanations of the various security components. The most significant one, which I highly encourage everyone to read, is Rod Smith's site about secure boot, which is the most comprehensive and cogent explanation of UEFI, boot managers and boot loaders, and secure boot. Another incredibly useful resources is Safeboot, which encapsulates many of the setup steps below in a Debian application. Finally, the Arch Wiki pages for Secure Boot, disk encryption, and systemd-boot. Many of the decisions to use certain technologies were inspired by Matthew Garret's Producing a trustworthy x86-based Linux appliance.

How best to use this guide

Using Linux is designed to be an empowering and enriching experience. Therefore, I'd like to issue a word of caution about blindly following this gude (no judgment though, I do it too). At many points in this guide there will be a series of steps with fairly complex commands, but I urge you to pause and think about each one. It may be slow and painful, and it will definitely require some googling, but if you don't proceed with intent it's possible to end up with a system that has and does a bunch of stuff that you don't understand. As this is a guide about security, that's, like, bad. Not that you should know how every intricate detail of Arch Linux, UEFI, TPMs, etc. work; I'm pretty sure no one does. But you should at least know how to find out if you come across something that is causing you trouble. It's a hard-earned skill, but you can get almost all of the way there by just paying close attention. I'll try to tip you off to when you should pay closer attention where I can, but neither of us is perfect, so make your best effort and I'll make mine.

I'm getting off the high horse now and getting on with the show.

Table of Contents

  1. [Getting Started](#Getting Started)
  2. [After the first boot](#After the first boot)
  3. [Setting up Secure Boot](#Setting up Secure Boot)
  4. [Using systemd-cryptsetup](#Using systemd-cryptsetup)
  5. [Using TPM2-TOTP](#Using TPM2-TOTP)
  6. [Working with dm-verity](#Working with dm-verity)

Getting Started

The first step is to install Arch normally, following the installation guide, up to partitioning the disks. You will likely have to disable Secure Boot in your device's firmware before you can boot the archiso USB stick, which is good practice for later on.

Much of this guide's format and content was stolen from @OdinsPlasmaRifle's really excellent guide. Where possible I've tried to expound a bit on why we're doing certain things, as well as modifying the guide to include additional steps that will make later implementing security features a little easier.

Setting up the partitions

From the archiso, run gdisk. For me, it looks like this:

gdisk /dev/nvme0n1

Wipe out the existing partition table, and add an EFI system partition (ESP) in the first 512 MB on the disk:

o
n
(Enter)
(Enter)
512M
ef00

This partition will contain the bootloader, and will eventually be mounted at boot. Now create the LVM system partition, which will take up the whole rest of your disk. This partition will eventually be encrypted via LUKS2 and can contain subpartitions with different read/write permissions.

n
(Enter)
(Enter)
(Enter)
8e00

After you're done, printing the GPT should show the following (though your partition sizes will likely be different; the table below was on a 120GB nvme drive):

Number  Start (sector)  End (sector)    Size      Code  Name
   1        2048           1048576    511.0 MiB   ef00  EFI system partition
   2      10506224        250069646   118.7 Gib   8e00  Linux LVM

Write the partition table to the disk:

w
Y

Now format the ESP (for compatability reasons, it must be FAT32 format):

mkfs.fat -F32 /dev/nvme0n1p1

Encrypting the disk

The next step is to set up disk encryption. Depending on your application, an encrypted disk may be unnecessary (we'll see a way later on that will still verify the contents of the disk even if they aren't secret), but for most applications it's a good idea.

First, make sure the dm-crypt module is loaded:

modprobe dm-crypt

Encrypt the disk using cryptsetup:

cryptsetup luksFormat /dev/nvme0n1p2

(Remember that the second partition on our disk is the LVM partition. I'm using an NVMe disk, but if you're running a different style of disk the preceding information may be different, e.g. your partition may be something like /dev/sda2)

Running this will prompt you to make sure you're ready to encrypt the disk. Type YES at the prompt to proceed. Next, you will be prompted to set a disk encryption passsword. This password is one of two security critical passwords on the device, so make it a good one (remember, length is better than character variation). Additionally, DO NOT FORGET THIS PASSWORD. Doing so will render your machine completely unsuable, and you will lose all data stored on the disk. Mitigate this risk by choosing a memorable (but also unique not easily guessable) password, and performing frequent backups of your data).

Once you have set your password, the disk is now encrypted and we are ready to begin setting up our LVM partitions. Start by decrypting the disk you just encrypted:

cryptsetup luksOpen /dev/nvme0n1p2 cryptlvm

cryptlvm is a label that you can subsequently use to reference the now-decrypted disk. LUKS mounts the disk in the dev/mapper folder, which is also where our LVM partitions will be mounted. Make sure the disk was decrypted and mounted properly:

ls /dev/mapper/cryptlvm

Configuring LVM

LVM stands for Logical Volume Manager. For historical reasons I don't really understand, physical volumes, i.e. real hard disks, and logical volumes, software-managed "disks" that may or may not correspond to physical disks are not usually easy to manage. Logical volumes may take up only part of one physical disk, or may span multiple physical disks (akin to RAID0), so the filesystem needs an intelligent way of keeping track of which partitions/filesystems live on which disks. LVM does all of this abstraction for you. At least, that's my understanding, I'm not super well informed on LVM. I just know that it works for our purposes here.

First, we tell LVM that there is a physical volume on our LVM partition (the disk itself knows that this partition exists; it doesn't know about the logical partitions we create next, that's what LVM manages).

pvcreate /dev/mapper/cryptlvm

Now we create a volume group, which is how LVM keeps track of which logical volumes are associated with a physical volume:

vgcreate volume /dev/mapper/cryptlvm

Now that we have a logical volume, tell LVM about our new logical partitions, starting with swap:

lvcreate -L4G volume -n swap

(Note that the size of this partition is up to you. General guidelines is something like half of RAM, but disks are so large these days in comparison to RAM that I've seen swap partitions as large as 4-5x the RAM. In case you're wondering, swap is used by the OS when RAM starts to get full. The OS can flush parts of RAM that aren't being used right this second to disk, effectively increasing the RAM size a little bit with a performance hit (which depends on the technology of disk you have; SSDs are pretty quick!)

Next we create a root partition. This corresponds to the / directory in Unix-flavored systems, and this presents another opportunity to implement a security feature. Taking a note from Safeboot's spin on System Integrity Protection (SIP), we can choose a smaller root partition that will later on enable us to hash it using the dm-verity module. Doing so will allow us to extend the tamper-evidence that Secure Boot provides up into the operating system, as if the hashing on the root partition doesn't match an expected value, we will know that it may have been tampered with. Additionally, setting this partition to read-only prevents some attacks that rely on modifying the kernel.

The downside to choosing a smaller root partition and mouting it as read-only is that it can make updating the system tricky. It also means we'll need more partitions over all, as we'll have to split out many folders that are traditionally kept on the root partition, like /var, as these partitions contain logging data that by definition needs to be writeable.

Mounting your root partition at read-only is probably overkill for most applications. It's probably only a good idea you are setting up a machine that is not likely to receive updates frequently and needs a higher degree of protection. Doing this in Arch Linux is therefore not a great move, as the rolling update cycle will cause paine with a read-only root partition. Other distros, like Debian and Ubuntu, are less likely to have issues with this as they release more slowly and all at once. This is why Safeboot works with Ubuntu (in addition to it being a popular distro).

If you are not planning to later add SIP, create a root partition that is larger than the one prescribed below (20G is probably enough, but 40G guarantees you'll never run out of space).

lvcreate -L12G volume -n root

If you are creating a read-only root partition, make sure to include a partition for /var (this may be a good idea for other reasons too):

lvcreate -L60G volume -n var

Finally, create a home partition, where user data will be stored, with the rest of the space on the disk:

lvcreate -l 100%FREE volume -n home

Once you're done, you can run lvdisplay to check your work. [TODO: add what mine looks like?]

Making the filesystems

Now that we've created our logical volumes, we need to initialize file systems on each of them. The choice of file system is up to you. It's hard to go wrong with ext4, but more recent systems like ZFS or BTRFS may offer better data reliability options.

mkfs.ext4 /dev/volume/root
mkfs.ext4 /dev/volume/var
mkfs.ext4 /dev/volume/home

Also don't forget to setup the swap partition:

mkswap /dev/volume/swap

Now we can mount our file systems and proceed with installation!

mount /dev/volume/root /mnt
mkdir /mnt/home
mkdir /mnt/var
mkdir /mnt/boot
mount /dev/volume/home /mnt/home
mount /dev/volume/var /mnt/var
mount /dev/nvme0n1p1 /mnt/boot
swapon /dev/volume/swap

Installation

Bootstrap Arch onto our newly created filesystem, and install utilities:

pacstrap /mnt base base-devel linux linux-firmware lvm2 vim

(This may take a while depending on your network connection. I have the pleasure of doing this on a Lenovo Flex 14 with really shoddy wifi drivers, which means its taking even longer than usual.)

Once that's done, generate fstab, the file used by the operating system to setup the disks at boot time:

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

Now it's time to chroot into system. This basically lets you walk around inside the environment we've spent the last however many minutes setting up, and spruce up the place before you boot off the disk for real.

arch-chroot /mnt

Set time locale (choose a relevant locale):

ln -sf /usr/share/zoneinfo/Africa/Johannesburg /etc/localtime

Set clock:

hwclock --systohc

Uncomment en_US.UTF-8 UTF-8 en_US ISO-8859-1 and other needed localizations in /etc/locale.gen. This is really important, as it is needed by most programs to figure out what language localizations to run with. This includes not just the English (or your preferred language), but also the programming languages, like the version of C supported by your system.

locale-gen

Create locale config file:

locale > /etc/locale.conf

Set the lang variable in the above file:

LANG=en_US.UTF-8

Add an hostname (any hostname of your choice as one line in the file. eg. myhostname). This is the name of your device, that will be displayed at login and shouted at you through your bluetooth headphones when you connect, so pick something good!

vim /etc/hostname

Update /etc/hosts. This overrides DNS settings when your machine does name resolution, so adding the following lines will ensure that whenever a program tries to connect to localhost it gets the right IP address (in this case, the loopback address). I'm only including an IPv4 address. If you need IPv6, or more complicated settings, godspeed.

127.0.0.1   localhost

Because our filesystem is on LVM we will need to enable the correct mkinitcpio hooks. These allow the initcpio, Arch's initial RAM file system that gets loaded into ram on boot, to include programs that can decrypt our hard drive.

Edit the /etc/mkinitcpio.conf. Look for the HOOKS variable and move keyboard to before the filesystems. If you don't do that, you may not be able to type in your password! Additionally, add sd-encrypt and lvm2 after keyboard. sd-encrypt is systemd-boot's hook, which will enable us to use systemd-cryptsetup later on). You can just use encrypt if you don't plan on using systemd-cryptsetup and are content to just enter a password on boot up every time (See the discussion below for why that may be the more secure thing to do depending on your needs).

HOOKS="base udev autodetect modconf block keyboard sd-encrypt lvm2 filesystems fsck"

If you're running an Intel-based system, you may also need to add i915 to the MODULES field. Google it.

Regenerate the initramfs (the -p flag here specifies the name of the image you're regenerating. To regenerate all of them, use -P)

mkinitcpio -p linux

Install the systemd-boot bootloader. Note that if you did something other than mount your boot partition at /mnt/boot above, you may need to pass the --path flag to tell bootctl where your ESP is.

bootctl install

Create the bootloader. Edit /boot/loader/loader.conf. Replace the file's contents with:

default arch
timeout 0
editor 0

The editor 0 ensures the configuration can't be changed on boot.

Next create a bootloader entry in /boot/loader/entries/arch.conf

title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options cryptdevice=UUID={UUID}:cryptlvm root=/dev/mapper/volume-root quiet rw

Replace {UUID} with the UUID of your LUKS drive. You can get it by running blkid /dev/nvme0n1p2, and I prefer piping this output into the bootloader entry file and editing it from there:

blkid /dev/nvme0n1p2 >> /boot/loader/entries/arch.conf

There are a few more things to explain here. The bootloader entry tells systemd-boot what files to load for this entry (the linux and initrd, i.e. "INITial Ram Disk"), and it passese options to the kernel command line through the options entry. These paramters, visible from the /proc/cmdline are basically parameters like any other program that tell the kernel to do certain things. Obviously the cryptdevice tells the kernel (and by extension the disk encryption kernel module) that the disk with that UUID is encrypted. The root parameter tells the kernel where the root file system is once the cryptdevice is decrypted. quiet tells the kernel not to show a bunch of the logging that happens on boot (you saw what this looks like when you booted from the thumb drive). splash tells the kernel to boot with a splash screen if there is one, and rw tells the kernel to mount the root partition in read/write mode.

We could go ahead and finish the rest of our security setup below now, in which case this last parameter could be set to read-only. However, you're reading this guide, which means you may not know how to do this stuff. Putting off marking root as read-only gives us wiggle room to experiment later.

Important: before exiting the chroot, you need to set a root password by typing passwd. You can also optionally add a non-root user, or you can wait until reboot to do so. Failing to set a root password will get you stuck at the login prompt once booted, and unable to do anything else. I typically also create my user account at this stage (the -m creates a home directory):

useradd -m matt 
passwd matt

Important before reboot, you probably also need to install a network manager. I'm choosing the NetworkManager package because it's used by GNOME, but any other works too. Without network access on reboot you won't be able to download any new packages. I usually just go ahead and install GNOME at this point while in the chroot, so that on the next bootup I get a nice graphical login.

pacman -S gnome

Reboot

With that, we're done with installation! Now we'll exit the chroot and reboot.

exit
reboot

In theory you should be good to go now. However, there's a good chance that things didn't go as planned and now you're either in a weird state (I was after I finished a trial install to write this, so you're in good company). This probably means you just typo'd something above. Don't freak out. Read whatever error message you're seeing and try googling it. Then, reboot back into the archiso. You can unlock and remount your file system and chroot into it just as we did when we set it up (though you don't have to do all the LVM configuration again). You can fix whatever may be wrong and then reboot again to see if you figured it out. It usually takes me a few iterations of that process to get things right, but don't give up. This is one place where Arch is significantly less fun that other distros with nice installers. But hey, you wanted bleeding edge, you get the bleeding.

After the first boot

Congrats! You've now setup and Arch Linux system! What's that? You don't like just having a black terminal for an operating system?

This section isn't related to security setup, but it will make your quality of life better. I'm assuming you're a human being, and you have probably used computers before, which means it might be nice to have a GUI. There's a lot of ways to accomplish this on Arch, but my preferred way is the GNOME desktop environment. People say it's bloated, and that's sort of true, but it does all the things I need it to do and as of yet I haven't found another desktop environment (more commonly abbreviated DE) that does it better. Checkout Reddit or the Wiki if you want to mess around with DEs and window managers (WMs) yourself.

Depending on your choice of DE and your device, you may have to fight with getting graphics, mouse, fingerprint reader, and other drivers loaded. Trying to put all of that stuff in this guide would make it a book, so I'm going to skip it and tell you to rely on the Wiki, Reddit, StackOverflow, and other excellent Internet resources.

There are only a few things I'm going to recommend. The first is and Arch User Repository (AUR) manager. I like yay because it's fun to welcome packages onto my system with enthusiasm. Google around if you're more dour.

I'm also going to recommend Plymouth, which is a splash screen manager for the boot process. The only reason I recommend it is because one of the security features we're going to add later, TPM2-TOTP, has an integration for it that displays nicely with it (it also provides a more aesthetically pleasing way to enter your disk encryption password, but oh my god, who cares). If you're not doing that step, I'd skip Plymouth altogether and keep your boot process faster. Make sure you follow the instructions for the systemd-boot hooks specifically (e.g. add sd-plymouth to your mkinitcpio.conf HOOKS variable instead of just plymouth).

One other note about Plymouth: if you aren't seeing it display properly, it's possible that it's because your machine is too fast for it to work right. Confusingly, setting the ShowDelay variable to 0 in /etc/plymouth/plymouthd.conf ought to fix that.

Setting up Secure Boot

Now we are ready to implement Secure Boot. I want to stress again that I highly recommend Rod Smith's super comprehensive explanation of what UEFI and Secure Boot are and how they work. I will provide a breif recap here and dive into setting up Scure Boot.

Secure Boot is a feature of the Unified Extensible Firmware Interface (UEFI), which we've already been interacting with on the down low. UEFI is basically a standard set of features that device firmware can implement (including a first-stage bootloader), and Secure Boot is one of its features. UEFI firmware has a notion of EFI executables that can be run after the firmware gets initialized (if you've ever mashed F12 repeatedly after pushing the power button to launch a live USB, the menu that comes up to let you select the live image is a list of EFI executables that the firmware has found.

Systemd-boot is a specific kind of EFI executable called a boot manager (also referred to as a second-stage boot loader), that in turn has a more sophistacted set of features for deciding what programs get control on boot. In principle, with the Linux kernel's EFISTUB implementation, you could just launch the kernel directly from UEFI, but this offers less flexibility (you can only boot one thing, whereas with a boot manager you can set multiple options, i.e. a regular image, a recovery image, rebooting into the firmware, etc). Additionally, using a boot manager gives us the ability to use software at boot time to do things like decrypting the hard disk, interacting with the TPM, and measuring the firmware, boot manager, and kernel to make sure nothing has changed unexpectedly.

Generating keys

Secure boot works by generating a set of signing keys which are used to cryptographically sign EFI executables so that only signed executables can be run via a UEFI implementation with Secure Boot turned on. Secure Boot relies on two different keys, a Platform Key (PK) and a Key Exchange Key (KEK). I'm not going to go into depth here, again check out Rod Smith's site, but briefly the PK is associated with your platform (usually the TPM on the motherboard), the KEK is used to sign a trusted list of signatures and keys (referred to as the database or db), and also to sign a ban list of signatures and keys not trusted by the machine owner (dbx). There are also pther auxiliary keys like Machine Owner Keys (MOKs) that came about as a way for one signing authority to delegate others, but again that's not really important to our discussion here.

To generate the keys and signatures we need to get Secure Boot working, I recommend just pulling down Rod Smith's script, described here and wget'able here. You also need the efitools package.

sudo pacman -S efitools
sudo mkdir /etc/efi-keys
cd !$
sudo wget https://www.rodsbooks.com/efi-bootloaders/mkkeys.sh
sudo chmod +x mkkeys.sh
sudo ./mkkeys.sh

The script will ask you to enter a Common Name, part of the X509 certificate standard. You can just put in whatever you'd like.

Generating and signing a unified EFI image

Now that we have signing keys, it's time to sign. I really like the sbupdate tool, which makes it seamless to generate a unified kernel and bootloader image as well as sign it. Installing sbupdate also installs pacman hooks which automatically remake and sign the unified image whenever the system is updated, so you don't have to do it manually every time. Of course, the usual caveats about choosing which software to include in your trusted arsenal apply, but fortunately it's a quite small package that consists of a bash script and some hooks, so it's fairly easy to verify by eye.

yay -S sbupdate

sbupdate does require some configuration, as once it is installed it is the program responsible for setting the kernel command line, splash screen, and other things.

KEY_DIR="/etc/efi-keys"
ESP_DIR="/boot"
OUT_DIR="EFI/Arch"
#SPLASH="/usr/share/systemd/bootctl/splash-arch.bmp"
#BACKUP=1
EXTRA_SIGN=('/boot/EFI/BOOT/BOOTX64.EFI' '/boot/EFI/systemd/systemd-bootx64.efi')
CMDLINE_DEFAULT="cryptdevice=UUID={UUID}:cryptlvm root=/dev/mapper/volume-root quiet splash rw"

Note that the CMDLINE_DEFAULT variable contains the same thing as the options variable from our systemd-boot file above, so make sure {UUID} is replaced correctly as in that file. Alternatively, you can just cat /proc/cmdline >> /etc/sbupdate.conf from a root shell and adjust as needed. The EXTRA_SIGN variable tells sbupdate to also sign and include systemd-boot and the basic Linux EFI stub into the unified image.

Now, run the tool: sbupdate

There should now be a file linux-signed.efi in /boot/EFI/Arch that corresponds to our signed unified image. Now we have to edit our systemd-boot entry to use that image only. Open the /boot/loader/entries/arch.conf file in your editor of choice and change it to this:

title Arch Linux Signed
efi   /EFI/Arch/linux-signed.efi

Make sure to run sudo bootctl update to tell systemd-boot that there's a new boot image to load.

Configuring Secure Boot in firmware

At this point we have now generated keys and a unified kernel image, and signed it, but the firmware still doesn't know what keys to trust, so we have to tell it. Reboot into the firmware interface. If you'd like to avoid having to mash the F2 key on reboot every time, you can use bootctl to reboot into the firmware interface:

sudo bootctl set-oneshot auto-reboot-to-firmware-setup
sudo reboot

On reboot, if you haven't already, the first thing you should do is set an administrator password on your firmware interface. It's not much good configuring Secure Boot if someone can just go in and turn it off! As with your disk encryption password, do not forget this password! But also pick a good one that's unique and memorable. If you absolutely must, stash the password in a password manager that you trust.

After that, it's time to enroll the keys you generated earlier into the firmware. On some devices, like the Framework laptop, you can do this directly from the firmware interface. On the Secure Boot page, there are options for locating the PK, KEK, db, and dbx files on the boot partition.

If you aren't so lucky as to have an awesome firmware interface that can enroll keys, you'll have to use sbkeysync or KeyTools, instructions for which you can find on the Arch wiki. I've never had much luck with sbkeysync, but hopefully you will. Remember to set your firmware interface to Setup Mode before trying to use either.

If you're using KeyTool, make sure to copy your keys over to /boot so that it can find them.

After the keys are enrolled, make sure to regenerate the initramfs and the unified kernel, and resign it.

sudo mkinitcpio -P
sudo sbupdate

Finally, turn on Secure Boot in the firmware interface and reboot. If you get into the OS, congratulations, you've successfully setup Secure Boot with keys only known to you! After this step, make sure to remove the keys from your /boot partition, as that partition isn't encrypted and will leak the keys to an attacker, which kind of defeats the point.

Using TPM2-TOTP

We now have Secure Boot setup! Wonderful! However, there is still potential for someone to tamper with the system undetected. An attacker can attach a microcontroller to the motherboard of the device and flash a new database of signed EFI executables, and then boot whatever they want. While we can't stop this attack, we can make it easier to detect using the Trusted Platform Module (TPM) that is resident on pretty much every x86 system produced in the last ten years (and even some non-x86 ones).

As part of the Secure Boot process, boot "measurements" are made and stored in the TPM, which provides secure registers that are difficult (though not impossible) to read without proper authorization. The registers are called platform configuration registers (PCRs), and each register contains certain data specified by the TPM standard. For example, PCR0 contains a hash of information about the device, including the firmware binary as well as a unique token stored in the TPM on the device. This means that PCR0 values are unique and cryptographically hard to fake. PCR values are also chained, so that the value in PCR1 contains a hash of new data (the specifics of which are not relevant here) plus the hash stored in PCR0. So on and so forth for the other PCRs. PCR7 contains a hash of the Secure Boot policy, including a hash of the databse of trusted keys, along with the hash chain, meaning that if we can have a reliable way to check its value we can make sure that our trusted databases have not been tampered with.

This is where TPM2-TOTP comes in. It's a utility that takes advantage of the TPM's ability to generate time-based one-time passwords (TOTPs) just like the ones used by multifactor authentication apps. The TPM generates a secret based on the current values of the PCRs and some other data and shares that data with the user via a QR code. The QR code can be used with any MFA app (I like Authy or Duo).

On boot, the TOTP is displayed at the screen where the user enters the disk encryption password, offering an opportunity to verify the state of the system before entering in a password that could potentially be sniffed by malicious software. If the TOTP doesn't match the one on the user's verification app, something has gone wrong and the system should not be trusted.

TPM2-TOTP can be found here. I recommend downloading the source and compiling it, as the AUR version may not correctly configure the hooks needed by systemd-boot to display the codes. Most of the depencies should already be installed, though you will need to grab the autoconf-archive package.

sudo pacman -S autoconf-archive tpm2-tss qrencode
git clone https://github.com/tpm2-software/tpm2-totp
cd tpm2-totp
./bootstrap
./configure --sysconfdir=/etc
make
sudo make install

Now generate a new secret:

sudo tpm2-totp --pcrs=0,7 init

Scan the QR code with your preferred authentication app, and then check that it worked:

sudo tpm2-totp show

Make sure tpm-totp appears in the HOOKS section of /etc/mkinitcpio.conf, then regenerate the initramfs, resign, and reboot.

sudo mkinitcpio -P
sudo sbupdate
sudo reboot

If the value you see is the one on your other device, things should be working. Regenerate the initramfs and resign, then reboot. At the password prompt, you should see the code, which you can verify.

Installing with Plymouth

If you would like a better looking version of this, install plymouth before installing TPM2-TOTP. The ./configure step should automatically detect that plymouth is installed and do the right thing. Note that you will have to add systemd sd-encrypt sd-plymouth sd-plymouth-tpm2-totp to your /etc/mkinitcpio.conf file's HOOKS section.

sudo mkinitcpio -P
sudo sbupdate
sudo reboot

I've managed to get this working on my Framework laptop, but not on the Lenovo I'm using to write this guide. It's a little finicky, and it might be easiest just to stick with the default.

Using systemd-cryptsetup

[TODO]

Working with dm-verity

[TODO]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment