UP | HOME

SSH Agent

Table of Contents

TL;DR

Store your private SSH key in pass:

pass insert -m ssh/priv < id_ed25519

Enable SSH support in gpg-agent (~/.gnupg/gpg-agent.conf):

enable-ssh-support

Point SSH_AUTH_SOCK at the gpg-agent socket (~/.profile):

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

Auto-load the key when i3 starts (~/.config/i3/config):

exec --no-startup-id bash -c 'pass show ssh/priv | ssh-add -'

Using gpg-agent as ssh-agent

I sometimes find my self in a peculiar situation, where I forget my YubiKey; yes, I do have a tendency to forget things. But hopefully some ingenuity will help me in these situations. One thing that tends to help me is to have a backup plan, another is gpg. I have used gpg or pass to store my tokens and passwords, and then sent them (encrypted of course) to github, where I can just clone the repo and source secrets directly into my shell environment, for example from .zshrc or .bashrc

export MY_SECRET_TOKEN=$(pass show my/secret)

and by setting ~/.gnupg/gpg-agent.conf to a big enough cache I don't have to enter the password every time I open a terminal (or when I use any of the other vault secrets). This has worked quite well I must admit, and at least give some security as long as I'm not letting any suspect people look into my environment. Anyhow, since this worked quite well, I was thinking of doing the same thing for my private key, there is actually no good reason that this could not be in gpg vault instead?

Let me try to break down this into todo tasks.

  • first we need to make sure that the private key is stored inside gpg vault.
  • Make sure that the ssh-agent is running.
  • add the ssh key using ssh-add

Prerequisite

Before we start we need to have some software available on the system.

gnupg/gpg-agent
The GNU Privacy Guard toolkit. Provides gpg for encryption/decryption and gpg-agent which acts as the credential daemon. This is the core of the whole setup.
pass
A minimal password manager that stores secrets as GPG-encrypted files in a directory tree. You use it here as the "vault" to store the private SSH key. Syncs via git, so accessible from any machine.
systemd user session
The gpg-agent is started and socket-activated via systemctl –user. This is standard on any modern systemd-based distro (Arch, Fedora, Debian 10+, Ubuntu 16.04+), but worth stating explicitly.
pinentry
small program to handle password entry.

Adding private key.

lets start by adding the key to the gpg vault , this is pretty straight forward

pass insert -m ssh/priv < id_ed25519

So this should be pretty self explanatory, the only thing that might be somewhat different is the -m which states that its a multiline.

ssh agent

Ssh keys can be passphrase-protected. But typing your passphrase every time could be some what frustrating. So instead the ssh-agent which is small daemon holds you decrypted private key in memory, handles authentication; and foremost, the private key is never exposed to other process.

One thing that keep in mind is that there are several different ssh-agent's, for example

openssh
ssh-agent simple, universal
gnupg
gnupg gpg users,YubiKey
gcr-ssh-agent
gcr Gnome desktop users
kwallet
kwallet KDE desktop users
seahorse
Gnome GUI key manager

Which is the best one? No idea, I guess that depends on your environment and you usage. For me I'll be sticking to gnupg since I use gpg and YubiKey and it fits my i3 usage.

systemd and gpg-agent

first of we need to start the ssh-agent, i think there are several ways to do this, but i think the best and (maybe) the easiest way is to use systemd.

systemctl --user cat gpg-agent
# /usr/lib/systemd/user/gpg-agent.service
[Unit]
Description=GnuPG cryptographic agent and passphrase cache
Documentation=man:gpg-agent(1)
Requires=gpg-agent.socket

[Service]
ExecStart=/usr/bin/gpg-agent --supervised
ExecReload=/usr/bin/gpgconf --reload gpg-agent

supervised just tells it to run in the foreground and all errors to stderr, this means it will show up in the journalctl -u gpg-agent in case of errors. This has obviously a socket connected to it:

systemctl --user cat gpg-agent.socket
# /usr/lib/systemd/user/gpg-agent.socket
[Unit]
Description=GnuPG cryptographic agent and passphrase cache
Documentation=man:gpg-agent(1)

[Socket]
ListenStream=%t/gnupg/S.gpg-agent
FileDescriptorName=std
SocketMode=0600
DirectoryMode=0700

The important part is where the socket is created. ListenStream=%t/gnupg/S.gpg-agent

Ok, this part should now be considered done ✅

gpg-agent ssh enable

Im not sure this is actually needed, but to be extra cautious we add following line to the ~/.gnupg/gpg-agent.conf

echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf

As I mentioned, not sure its needed since we already enabled the systemd socket for ssh, but it creates a socket for auth.

ls -la | grep .ssh

Adding environment

We have most of it setup, the problem is that we need to add the SSH_AUTH_SOCK to the ssh socket was created with gpg-agent. In my case, since I'm using i3, I don't get anything setup automatically (which is how I want it). But I want the variable set at every login; that means for example if I use terminal to login. So setting it only in i3 config won't do it. I could settle with .zshrc but then again, this would mean it won't be set in emacs if I start emacs without going through a console.

This is what .profile or .zprofile or even .xprofile now which one should i use?

The idea is that .?profile files are read before anything is set, right after we start loading the windowmanager , in that way , everything should follow. But which one should i choose? Good question, i settled with adding:

source ~/.profile

in all the files (except .profile) and in .profile i added.

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

So after a logout sequence i get this:

ssh-add -l
2048 SHA256:HiIjSZBhxBLqHNYhOBNR3b5foPQOwsfO6fqG7Yl0MG8 cardno:32_234_712 (RSA)

This is my YubiKey, so it seems as if this works.. But we won't be using this for now.

Adding key to agent

We already have the key in the gpg vault. We now want to automatically add this key to the gpg-agent. its quite simple idea.

pass show familjen/calle/ssh/spock| ssh-add -
Identity added: (stdin) (olsen.carl@gmail.com)

It now asks for a password, through pinentry which can be configured in the ~/.gnupg/gpg-agent.conf

by adding for example

echo "pinentry-program /usr/bin/pinentry-qt5" >> ~/.gnupg/gpg-agent.conf

Or some other pinentry program.

and then

gpg-connect-agent reloadagent /bye
OK

Which reloads the configuration

Now back to the problem, we want to add the ssh key to the agent. We cant add it to the .*profile since we won't be able to use the pinentry, we can set it in .zshrc , but that means we need to open a terminal before anything else, and that I will forget. So the 3rd alternative is to add a line in i3 config.

exec --no-startup-id bash -c 'pass show familjen/calle/ssh/spock | ssh-add -'

Now, this makes it work, the only problem is that I need to type in a password as soon as I login, but thats reasonable.

Lets see if it worked after a reboot.

Verification after reboot

Login and then password

ssh-add -l
2048 SHA256:HiIjSZBhxBLqHNYhOBNR3b5foPQOwsfO6fqG7Yl0MG8 cardno:32_234_712 (RSA)
256 SHA256:LQBlj7brI5NIdiHlMPtlh3P2BYzoCvxs2/jHtABAGnw olsen.carl@gmail.com (ED25519)

Voilá! It worked ✅

Summary

My task was to store the private ssh key into a gpg vault. This could then be retrieved and added to a ssh-agent , in my case I used gpg-agent since that kind of fits into the rest of the system. The nice thing about this setup is that I can retrieve the private key from any system as long as I have an Internet connection (the vault is stored in a remote repo). By calling pass from the i3 startup config , I could add the private key to the ssh-agent and it will be used automatically every time I use ssh.

Date: 2019-03-26 Tue 09:02

Author: Carl Olsen

Created: 2026-04-12 Sun 19:52