Recently, I upgraded my NAS machine and decided I wanted to set up full disk encryption with the disk encryption key sealed inside a TPM. This setup is very similar to Microsoft's BitLocker disk encryption. Just to make it more difficult for myself, I decided to use a TPM2 device rather than an old TPM1.2 device. There are existing projects that implement this functionality for Linux using TPM1.2, but I did not find one for TPM2.
A TPM, or Trusted Platform Module, is a small and supposedly tamper-resistant chip that can be added to a computer in order to safely store some secret information. Among other features, a TPM can be programmed such that it only allows access to its secret information if the computer has booted up in the correct state (e.g. has not been booted from an external USB thumb drive). If a disk encryption key is stored in a TPM, then it can be configured to automatically unlock the root disk during a normal boot but not unlock it when something in the boot configuration changes.
There are two major versions of TPM devices, 1.2 and 2.0. A good description of the changes in 2.0 can be found here.
I am using an ASRock E3V5 WS motherboard with ASRock's TPM-S 2.0 device which is based on a Nuvoton NPCT650. The TPM module just needs to be plugged into the matching connector near the bottom of the motherboard.
In the UEFI configuration for the motherboard, I ensured that the TPM module was detected, that the software/Intel ME emulated TPM was disabled, and that the TPM was set up to use SHA-2 hashes (which actually causes both SHA-1 and SHA-2 hashes to be available).
On the software side, I am using
IBM's TPM 2.0 TSS along with
some custom shell scripts. Unfortunately,
despite claims on James Bottomley's linked blog, Ubuntu 16.04 LTS did not seem
to have a package for the IBM tools. I built the tools from source after
that disables the building of a shared object for the common code (preferring
to statically link it instead). After compiling, I followed James Bottomley's
instructions for creating a
81000001 key handle.
At this point, I created the
/opt/tpmdisk directory and placed the .sh files
from my repository into it. Finally, I copy the
At this point, I create a 32-byte file of random data and store it at
/keys/rootkey.bin. Ensure that this file is readable only to
purpose of this file is to allow the shell scripts to add/remove LUKS key slots
without needing the "recovery" password. Because this file is being stored on
the disk that is to be encrypted, it should not introduce an extra security
vulnerability. I use the
cryptsetup utility to set key slot 7 to the randomly
generated file and key slot 6 to a "recovery" password.
/opt/tpmdisk/new-disk-key.sh can now be run (as
root) to create a third
disk encryption key that will be sealed to the TPM. The sealed key will be
,keyscript=/opt/tpmdisk/unseal-disk-key.sh is appended after the
luks option in
/etc/crypttab and the initramfs is regenerated.
This script is relatively straightforward. It creates a temporary directory and
then creates inside it a temporary file with 32 random bytes. This will be the
new sealed-to-the-TPM encryption key, but it is currently in the clear. The
script removes the existing data in the appropriate LUKS key slot and assigns
the newly-generated key to that key slot. The script also invokes
seal-to-pcrs.sh to perform the actual TPM sealing operation. The script
finally cleans up and removes the cleartext copy of the new key.
This script performs the bulk of the work to seal secret data to a TPM. The data
is sealed such that "PCR values" in the TPM must match certain values in order
for the data to be unsealed. "PCR values" are special append-only registers that
log various steps of the boot process. The PCRs that are used are specified
(redundantly) by the
PCR_BITS variables in the script. The
PCR_BITS variable is a hex value that contains 1 bits in the bit position
corresponding to values in the
The PCR values chosen here are modified from the BitLocker recommendations. However, note that all PCRs 8 and above are created by the operating system rather than by the BIOS/UEFI, and Linux does not seem to use any of them. Also, for a TPM2 system, PCR 7 contains the UEFI Secure Boot configuration, so that is included in my list of PCRs.
This script first gathers the current value of all the PCRs into a temp file (one line for each value). These values are then combined into a textual policy file. Note that TPM2 policies are rather complicated and can contain an arbitrary amount of AND and OR terms. Here we are just creating a single AND term with all of the PCR values. The textual policy is then converted into a binary policy. The data is finally sealed to the TPM using that policy file.
This simple script tries to use
unseal-from-pcrs.sh to unseal the disk
encryption key. If this fails for any reason (such as the kernel having been
modified/upgraded), it falls back to the normal
askpass utility that belongs
to cryptsetup (where the "recovery" password can be entered). This is necessary
because otherwise the Ubuntu initramfs will loop forever (or a very long time)
repeatedly trying and failing to unlock the disk.
This script performs the bulk of the work to unseal data from a TPM. First, it loads the sealed data from files on disk into the temporary storage of the TPM. It then starts a session where policy verification can be performed. It then instructs the TPM to try to check the policy using (hopefully) the same list of PCRs that was used to create it. It then calls the unseal command and, if the PCR values match, the TPM will unseal the data. The script finally cleans up.