Private keys don’t belong in files on disk.

This is a post for Linux admins or security engineers. Tested on Ubuntu 25.10 (Questing Quokka).

This is a follow-up to Using TPM-backed Client Certificates in Chrome on Linux.

Sooner or later, your SSH key will open the door to a lot of servers, including sensitive ones. The default setup, a key file sitting in ~/.ssh/, means that anyone who can read that file can impersonate you. A compromised workstation, a careless backup, a stolen laptop: any of these can leak your private key without you ever noticing.

In the previous post, we set up TPM-backed client certificates for Chrome. The same principle applies to SSH: move the private key into the TPM, and it can never be extracted. The key can be used, but not copied.

This guide shows how to:

  1. Create an SSH key pair inside the TPM
  2. Export the public key
  3. Use the TPM-backed key for SSH login

Prerequisites

This guide assumes you already have a working TPM PKCS#11 setup from the previous post. Specifically:

  • tpm2-pkcs11 tools are installed
  • Your user is in the tss group
  • The TPM2_PKCS11_STORE environment variable is set
  • A token has been initialized (we used the label PKI)

If you haven’t done this yet, follow the initialization steps in that post first.

Find Your PKCS#11 Module

In the previous post, we used /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so as the PKCS#11 module for the TPM.

You can verify which modules are available on your system using p11-kit:

p11-kit list-modules

If p11-kit is not installed: sudo apt install p11-kit

This will output something like:

module: tpm2_pkcs11
    path: /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so
    uri: pkcs11:library-description=TPM2.0%20Cryptoki;library-manufacturer=tpm2-software.github.io
    library-description: TPM2.0 Cryptoki
    library-manufacturer: tpm2-software.github.io
    library-version: 1.9
    token: PKI
        uri: pkcs11:model=Intel;manufacturer=Intel;serial=0000000000000000;token=PKI
        manufacturer: Intel
        model: Intel
        serial-number: 0000000000000000
        hardware-version: 1.38
        firmware-version: 147.1
        flags:
              rng
              login-required
              user-pin-initialized
              token-initialized

The path: line tells you which shared object to use with SSH. We’ll need this later.

If you have a YubiKey or another PIV-capable token plugged in, you may see an additional module via OpenSC:

module: opensc-pkcs11
    path: /usr/lib/x86_64-linux-gnu/pkcs11/opensc-pkcs11.so
    uri: pkcs11:library-description=OpenSC%20smartcard%20framework;library-manufacturer=OpenSC%20Project
    library-description: OpenSC smartcard framework
    library-manufacturer: OpenSC Project
    library-version: 0.26
    token: Your Name
        uri: pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=01de4dbe3fc0ff33;token=Your%20Name
        manufacturer: piv_II
        model: PKCS#15 emulated
        serial-number: 9b57ad4279c01423
        flags:
              rng
              login-required
              user-pin-initialized
              token-initialized

You can use any PKCS#11 module that exposes hardware-backed keys. Good practice is never to reuse a key for multiple purposes, so we’ll generate a new one specifically for SSH.

Create an SSH Key on the TPM

Add a new key to the existing PKI token. The key is distinguished by its key label (ssh), separate from the certificate key created in the previous post:

USER_PIN="<your user pin here>"
tpm2_ptool addkey \
  --algorithm ecc256 \
  --label PKI \
  --key-label ssh \
  --userpin "$USER_PIN"

This creates an ECDSA P-256 key pair inside the TPM. The private key never leaves the hardware.

We’re using P-256 here rather than the P-384 from the previous post. P-256 has broader support across SSH implementations and is the most widely deployed elliptic curve for SSH. P-384 would also work if your servers support it.

Export the Public Key

SSH needs the public half of your key. The ssh-keygen tool can extract it directly from a PKCS#11 module:

MODULE=/usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so
ssh-keygen -D "$MODULE"

This lists all keys available through the module. The output looks like:

ecdsa-sha2-nistp256 AAAAE2VjZH...EkHlCg= ssh

The label at the end (ssh) identifies the key you just created. If you have multiple keys on the token, look for the one matching your key label.

Copy that entire line into ~/.ssh/authorized_keys on every server you want to access with this key.

SSH is strict about file permissions. Make sure ~/.ssh/ is mode 700 and ~/.ssh/authorized_keys is mode 600, both owned by your user. SSH will silently refuse to use the file otherwise.

Configure SSH to Use the TPM Key

Tell SSH to use the PKCS#11 module when connecting to your server. Add this to ~/.ssh/config:

Host your-server
    PKCS11Provider /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so

Replace your-server with the actual hostname or alias.

Now connect:

ssh your-server

SSH will prompt for your TPM user PIN - the same one you set during token initialization. After entering it, authentication proceeds using the hardware-backed key.

If you want to use the TPM key for all SSH connections, you can set the provider globally:

Host *
    PKCS11Provider /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so

Be aware that SSH will then attempt PKCS#11 authentication on every connection, even to hosts that don’t use this key. You can avoid unnecessary PIN prompts by being explicit about which hosts use the provider.

Final Thoughts

Between this post and the previous one, your TPM now holds keys for two different purposes: browser-based client authentication and SSH. The private keys for both are locked inside the hardware, immune to file-system-level theft.

This is a meaningful improvement over key files on disk. A compromised workstation can still use your keys while it is compromised, but a remote attacker cannot silently copy them for later use. An old backup cannot leak them. A stolen disk image is useless without the physical machine.

The trade-off is convenience. You need to enter a PIN. You cannot easily move the key to a different machine - which is exactly the point, but it also means you need a plan for key distribution across your workstations. One key per machine, registered on the servers that machine needs access to, is a clean model.

Hardware-backed keys don’t solve everything, but they eliminate an entire class of key theft scenarios. Combined with the habits from the previous post (lock your screen, keep your system patched, use strong PINs) they make your authentication stack considerably harder to compromise.