Using gpg-agent Effectively

I’ve used GnuPG (or “GPG”) for years now, but until recently I had never bothered setting up gpg-agent. I’ve just never needed it. The agent mediates access to private keys. These keys are only required for decryption and signing; encryption and signature verification only require public keys. I’ve mostly used GPG in the past for encrypting backups before uploading them to cloud storage, and this doesn’t require using an agent. When I needed to restore backups or sign Debian and RPM packages I would just enter my password, but this was infrequent enough to make an agent unnecessary.

A few things happened recently that changed this. I recently switched away from the Gmail web UI, and I now access my email using mbsync and mu4e (a topic for a future blog post). My dot files are stored in a private git repository I self-host, and I’m using app passwords for the IMAP and SMTP credentials. Even with all that, I’m not really a fan of adding plaintext passwords to git repositories. The alternative is to store these files in ~/.authinfo.gpg which can be version controlled in git. Using a real email client has also made it easier to work with PGP signed (and encrypted) email, so that was another reason to set up an agent. And finally, I wanted to start signing my git commits, and I don’t want to enter a long password every time I commit.

SSH Agent Functionality

As I started looking at the gpg-agent man page the first thing I noticed was its SSH agent functionality. I’ve been using Ed25519 SSH keys since June 2015, and have had a long and difficult battle with the GNOME keyring, which doesn’t support these keys. I had to employ crazy hacks to work around GNOME when they broke environment variables in Wayland. Then GNOME broke my agent again when they changed how sessions are initialized, and after some serious code spelunking I found out the source of the issue, and asked them to revert their “fix”. The final place I ended up was having an ssh-agent process managed in a systemd user session. This works, but has a serious drawback. Keys aren’t automatically loaded into an agent during login, and need to be manually added. The SSH protocol is very basic and doesn’t have pinentry capabilities. This meant I needed to manually run ssh-add in a terminal every time I logged in to load my keys. If I forgot to run this step, other commands using SSH (e.g. git) would prompt for the key decryption password on their TTY, and the key wouldn’t be loaded into the agent process automatically.

I was curious to see how the gpg-agent SSH functionality stacked up. It is really good, and now I’m kicking myself in the foot for not investigating this possibility earlier. GnuPG added Ed25519 support back in in August 2015, as part of the v2.1.7 release. This means that like OpenSSH, and unlike gnome-keyring-daemon (henceforth “GKD”), it can load my Ed25519 SSH keys. That’s pretty awesome. Even better, GnuPG has real pinentry support. It can ask for your decryption phrase using a regular pseudo-TTY (like running ssh-add in a terminal), using a curses application, or using a modal GNOME 3 dialog.

Ironically, gpg-agent also has full GKD integration, meaning that it works as a better GNOME SSH agent than the one GNOME ships. The way this works is that the pinentry-gnome3 input method can optionally store your key decryption passphrase in GKD.1 If you choose to store your passphrase this way your SSH key will be unlocked automatically when it’s first needed, without manually entering the passphrase. This reduces security, but it’s probably what many people want and expect.

If you want something more secure you can configure a caching policy for keys. At the highest level this is controlled by two options. Once an SSH key is loaded into memory, it will be evicted once it hasn’t been used for default-cache-ttl-ssh seconds. Regardless of how often it’s accessed, once it’s been in memory for max-cache-ttl-ssh second it will be evicted from memory, requiring you to re-enter your password. The defaults are pretty draconian, at 30 minutes and 2 hours respectively. I increased mine to 8 hours, meaning that I enter my password once or twice a day. It’s also possible to set a caching policy on a per-key basis, as I’ll explain later.

Running gpg-agent A Systemd User Service

I’m a really big fan of systemd user services. This is a feature in systemd that allows you to run services under the systemd instance managing your login session. In my opinion this is about 1000x better than the traditional way of managing user session daemons like ssh-agent and gpg-agent. The advantages of using systemd user services are:

I already manage a number of session services and timers using systemd, so naturally I set about to write systemd user services to manage my gpg-agent process. I was pleasantly surprised to see that GnuPG 2.1.16 (released in November 2016) added native support for systemd user services, meaning I didn’t need to write my own unit files. The process to use the official systemd user units isn’t very well documented, so I’ll explain it here.

The basic idea is that GnuPG includes a number of .socket and .service files. The sockets are managed by a systemd user service, and a gpg-agent or dirmngr process can be started on-demand when a socket is used. The agent process stays in the foreground and sends log output to stdout/stderr where it’s intercepted by journald. You need to copy these files to ~/.config/systemd/user to activate and use them.2

On Fedora the gnupg2 package will put these files in /usr/share/doc/gnupg2/examples/systemd-user. If you’re building from source you can find these files in the doc/examples/systemd-user directory. You should see the following files:

# On Fedora you'll find the files here.
$ ls -l /usr/share/doc/gnupg2/examples/systemd-user
total 32
-rw-r--r--. 1 root root  212 Aug 28  2017 dirmngr.service
-rw-r--r--. 1 root root  204 Aug 28  2017 dirmngr.socket
-rw-r--r--. 1 root root  298 Aug 28  2017 gpg-agent-browser.socket
-rw-r--r--. 1 root root  281 Aug 28  2017 gpg-agent-extra.socket
-rw-r--r--. 1 root root  223 Aug 28  2017 gpg-agent.service
-rw-r--r--. 1 root root  234 Aug 28  2017 gpg-agent.socket
-rw-r--r--. 1 root root  308 Aug 28  2017 gpg-agent-ssh.socket
-rw-r--r--. 1 root root 2274 Aug 28  2017 README

Copy them to your systemd user directory:

# Create the directory if it doesn't already exist.
$ mkdir -p ~/.config/systemd/user

# Copy the files.
$ cd /usr/share/doc/gnupg2/examples/systemd-user
$ cp *.{socket,service} ~/.config/systemd/user

# This will save a lot of typing. I have it in my .bashrc.
$ alias sysu='systemctl --user'

# Refresh systemd.
$ sysu daemon-reload

# Enable and start the sockets.
$ sysu enable *.socket
$ sysu start *.socket

There’s one final required step: you need to tell gpg-agent where to ask for pinentry input. For pinentry in X11 or Wayland you can add the following line to your agent config:

# Set a default display for gpg-agent.
$ echo "display :0" >> ~/.gnupg/gpg-agent.conf

You can also set the GPG_TTY environment variable if you’re not using a graphical session.

Using The SSH Agent

To get the SSH agent to work you need to point SSH_AUTH_SOCK at the new SSH socket. The simplest way is to add this to your shell profile:

# Add this to ~/.profile or ~/.bash_profile
export SSH_AUTH_SOCK="/run/user/$(id -u)/gnupg/S.gpg-agent.ssh"

If you want to be really fancy you can inject the variable into your session using the set-environment command. There’s not really any advantage to doing it this way, but if you want to see what that looks like you’d change the gpg-agent-ssh.socket file like this.

There’s one final gotcha: the agent will only act delegate SSH keys with keygrips listed in ~/.gnupg/sshcontrol. You can also use this file to set caching policies on a per-key basis, which could be useful if you have multiple SSH keys and wanted to cache some of the keys longer or shorter than the others. The first time you use an SSH key with the agent you need to manually run ssh-add in a terminal. This should update the sshcontrol file and afterwards you should see something like this:

$ cat ~/.gnupg/sshcontrol
# List of allowed ssh keys.  Only keys present in this file are used
# in the SSH protocol.  The ssh-add tool may add new entries to this
# file to enable them; you may also add them manually.  Comment
# lines, like this one, as well as empty lines are ignored.  Lines do
# have a certain length limit but this is not serious limitation as
# the format of the entries is fixed and checked by gpg-agent. A
# non-comment line starts with optional white spaces, followed by the
# keygrip of the key given as 40 hex digits, optionally followed by a
# caching TTL in seconds, and another optional field for arbitrary
# flags.   Prepend the keygrip with an '!' mark to disable it.

# Ed25519 key added on: 2018-03-21 03:03:49
# Fingerprints:  MD5:39:fe:2b:cf:54:3a:c2:db:e4:b1:d2:70:a2:bc:82:fe
#                SHA256:LKeBCI5/EDJDMKzH4qBBhzJFJaBObn+APfoOzpLRpCI
A8BB036E2A737F56BB0D0B4F9700BC3DC4A9137F 0

This is only required the very first time you use a key. After the key is present in this file you can just type ssh and the GnuPG agent process will automatically be used.

  1. If you want to learn what else is stored in GKD you can use gnome-keyring-dump. I was interested to learn that Chrome stores its master password in GKD, but Firefox does not.
  2. I’ve submitted a patch to Fedora that will make this step unnecessary in the future.