Mkinitcpio tpm2 encrypt

Submitted by Stopka on Thu, 10/29/2020 - 21:44

All my personal and company computers are powered by Arch Linux with encrypted storages. This setup brings an inconvenience of entering two passwords on startup. One unlocks the storage encryption, second logs me to my user account. Isn't there a way to unlock the system volume automatically? Mobile phones for example also encrypt their storages and still we don't have to enter a password during boot.

Fortunately for this purpose are newer laptops equipped by so called trusted platform module, aka TPM. There are two mutually incompatible versions: older TPM1.2 and new TPM2. Manual for setting up a 1.2 TPM version can be found in the official Arch Wiki, for the TPM2 there are several blog posts, which are outdated or not detailed enough.


For our puprose we need to know, that TPM is a device on mother board, which, to put it simply, tracks the computer booting process. In every stage of the boot it creates a hash of several values and stores it to one of it's Platform Configuration Register (PCR). Which value hashes are stored in wich register can be found in following table. Some registers are meant to store hash of the program that is going to be executed, other contain hash of configuration values.

Number Usage
1 BIOS configuration
2 Option ROMs
3 Option ROM configuration
4 MBR (master boot record)
5 MBR configuration
6 State transitions and wake events
7 Platform manufacturer specific measurements
8–15 Static operating system
16 Debug
23 Application support

For example, if I was watching changes on registers 0,2,4 and 7, I would have noticed that hashes change every time I change a configuration value in bios, everytime I flash different version of bios firmware or even when I update linux kernel.

On Arch Linux there is a package in official repository called tpm2-tools, which contains all you need to work with tpm2 device. There is a program called tpm2_pcrread, which lists current state of PCR registers.

TPM device can use these registers to seal a secret, like the main storage encryption key. When sealing the secret, we select several registers and tpm2 generates private and public keys. Later on when we submit those generated keys to tpm2, it checks registers again and returns the original secret. The point is, that tpm reveals the secret only if all selected hashes match the sealing state. Only when nobody tampered firmware, configurations or any other tracked part of the system it reveals the secret.

Sealing the key

The very first step is to clean tpm device from our previous attempts and take ownership. If we have persisted some object on the selected address before, we need to evict the object from tpm device as well.

tpm2_startup -c

#remove persistemt object
tpm2_evictcontrol -C o -c 0x81000001

At this moment we should have tpm device prepared for sealing of our key. We create an object and make the object persistent. Then we create a policy: a list of registers and a hash function that should be used for sealing. At last we take the object together with the policy and do the sealing of our key stored in ssd-crypt.key file.

#create object
tpm2_createprimary -c primary.ctx

#make object persitatnt
tpm2_evictcontrol -c primary.ctx 0x81000001

#create policy
tpm2_createpolicy --policy-pcr -l "sha256:0,2,7" -L "pcr.policy"

#seal key
tpm2_create -C "0x81000001" \
  -a 'fixedtpm|fixedparent|adminwithpolicy|noda' \
  -i ./ssd-crypt.key -L ./pcr.policy \
  -r ssd-crypt.priv.key -u

At this stage we have our key sealed by tpm. In the working directory there should be newly created public and private key.

We can try unsealing the key back by following steps. After executing these commands, there should be new file called ssd-crypt.unseal.key, which contains our original key.

#load persistent object
tpm2_load -C "0x81000001" \
  -r ssd-crypt.priv.key -u
  -c primary.unseal.ctx

#unseal key
tpm2_unseal -c primary.unseal.ctx \
  -o ssd-crypt.unseal.key -p "pcr:sha256:0,2,7"

Unlocking the storage on startup

The main system storage needs to be unlocked in the initramfs boot phase. Initramfs is a file containing init system, kernel modules, applications and configuration files needed to mount the main storage. Kernel loads it's content to RAM and uses tools inside to find and mount proper storage device.

In Arch Linux the Initramfs file is generated by mkinitcpio program, which has it's configuration in /etc/mkinitcpio.conf. Here there is list of modules that should be added, list of hooks (packages of libraries, applications and configurations) and even order in which they should be called during the boot.

From Arch User Repository (AUR) you can instal mkinitcpio-tpm2-encrypt package. By installing this package you add mkinicpio hook tpm2 to your system. Hook helps to unlock storage by unsealing the key using tpm2 device.

There are two init systems available in Arch Linux: Busybox init and Systemd init. According to init system you use, the proper configuration of /etc/mkinitcpio.conf file must be applied. In case of Busybox init you just add tpm2 hook before encrypt hook.

HOOKS="... block tpm2 encrypt filesystems ..."

Recently, and here is my main addition to this topic, I extended the package to also support systemd init. Therefore you can as easily enable tpm key unsealing by adding sd-tpm2 hook before sd-encrypt into /etc/mkinitcpio.conf file.

HOOKS="... block sd-tpm2 sd-encrypt filesystems ..."

In some setups it can be also usefull to add sealed private and public keys to initramfs.

FILES="/etc/tpm2-encrypt/ /etc/tpm2-encrypt/key.priv"

Hook unseals the storage key using tpm2 device into /crypto_keyfile.bin file, and enables unlocking the storage without entering of the password manually. Hook retrieves all needed unsealing information from kernel parameters. This way you can pass parameters for unsealing the key from example above.

tpmkey=rootfs:/etc/tpm2-encrypt/key:0x81000001 tpmpcr=sha256:0,2,7

Last step is to set your system to unlock the storage by unsealed file. If the unsealing process fails, you still can get into your system. You are simply asked for storage password like before.

Kernel updates

There is a chance that we set PCR policy too strict, and therefore we will have to reseal the storage key after each kernel update, because any kernel change means also change of hashes in several PCR registers. The sealing process is definitely not something  we want to do often and it can't be easily automated. Maybe you are thinking of simple solution to this problem: Let's select only those registers not changing after kernel update. And you are right, but leaving these registers without verifcation opens a posibility that someone tampers the kernel, because it is almost the only thing on not encrypted partition, and get the key from the tpm device. Isn't there something that could prevent to run kernels not verified by us?

The answer is secure boot. If we load our custom secure boot keys to efi and we also sign the kernel with these keys, we can be sure, that computer will execute only our verified kernels, nothing else.

The result

In efi I have secure boot enabled with my own keys. I have 2 partitions on my main storage drive.

First one is small, not encrypted, in FAT32 format. It contains only kernel and initramfs packed together into single binary file linux.efi (using efi stub) signed by my own secure boot keys. This is the partition were efi looks for operating system to boot.

Second partition is lvm volume group encrypted by luks. In the lvm I have separate swap volume for hibernation to work, and root filesystem volume. 

Efi just executes the binary, loads the kernel, mounts also the initramfs and runs all set services there. The initramfs contains tpm2 tools and unsealing keys for main system partition, so it can unseal the key from tpm2, use this unsealed key to unlock the second partition and then mount root filesystem and swap.

After only few seconds I am welcomed by gdm login screen. No other password need anymore.

For unsealing I use only those registers that checks bios firmware and configurations. Signing of the efi binary is done automaticaly after each kernel update by sbupdate-git AUR package. This ensures that even after kernel updates booting isn't going to fail.

And it is secure. If somebody changes secure boot keys in efi, also PCR hases change and storage key remains safely sealed. If somebody turns off secure boot completely or modifies bios, again hashes change. If somebody tampers kernel or tries to boot something not verified by me, efi will deny to boot such binary and key still remains safe.