indexwritingsjournal › 2021

symlink.jnl[2021]

Remote access to PKCS#11 tokens

For a long time I had one "primary" SSH key which I carried around everywhere – there was a copy of it on all my PCs, on my USB stick, and later even some servers – and it could access everything as well, making it… not the best idea. (On some hosts I did also have local keypairs, but didn't want to bother with the combinatioral explosion of having to add each key on many different systems: Git forges, shell accounts, and so on.) In fact, I'd been using the same key since 2011 and it was configured in so many places that I couldn't easily rotate it.

After re-doing my SSH keys from scratch this year, I still ended up having many host-specific keypairs as well as one "main" keypair, but decided to guard it a bit better this time – instead of being copied around to all my systems, now it only exists on hardware tokens (and encrypted backups).

One of those hardware tokens is a TPM1.2 module that I've installed on my server (ember), where I do most of my linuxing nowadays. Setting up the obsolete TPM1.2 software stack (TrouSerS + OpenCryptoki) was kind of a pain, but it still somehow works and I can SSH to various hosts using ssh foo -I libopencryptoki.so.

However, this only works when ssh'ing from the server, yet sometimes I still want to use the same key from hosts which don't have any kind of embedded TPM (such as my work laptop), and I'm usually not in the mood for juggling USB devices, so instead I decided to access the server's TPM module remotely via SSH. The goal is similar to SSH "agent forwarding", but in reverse – instead of signing requests being forwarded from distant servers to the local ssh-agent, they have to be forwarded from the local SSH client to a remote host.

Many Linux systems now ship the p11-kit library, which acts as a PKCS#11 multiplexer and includes a "proxy" module – instead of each program needing to know which PKCS#11 module to use (and usually only one can be used at a time), they can be told to use p11-kit-proxy.so and will immediately see all tokens in all slots through that single module. When invoked, p11-kit looks for *.module configuration files that look like this:

# ~/.config/pkcs11/modules/tpm.module
module: /usr/lib/pkcs11/libopencryptoki.so

However, p11-kit also supports out-of-process modules – it can be configured to spawn the p11-kit remote helper and marshal all PKCS#11 requests through its stdin/stdout. This is very easy to configure by just specifying a remote: command instead of module, so the same tpm.module on a different host would look like:

# ~/.config/pkcs11/modules/tpm@ember.module
remote: |ssh ember p11-kit remote /usr/lib/pkcs11/libopencryptoki.so
enable-in: ssh, ssh-keygen

With this in place, I can now run ssh foo -I p11-kit-proxy.so from my laptop and it will authenticate using the key stored on the remote system, showing a PIN prompt just as if I were using a local smartcard.

Of course, the "background" SSH connection invoked by p11-kit has to use some other credentials (you really don't want it to recursively invoke itself), which in my case is done through Kerberos. (The optionally enable-in parameter limits which programs will see this token, so that e.g. Chrome won't try to load client certificates via SSH.)

In general, this might not be very secure – it just gives me some peace of mind regarding the many copies and locations of the private key. (For example, I don't have to worry about deleting five copies of id_rsa_main.ppk from a dead machine, I can just centrally remove that host from ember's authorized_keys.) The current setup also has a slight downside that TPM1.2 only supports RSA2048 keys, which is sufficient for now but will eventually need an upgrade.