SSH keys in TPM

Various ways to protect ssh keys with a tpm on linux

I experimented with storing certificate authority, SSH, and GPG keys in a TPM. This post will be about SSH keys.

GPG Keys

The first choice is easy if you've already gone through setting up GPG keys. You can use those keys through gpg-agent's ssh-agent support.

I had to mark the key with use-for-ssh so gpg-agent would export it. To do that, first you need to find your key in the ~/.gnupg/private-keys-v1.d/ directory:

$ cd ~/.gnupg/private-keys-v1.d/
$ grep -A1 Token *
9CCF0714C69A135C05DC3F626E024CF28B07DB31.key:Token: D27600012401115031310AEF19C71111 MSFT/Pluton\x2ETPM\x2EA/0000000
9CCF0714C69A135C05DC3F626E024CF28B07DB31.key- 000000000/gpg/37653438636434326434623962313366 - -
...

Here, I'm looking for the file that has my tpm key named gpg. Now I can add the ssh flag to it and restart the gpg agent:

$ echo "Use-for-ssh: 1" >> 9CCF0714C69A135C05DC3F626E024CF28B07DB31.key
$ systemctl --user restart gpg-agent

In order to use the gpg-agent instead of the ssh-agent, I have to change SSH_AUTH_SOCK. To make this permanent, you'll want to set it in your shell startup, like .bashrc

export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

gpg-agent doesn't put comments on keys, so to find the key you want to use, gpg can show it in ssh format:

$ gpg --export-ssh-key dan-tpmkey@drown.org
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCXpUuV6fjiypEc1WwlseL0/z7PyhjY101617Mi1O+nbT2vQd4i3UkdKFW0jIivVFSUmjltk6KRLRep1OdZEPHi4i06JzcqG+btSkyELrTRmUtnmL4LMLKX/o2FeUxHWmjuxRJVnsNP8vWd4AAq0MsEuxdRshMvXCUPq0R5a7cSizOa8QaMmCJlUbnm0iPADry5XVEOjUdJY5SpFrbYk90QEoGTKc4Sv96SFBVfhFbijDLhFEa0emaiv+ejh0okAf2lvFPFXXaVlb9E8rObLPeCRVeU60RoENZ2IPc3DeckDAuufdy/916IHn4CPOXYoDsGyXB23kAPoqasGijycuS63yhXPr1xE3f6Sxeg49zgRKhK6qMwdnq6VVAyqVoOG+L4bh8SuwO4DAt7vT0297+vYbAg2y0K2rPVpGkd0Zd6c+X/nlXOWUrhhG/xx1fSjXJv795y7mRGRMDrsZmpM7ojBmmL6MJX5E0gpFShZvU1URvOti/ziZEBcMWWaEPByMc= openpgp:0x0C9F877F

Now you can put your tpm key into your various authorized_keys files. One thing to consider is how to enter the pin to unlock the key. If you're on the graphics console of the system with the tpm, you can use a graphical entry program like /usr/bin/pinentry-gnome3. If you're logged in remotely, you might want a text input like pinentry-curses. With curses, you'll need to start the gpg agent in your terminal so it knows how to reach you.

PKCS11

In order to use the keys with ssh-agent, you can use its pkcs11 support. I'm assuming you've already setup a key from the GPG guide using the command tpm2_ptool.

First you need the path to the tpm library (this example is Ubuntu's path):

$ p11-kit list-modules | awk '/^module: / { module=$2} /path: / && module=="tpm2_pkcs11" { print $2}'
/usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so

ssh-keygen lets you list the keys available:

$ ssh-keygen -D /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCXpUuV6fjiypEc1WwlseL0/z7PyhjY101617Mi1O+nbT2vQd4i3UkdKFW0jIivVFSUmjltk6KRLRep1OdZEPHi4i06JzcqG+btSkyELrTRmUtnmL4LMLKX/o2FeUxHWmjuxRJVnsNP8vWd4AAq0MsEuxdRshMvXCUPq0R5a7cSizOa8QaMmCJlUbnm0iPADry5XVEOjUdJY5SpFrbYk90QEoGTKc4Sv96SFBVfhFbijDLhFEa0emaiv+ejh0okAf2lvFPFXXaVlb9E8rObLPeCRVeU60RoENZ2IPc3DeckDAuufdy/916IHn4CPOXYoDsGyXB23kAPoqasGijycuS63yhXPr1xE3f6Sxeg49zgRKhK6qMwdnq6VVAyqVoOG+L4bh8SuwO4DAt7vT0297+vYbAg2y0K2rPVpGkd0Zd6c+X/nlXOWUrhhG/xx1fSjXJv795y7mRGRMDrsZmpM7ojBmmL6MJX5E0gpFShZvU1URvOti/ziZEBcMWWaEPByMc= gpg

And ssh-add lets you add them to the agent:

$ ssh-add -s /usr/lib/x86_64-linux-gnu/pkcs11/libtpm2_pkcs11.so
Enter passphrase for PKCS#11:

You can add an optional -t [seconds] timeout if you want the keys to only be available for a short time, by default it will keep the key available until ssh-agent is killed.

password-safe extension

My last example is for when you have a key in a different format that isn't supported by your tpm. You can store it in a password-safe protected by the tpm. Whenever you need the key, you can load it into your ssh-agent and then use it. While the key is in memory, it isn't protected from a process copying the key if it's running as your user or as root. But you can tell ssh-agent to only hold on to the key for a specific timeframe, limiting your exposure. And when the key isn't in memory, it's protected by the tpm against brute force decryption attacks.

First, the password-safe extension. Put this in /usr/lib/password-store/extensions/ssh-add.bash:

#!/usr/bin/env bash

set -e -o pipefail

cmd_ssh_add_usage() {
	cat <<-_EOF
	adds the ssh key in the given secret name to the ssh agent for 60 seconds
	_EOF
}

cmd_ssh_add() {
  local path="${1%/}"
  local passfile="$PREFIX/$path.gpg"
  if [[ -f "$passfile" ]]; then
    $GPG -d "${GPG_OPTS[@]}" "$passfile" | ssh-add -t 60 -
  fi
}

[[ "$1" == "help" || "$1" == "--help" || "$1" == "-h" || "$1" == "" ]] && cmd_ssh_add_usage
cmd_ssh_add "$@"

Make sure that ssh-add.bash file is executable and owned by root.

Next, add your key to the password-safe:

pass insert -m sshkeytest <my-private-keyfile

Then you can add it to your ssh agent with:

$ pass ssh-add sshkeytest
Identity added: (stdin) (abob@frameworklaptop)
Lifetime set to 60 seconds

References:

tpm2-pkcs11/docs/SSH.md at master · tpm2-software/tpm2-pkcs11
A PKCS#11 interface for TPM2 hardware. Contribute to tpm2-software/tpm2-pkcs11 development by creating an account on GitHub.