smallstep_full_white

All About TPMs

Carl-Tashian.jpg

Carl Tashian

Follow Smallstep

A Trusted Platform Module (TPM) is a crypto-processor with a standardized interface defined by the Trusted Computing Group. The word “crypto-processor” makes it sound more powerful than it is. A TPM is typically a small, relatively slow microcontroller, but it can also be integrated into a CPU (and run in a special trust zone) or emulated as part of a VM hypervisor (a “vTPM”).

A TPM can perform a handful of crypto operations. Due to crypto export restrictions, you can’t run bulk data through a TPM for on-the-fly full-disk encryption, but it’s a great place to keep all kinds of encryption keys safe and to perform sensitive cryptographic operations safely.

TPMs grew out of the 1990s dot-com boom. During that period, it became clear that people did not only want to use the internet to share important scientific research and pirated video games. People also wanted to buy things. And personal computers and the internet just weren’t designed for that. TPMs were created to secure both computers and network traffic, providing strong security guarantees across a huge variety of devices.

Only in the past 3-4 years have TPMs become ubiquitous. So, the technology has had a long time to mature and learn from early missteps and vulnerabilities. Brandon Weeks, a friend-of-Smallstep who works on device attestation at Google, sent us this timeline for when TPMs became a requirement on various platforms:

iPhone: 2013
Android (TEE): 2015
MacBook Pro: 2016
Android (StrongBox): 2018
Google Compute Engine (vTPM): 2018
VMware vSphere: 2018
Windows Server: 2021
Windows: 2021
Azure (vTPM): 2021

👾 Want to play with a TPM2 module right now? TPM-JS simulates a full TPM2 stack in your browser and offers a playground for TPM2 functionality.

One of the TPM’s most clever features is that it can act like an oyster. It’s very sensitive to the presence of predators. Its hard shell is closed by default, and it only opens when conditions are deemed safe. Once open, it becomes possible to use the keys stored inside.

Conditions for the safe opening (”unsealing” in TPM-speak) of private keys are defined by policies that can include things like the hash of the running OS kernel or bootloader, the current hardware configuration, or a static password. You can even set time-based policies so that keys are only available during certain times of day.

TPMs are built with a few low-level capabilities (key generation, key storage, encryption, decryption, signing and hashing operations) that enable a wide range of high-level applications.

Let’s take a look at what becomes possible if we start leveraging TPM 2.0 on a Linux machine. In recent years, support for TPMs has finally extended beyond the kernel. Unfortunately, most Linux distributions still won’t these enable TPM-backed security features by default if a TPM is present.

Here are some popular applications for a TPM on a Linux device:

Secure boot

A TPM can solve a bunch of broader issues in computer security:

With secure boot, the TPM stores a list of signatures for binaries that are allowed to boot the system, and a list of revoked signatures that are forbidden to boot the system.

Why does this matter? For one thing, secure boot makes passwordless disk encryption possible. If the TPM sees that the system booted using an allowed/trusted OS bootloader, it can make a disk encryption key available to the OS.

Practically speaking, in order to make this work with Linux you need to use a unified kernel image. Normally the kernel image is a separate binary from the boot loader. A unified kernel image is a single binary that contains a boot loader (systemd-stub), a kernel, a kernel command line (the configuration parameters for loading the kernel), and an initial RAM disk (initramfs). As long as this entire package is signed and trusted by the TPM, there’s no opportunity for tampering with any of the pieces and being able to boot the system in a way that the TPM trusts.

Secure boot is really about whether or not the TPM trusts the binary used to boot the system. While an attacker can always boot a system using an arbitrary, untrusted binary, if secure boot is enabled, the TPM will not reveal its secrets to an untrusted OS. So, for example, an untrusted OS would not be able to decrypt a disk whose encryption key is stored on the TPM.

A lot of work is being done to make a more secure boot possible in Linux, especially in the world of systemd. See this blog post for a much deeper dive.

Full-disk Encryption

systemd-cryptenroll makes it easy to configure your system to unlock a LUKS-encrypted disk at boot using a TPM2 key. This is a really practical, useful application of a TPM because it allows unattended full-disk encryption. When disk encryption is configured to use a TPM, it’s not necessary to be physically present (eg. to type a passphrase) in order to unlock the disk. The TPM, in concert with the UEFI BIOS, bootloader, and Linux kernel, ensures that the conditions are safe (the bootloader and kernel images haven’t been tampered with, the disk partitions haven’t changed, etc) before allowing the disk encryption key to be decrypted.

Hardware-protected Credentials

There's a few ways to protect credentials using a TPM, but there aren't many user-facing tools that help. systemd-creds makes it easy to encrypt credentials for systemd services using a TPM2 private key.

Hardware Root Of Trust and Device Identity

TPM 2.0 supports a few options for proving a TPM identity to a third party. There are tradeoffs of privacy and security here. There are ways to prove to a remote system that a key is held by a TPM, without identifying a TPM device ID—this is known as anonymous attestation. You can also do the opposite—attesting a unique device ID—which is useful for, say, an enterprise VPN that requires signing in from a company-controlled device.

PKCS#11 Token Applications

The tpm2-pkcs11 library makes it possible to use your TPM as a PKCS #11 token for a variety of applications. PKCS #11 is considered a legacy interface to the TPM, because it doesn’t give you access to more advanced TPM features. But, lots of applications support PKCS #11, this is a really nice bridge library. Here are some use cases:

  • SSH key storage

    SSH supports PKCS#11 tokens via a PKCS11Provider directive. Here’s two tutorials for setting up SSH with TPM-backed keys: 1 2

  • Certificate Authority private keys

    Our step-ca Certificate Authority supports PKCS #11 tokens for the cryptographic protection of CA keys.

  • EAP-TLS

    The wpa_supplicant Wi-Fi client for Linux supports PKCS #11 private key URIs. You can leverage this to sign into a WPA2 Enterprise network using X.509 certificate authentication. Here's a tutorial for setting it up.

  • IMA (Integrity Measurement Architecture)

    While not quite ready for production, IMA is the Linux equivalent of a “measured boot.” IMA takes “secure boot” a step further. With a secure boot, the TPM knows whether it has been booted with trusted binaries. With IMA, you can set a policy that detects whether critical files have been altered, or whether untrusted binaries have been run. Unlike secure boot, with measured boot the boot process will halt if system integrity cannot be authenticated.

  • Remote Attestation

    Keylime enables remote attestation (system trust), for the boot process and down to the level of individual system calls of a running system. A verification server can confirm the device hasn’t been tampered with. If it detects anomalies, the verification server can ensure that the compromised device is removed from a cluster.

  • The tpm2-tools package has low-level commands for inspecting, controlling, and configuring a TPM. Because some of the higher-level interfaces to TPM2 are still rough around the edges, you may need one these tools at some point.

Integrating TPM2

Some open-source, high-level interfaces to TPM2, by programming language:

Under The Hood

TPM keys

Every TPM uses the following keys and values under the hood:

  • A primary seed, which is hardwired into it at the time of manufacture. When you ask the TPM to generate and store new crypto keys, they are derived from the Primary Seed using various key derivation functions that ensure there’s no reuse. Once you have generated a new key in the TPM, you can use it to wrap (encrypt) more keys (”child keys”) that can be stored on disk.
  • An endorsement key (EK) pair, created at time of manufacture, identifies the TPM. There’s two of these: One for RSA, one for ECC. And, there’s two X509 endorsement certificates as well. The certificate is signed by the TPM manufacturer’s CA. The endorsement key can’t be used for signing, but it can be used for encryption. So, it can help a third party identify other key pairs as TPM-generated, or to discover which TPM generated them.
  • Attestation Keys (AKs) are “pseudo-identity keys.” A third-party (typically an "Attestation CA") can use an AK—along with the EK certificate and the TPM manufacturer's CA certificate—to prove that a particular AK resides in the EK's TPM, and issue a certificate for an AK that doesn't reveal the TPM's hardware identifier. In this case, the Attestation CA is used to anonymize the TPM. The AK certificate can then be used as an application certificate, and those applications are able to trust that the AK is a hardware-bound TPM attestation key.
  • A master wrapping key, called the storage root key, which is created when you take ownership of the TPM and is stored within the TPM. It’s used to protect keys created by applications when attestation is not used.

Interfaces to a TPM

  • TPM TIS hardware interface — one of the Linux kernel modules for tpm is tpm_tis.
  • TPM CRB interface
  • TPM Advanced Configuration and Power Interface (ACPI) interface (btwn TPM and the BIOS)
  • TPM ACPI Physical Presence Interface (PPI) interface
  • PKCS#11, via tpm2-pkcs11

Platform Configuration Registers (PCRs)

TPMs are capable of storing a handful of SHA1 or SHA256 hashes in Platform Configuration Registers (PCRs). These registers enable features like Secure Boot. Each register can be reset (to all 0s or all 1s, for example), and “extended” (by passing in a hash). The registers cannot be written to directly. Extending is a one-way function that yields a new hash in the PCR:

# tpm2_pcrread sha256:16
sha256:
  16: 0x0000000000000000000000000000000000000000000000000000000000000000
# tpm2_pcrextend 16:sha256=1000000000000000000000000000000000000000000000000000000000000000
# tpm2_pcrread sha256:16
sha256:
  16: 0xA44A029E04493B8D2FE7893391C2B3CEEFEC1603C585AAD6203F2D14E07BFEAD

The extended PCR value is sha256(the current PCR value + the passed-in hash). Let’s simulate it on the command line:

$ echo "00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000" | xxd -r -p - > twohashes
$ xxd twohashes
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 1000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
$ sha256sum twohashes
a44a029e04493b8d2fe7893391c2b3ceefec1603c585aad6203f2d14e07bfead

In a Secure Boot environment, the system takes measurements of its state as it boots (including hashes of the system firmware image, the hardware configuration of the machine, the partition table of the boot disk, the boot loader binary, the kernel binary, and other binaries involved in the boot process) and it extends the PCRs with those measurements.

Once the system boots, if the PCRs don’t match predetermined safe values, the system knows that it has not booted securely and can halt itself or take some other action.

And, here’s where the magic happens: you can seal a private key stored in a TPM and only allow it to unseal if an expected set of values is in the PCRs. If the PCRs don’t match, the key will not be available. This makes LUKS full-disk encryption safe on an unattended system, where the private key is made available when none of the (unencrypted) boot loader and kernel binaries have been tampered with. An attacker with physical control of the machine could still boot it with arbitrary code—a TPM doesn’t protect against that—but they cannot decrypt the disk.

Many of the PCR registers are left to the OS or the user to populate. The systemd-cryptenroll man page has the best list I could find of well-known PCRs in Linux.

If you have a machine with a TPM2 chip, run tpm2_pcrread to check the values of the registers on a running system:

# tpm2_pcrread
sha1:
sha256:
  0 : 0x6A719672C565E49FF687DAA7FA1B6E08AD27E3ED1DFBD4A10047A3AAA63CA2A3
  1 : 0x5F3A4B193B94A24E88837E4C09917AF57B2C3B660D1AC63831D6941ADF9FF105
  2 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
  3 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
  4 : 0x1630E93B6B627AEDBD7BD88F298249D1102E13B5AD3E44EDC9B9B195A0BA05D0
  5 : 0x86B4CCE49518A72C0A420274F81E1767D701012E5BD0BED2F03E3B113F38A039
  6 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
  7 : 0xB37EAD4F015EB7493453A3AEF7A3D4727D9CC11DAD1DCE73A337C6546B717C4A
  8 : 0xDFC20104F650EDAF1E1FAC04853A3928BB763D31D4631459ECEA4612D9CF791E
  9 : 0x99FCAF3D0B4DA29EEFC1A009788230EF1A66E98F447D0A5D9DA6CFE1F1379915
  10: 0x622C836AB972D183F6ABD5C905426EA06B8790BA8062CCBA97EF4D139D5AD3EF
  11: 0x0000000000000000000000000000000000000000000000000000000000000000
  12: 0x0000000000000000000000000000000000000000000000000000000000000000
  13: 0x0000000000000000000000000000000000000000000000000000000000000000
...

PCRs are non-volatile memory locations that are just large enough to store a hash (eg. 256 bits, for a SHA256 digest). A TPM2 module has at least 24 PCRs.

Examples

Now lets get into a couple of examples that use TPM2:

  • Run a step-ca Certificate Authority using a TPM-protected CA encryption key
  • Encrypt a LUKS2 volume using a sealed TPM2 key on Fedora 35

Both of these examples use features of systemd. In Linux, systemd is one of the only projects I've found that's actively integrating user-facing features with TPM2.

Run a step-ca daemon using TPM-protected credentials

You can store your CA signing key encrypted on disk, and keep the encryption key stored in your TPM.

Before you begin, you should install step-ca if you haven't already.

👾 This example requires systemd 250+

First, set up the host credentials needed:

$ sudo systemd-creds setup

This will write two symmetric keys. One to /var/lib/systemd/credential.secret, and the other to your TPM. If you don’t have a TPM, it will just write one key to the file.

Now you can use the combined TPM + file key to encrypt a secret:

$ echo "password" > password
$ systemd-creds encrypt -p password step-ca

### OR do this
$ systemd-ask-password -n | systemd-creds encrypt --name=step-ca -p - -

This will write a SetEncryptedCredential= stanza to the file called step-ca. The credential is labelled step-ca.

SetCredentialEncrypted=step-ca: \
        Whxqht+dQJax1aZeCGLxmiAAAAABAAAADAAAABAAAAA7UzsN7ppUiV+13cUAAAAAfImu6 \
        d3eKQPPoTP1Sfg23PTXU+dUoZVd591UxvROTlj7Zc6lnBN+yCtkdPDA2WeF4nDXUQiWZw \
        ==

Add this to your systemd unit for step-ca, and reference it in the ExecStart= line:

ExecStart=/usr/bin/step-ca config/ca.json --password-file ${CREDENTIALS_DIRECTORY}/step-ca

When the service starts up, systemd decrypts the secret, and temporarily puts it into CREDENTIALS_DIRECTORY for use by the service.

Encrypt a LUKS2 volume using a sealed TPM2 key on Fedora Linux

Thank you to Joe Doss (Twitter, Mastodon), our in-house Fedora expert, for contributing this section

Preflight Checks

Verify that you have a TPM in your computer:

# systemd-cryptenroll --tpm2-device=list
PATH        DEVICE      DRIVER 
/dev/tpmrm0 MSFT0101:00 tpm_crb

Verify that you are booted into SecureBoot. If you see that it is disabled you will need to enable it in the BIOS

# mokutil --sb-state
SecureBoot disabled

You should enable SecureBoot before you start.

Note: Enabling SecureBoot will cause third party kernel modules (such as NVIDIA drivers) to fail to load. You can work around this by using this guide or this guide to automatically sign the drivers built by akmod.

Enroll your encrypted volumes

# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+4+7+8 /dev/nvme0n1p3
# systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+2+4+7+8 /dev/nvme1n1p1

I'm using Btrfs with RAID 1, so I have enrolled both NVME drives.

When setting --tpm2-pcrs=0+2+4+7+8 the following items are these are validated at boot time:

0 System firmware executable
2: Kernel
4: Bootloader
7: Secure boot state
8: Kernel command line string

PCR 0,2,4,7,8 validates the firmware, kernel, bootloader, and kernel command line string before releasing the decryption key. If you're using PCR 2 and multiple kernels, you need to enroll a key within each kernel.

If you upgraded firmware, kernel, or bootloader, the TPM will not release the key. As a result, auto decryption will fail, and you'll be prompted for a passphrase. You will need to remove the old key and enroll a new key if any of these things change

Remove all TPM2 keys and enroll a new key

systemd-cryptenroll /dev/block-device --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8

This will ask for your volume's passphrase. If you'd like to automate this, you may set the PASSWORD environment variable to your passphrase.

Edit /etc/crypttab

Add tpm2-device=auto,discard to the end of each LUKS device line in /etc/crypttab

# cat /etc/crypttab
luks-014aa5a6-a007-11ec-a054-7c10c93c41b1 UUID=0818cd36-a007-11ec-aaab-7c10c93c41b1 - tpm2-device=auto,discard
luks-0e9e99f6-a007-11ec-8130-7c10c93c41b1 UUID=15bc3342-a007-11ec-a502-7c10c93c41b1 - tpm2-device=auto,discard

Edit /etc/default/grub

Edit /etc/default/grub and add rd.luks.options=tpm2-device=auto to GRUB_CMDLINE_LINUX

GRUB_CMDLINE_LINUX="rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1 rd.luks.uuid=luks-12a79b0a-d32f-44f0-b038-05e1f676bda8 rd.luks.uuid=luks-b88f07d7-2ee6-4339-892f-21adc34fbc8e rd.luks.options=tpm2-device=auto rhgb quiet rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1"

Add TPM2 support to dracut

Work around this BZ https://bugzilla.redhat.com/show_bug.cgi?id=1976462 by creating /etc/dracut.conf.d/tss2.conf and adding this line to it:

install_optional_items+=" /usr/lib64/libtss2* /usr/lib64/libfido2.so.* "

and then run dracut -f to rebuild the initrd.

Verify and reboot!

Verify that you have the TPM added to the device:

# systemd-cryptenroll /dev/nvme0n1p3
SLOT TYPE    
   0 password
   1 tpm2
# systemd-cryptenroll /dev/nvme1n1p1
SLOT TYPE    
   0 password
   1 tpm2

and now you can reboot and your TPM should unlock your encrypted drives!

Sources:

Further reading

Carl Tashian (Website, LinkedIn) is an engineer, writer, exec coach, and startup all-rounder. He's currently an Offroad Engineer at Smallstep. He co-founded and built the engineering team at Trove, and he wrote the code that opens your Zipcar. He lives in San Francisco with his wife Siobhan and he loves to play the modular synthesizer 🎛️🎚️