Blog

Using a TPM for SSH authentication

I recently got a new laptop equipped with a TPM chip (like almost all new laptops nowadays, as I believe the presence of such a chip is a requirement for the latest versions of Microsoft Windows). I have still to investigate all the possible uses I can make of that chip, but here I describe how to use it for SSH authentication.

Hardware and kernel-level support

First, let’s check whether the TPM is correctly recognized by the Linux kernel:

# dmesg | grep tpm
[   16.880645] tpm_tis IFX0783:00: 2.0 TPM (device-id 0x1B, rev-id 22)

So, it’s an Infineon chip (IFX), which is handled by the tpm_tis module (along with the tpm_tis_core, tpm_crb, and tpm modules).

If you’re compiling your own kernel, you need to make sure all the CONFIG_TCG_* options, in Device Drivers > Character devices > TPM Hardware Support, are enabled.

The TPM Software Stack

Next, we need the user-space libraries and tools to interact with the TPM, what the Trusted Computing Group calls the “TPM Software Stack” or TSS. Importantly, the chip on my laptop is a “2.0 TPM” (implementing version 2.0 of the TPM specification); this means we cannot use TrouSerS, which only supports TPM 1.2. This is kind of a shame, because it seems most of the available documentation on the net regarding the use of a TPM under GNU/Linux is focused on TrouSerS.

Instead, there are two free TPM 2.0 Software Stack implementations out there: IBM’s TPM 2.0 TSS and the one from the “tpm2-software community” (mainly driven by Intel). I’ve choosen the latter, mostly on the grounds that it seems to provide more tools for integration within a GNU/Linux system, such as an OpenSSL engine or a PKCS#11 interface—which we’ll use below.1

So, let’s install the core of the TSS, the tpm2-tss libraries and tools. They may already be available in your distribution’s package repositories (e.g. in Debian), if not, install them from source:

$ wget https://github.com/tpm2-software/tpm2-tss/releases/download/2.3.2/tpm2-tss-2.3.2.tar.gz
$ tar xf tpm2-tss-2.3.2.tar.gz
$ cd tpm2-tss-2.3.2
$ ./configure --with-udevrulesdir=/lib/udev/rules.d
$ make
# make install-strip

For Slackware users, I wrote a SlackBuild script for tpm2-tss, as well as for all the other packages mentioned in this note.

Then, we need to decide how users will access the TPM. There are three options here:

We’ll use the access broker daemon here, so let’s install the tpm2-abrmd package, again either from your distribution’s repositories (Debian), from my SlackBuild for Slackware users, or from source:

$ wget https://github.com/tpm2-software/tpm2-abrmd/releases/download/2.3.0/tpm2-abrmd-2.3.0.tar.gz
$ tar xf tpm2-abrmd-2.3.0.tar.gz
$ cd tpm2-abrmd-2.3.0
$ ./configure --with-systemdsystemunitdir=/usr/lib/systemd/system \
    --with-systemdpresetdir=/usr/lib/systemd/system-preset
$ make
# make install-strip

The daemon should run under a dedicated user account with read/write access to the /dev/tpm0 device. If you installed it from your distribution’s repositories, the packager probably already took care of that for you and you can skip the rest of this section; otherwise, read on for some hints about what to do.

For Slackware users again, my SlackBuilds already mentioned do take care of the following.

The tpm2-tss project comes with a Udev rule to grant access to the TPM device to an user account called tss, you will need to make sure such an account exists and then to make Udev reload and apply its rules:

# groupadd --system tss
# useradd --system --comment "TPM2 Software Stack" --home-dir / --gid tss tss
# udevadm control --reload-rules
# udevam trigger

The tpm2-abrmd project comes with a systemd unit file; if your distribution uses systemd, you shouldn’t need to do anything to ensure the daemon is running. If your distribution does not use systemd, then it’s entirely up to you to come up with a control script suitable for whatever init system your distribution does use. Note that tabrmd is part of this new generation of daemons that know nothing else but systemd: the daemon blatantly assumes it will be managed by systemd and will not bother to write its PID somewhere or even to detach from the terminal, so your control script will need to take care of that.

The PKCS#11 interface to the TPM

There’s one last piece of software that we still need before we can start using the TPM with SSH: tpm2-pkcs11, which will allow us to use the TPM as if it was a PKCS#11-compatible cryptographic token (and therefore will make it usable by any program capable of using such a token, such as OpenSSH).

The tpm2-pkcs11 project is quite recent and may not have found its way into many distributions yet (e.g. it was not available in any Debian versions, even -sid, at the time of this writing), so for now at least you will probably have to install it from source:

$ wget https://github.com/tpm2-software/tpm2-pkcs11/releases/download/1.0/tpm2-pkcs11-1.0.tar.gz
$ tar xf tpm2-pkcs11-1.0.tar.xf
$ cd tpm2-pkcs11-1.0
$ ./configure
$ make
# make install-strip

Creating a TPM-based key

First, let’s define the following environment variable:

export TPM2_PKCS11_TCTI=tabrmd:

This will instruct the tpm2-pkcss11 code to only access the TPM by talking to the access broker daemon; by default, it will first attempt to access the /dev/tpm0 device directly, and only after it failed to do so (due to missing permissions) it will fall-back to contacting the daemon (it works fine, but will result in cluttering your console with useless error messages).

The general syntax for this variable is method:details, where method can be:

Then we need to create the directory that will host the actual key and other metadata needed by the PKCS#11 interface:

$ mkdir ~/.tpm2_pkcs11
$ tpm2_ptool init
action: Created
id: 1

Somewhat counter-intuitively, the private key will not end up stored within the TPM chip! Instead, the key will be stored in a SQLite file in the .tpm2_pkcs11 directory, encrypted in such a way that it can only be decrypted and used by the TPM. Everytime the private key will be needed (e.g. to authenticate to a SSH server), the tpm2-pkcs11 library will send the (encrypted) key to the TPM along with the data that are to be signed; the TPM will decrypt the private key and perform the signing operation.

This design allows to use an unlimited number of different keys with a single, memory-constrained TPM chip; obviously, the downside is that it is then up to the user to take care of not loosing the contents of the .tpm2_pkcs11 directory.

Now, we create a (pseudo) PKCS#11 token:

$ tpm2_ptool addtoken --pid=1 --label=mylabel --sopin=XXXX --userpin=YYYY

The pid value should be the primary object id returned by the init command above. The label can be chosen freely but must be unique among all tokens. Finally, sopin and userpin are the administrator and user PINs, respectively.

Now that we have a token, we can create a key on it. Here, we will create a ECC key based on the NIST P-256 curve:

$ tpm2_ptool addkey --label=mylabel --userpin=YYYY --algorithm=ecc256

Using the key with SSH

We need to obtain the public part of the key we just created, so that we can allow that key to be used on whatever SSH server we want to log into. To do that, we use the standard ssh-keygen command, instructing it to get the key from our PKCS#11 token:

$ ssh-keygen -D /usr/local/lib/pkcs11/libtpm2_pkcs11.so > key.pub

Depending on how you installed the tpm2-pkcss11 package, you may need to adapt the path to the libtpm2_pkcs11.so shared library.

Append the contents of the key.pub file to the ~/.ssh/authorized_keys file of your account on the SSH server. You may then attempt to log in to that server, again by instructing the ssh program to use the PKCS#11 token:

ssh -I /usr/local/lib/pkcs11/libtpm2_pkcs11.so myserver.example
Enter PIN for 'mylabel': YYYY

To avoid having to use the -I everytime, you may use the PKCS11Provider option in SSH’s configuration file (either globally or restricted to a given host). If you also use other SSH keys that you normally load into a SSH agent, you may also want to disable using the agent for a particular host (otherwise SSH will always attempt to use the agent without ever using the PKCS#11 token):

Host myserver.example
  PKCS11Provider /usr/local/lib/pkcs11/libtpm2_pkcs11.so
  IdentityAgent none
  1. Also, the fact that IBM’s code comes with some docs in Microsoft Word files, with Visual Studio build files, all packed in a fudging tar bomb… did not shed the project in a very good light to my eyes.
  2. If you’ve been using smartcards under GNU/Linux, this is similar to the approach used by PCSC-Lite: the pcscd daemon is the only program to access the card reader(s) directly and other programs only talk to the daemon.