secret agent man

For as long as I can remember I have used one SSH key pair for each device. I know there are some who prefer to use a different key for different accounts. I tried this in the past but felt it didn’t increase the security sufficiently enough to warrant the complexity in my use case.

I have three main devices; my desktop PC, my laptop, and my phone. This means that any system I need to ssh on to requires three entries in the authorized_keys file.

I use drist for ensuring my keys are on my servers (see a previous post about this tool), meaning I can connect from any of my devices.

When I rebuilt my laptop recently I generated a new key pair, then updated my drist configuration and pushed it out to my systems. All was well until I wanted to connect to my account on sdf.org, and realised I had not pushed the updated public key for my laptop to my SDF account.

This got me thinking. For those that use a GPG key, it is very common to have one key that belongs to an identity. In my case my key is used with my email, git commit signing, and other encryption to prove I am pyratebeard. The private key has been securely copied to my laptop and phone and imported into the GPG keyring.

Could one SSH key pair for my identity be enough? If the private key was securely copied to my devices, then my systems and any accounts that require ssh only need to know about one key.

To get an idea of how others work I put out a poll on Mastodon.

results

It surprised me that an equal number of people use one key per device as those that use one key for all.

Maybe using one key isn’t such a bad idea. Of course this changes my threat model. If any of my devices are compromised I would have to replace the key on all of them. There has to be a secure way of achieving this.

When a GPG key is loaded into your keyring you don’t have to keep the private key. With SSH keys there is no keyring, ssh uses the private key file when connecting. There is of course ssh-agent which can load the key in memory, but the private key still has to be read after a reboot. The key will still need a passphrase to be used, just like using your GPG key still requires a passphrase, but something about having the GPG key in a keybox file seems more secure than the SSH key “just lying around”.

As it turns out you can add an SSH key as a subkey to a GPG key, then gpg-agent will provide the authentication instead of ssh-agent, and more importantly you don’t need an SSH private key file.

At first I attempted to add my existing SSH key to my GPG key, but hit a few blocks and started down a rabbit hole. Instead I opted to create a new SSH key. This would mean I would have to push it out to everywhere I needed it, a small price for ease of setting up.

It is a good idea to take a backup of your existing GPG key

gpg2 -a --export-secret-keys <key_id> > gpg-backup.asc

To generate a new SSH key incant

gpg2 --quick-add-key <key_id> ed25519 auth 0

Now is a good time to take the new SSH public key and copy it everywhere you need it. You could use a tool such as drist or do it manually. I could not figure out how to do it with ssh-copy-id, if anybody knows how then please get in touch

gpg2 --export-ssh-key <key_id>

Next we can stop our ssh-agent and gpg-agent. I use keychain for managing my agents so incant

keychain --agents ssh,gpg -k

We have to tell GPG which subkey to use for SSH, we do this by taking the keygrip and putting it into GPG’s sshcontrol file

gpg2 -k --with-keygrip <key_id>
echo <keygrip> >> ~/.gnupg/sshcontrol

Now to start the gpg-agent back up, no ssh-agent required. In my zsh config I modified the keychain command to remove the option to start ssh-agent. I then set the SSH_AUTH_SOCK variable

eval $(keychain -q --agents gpg --nogui --eval 0xC7877C715113A16D)
gpg-connect-agent updatestartuptty /bye >/dev/null
if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ] ; then
    export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi

You can also see a gpg-connect-agent command in there. This is used to tell GPG to update the TTY it is using. GPG has no way of knowing which TTY you are using, so when it requires pinentry it needs to know which TTY to listen for user interaction. You could avoid this by using pinentry with a GUI.

Now if you open a new terminal, or source your config, SSH should work using your GPG key. You will be prompted to enter a passphrase with the first connection, this is the same as your GPG passphrase.

Now you can copy your updated GPG key to your other devices (you may need to delete the existing key before importing)

gpg2 -a --export-secret-keys <key_id> > gpg_with_ssh.asc

The SSH subkey is working okay so far. I am using it on my desktop (running Arch Linux), my laptop (running OpenBSD), and with Termux on my phone.

There have been a few issues though, hopefully easily fixed with a bit of investigating. On Termux I noticed that if I have two (or more) tmux windows I need to rerun the gpg-connect-agent command otherwise pinentry may startup on the other window.

On OpenBSD pinentry seems to crash tmux. This is my first OpenBSD install as a workstation so I am still figuring things out.

During my research I was also reminded of SSH certificates and their advantages over key pairs. I am going to delve into that with my own systems (expect a write up!) but it doesn’t help on systems I do not control, such as SDF.

Relying only on my GPG for SSH still feels a bit odd but I will stick with it for a while and see how it goes. It certainly makes my authorized_keys file management easier!