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.
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.
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:
/dev/tpm0device directly; this is not recommended and indeed, according to my experience, not working when using the PKCS#11 interface as we’ll do later.
dev/tpmrm0device directly; this is the in-kernel resource manager for the TPM.
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
/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.
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
First, let’s define the following environment variable:
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
/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
The general syntax for this variable is method:details, where method can be:
device, to access a device directly; details should then be the path to the device, as in
tabrmd, to talk to the access broker daemon; details may then provide the IP address and port of the daemon, as in
tabrmd:host=127.0.0.1,port=555, or be left empty to use the default values;
mssim, to talk to a software simulator such as ibmswtpm2 (good for development or learning purposes); details may then provide the IP address and port of the simulator, as for the
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
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
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
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