Clicky

Harry R. Schwartz

Software engineer, nominal scientist, gentleman of the internet. Member, ←Hotline Webring→.


Configuring gpg-agent on a Mac

Published 05 Nov 2014. Tags: beards, computer-science, email, security.

Update: As of GPG 2.1 all this might no longer be necessary. It looks like gpg-agent is started automatically. Hurray!

In my last article I detailed setting up GPG and git to automatically sign all of your git commits. This is a reasonable thing to do, but there’s an issue. GPG will prompt for your passphrase every time you need to sign something, so if, say, you’re rebasing a branch with twelve commits, you’ll need to type your passphrase twelve times.

If crypto’s hard to use, you’ll eventually give up and disable it. Enter gpg-agent.

gpg-agent is a daemon that caches your GPG password. When GPG needs authentication it’ll check gpg-agent first to see if the password is cached. If it’s cached, gpg-agent will return it; if not, gpg-agent will prompt the user to enter their password through a pinentry program and store it for a certain amount of time.

GPG needs some information to know how to connect with gpg-agent. It expects this information to be stored in the GPG_AGENT_INFO environment variable.

Only one instance of gpg-agent should be running per machine, so we don’t want to start it in our .bashrc. Instead, we want to start it as a daemon when the computer boots. On most other Unixes this would be fairly easy (we could start it in our login shell, or maybe in our .xinitrc), but it’s a bit more involved on a Mac because Apple uses launchd to start its services. Writing such a service requires creating an associated plist in XML, and I just don’t know how to do that. Instead I used the lovely LaunchControl program to create a launchd job for me.

The Steps

Install LaunchControl by downloading and installing the .app file.

Run brew install gpg-agent. pinentry should have been installed as a dependency.

Add a line containing use-agent to your ~/.gnupg/gpg.conf file. This tells GPG to look for gpg-agent and to use it if it’s available.

Configure gpg-agent in ~/.gnupg/gpg-agent.conf. Options are summarized in the documentation, but I use:

default-cache-ttl 600
max-cache-ttl 7200
pinentry-program /usr/local/bin/pinentry

In particular, make sure that the pinentry-program points to the pinentry you just installed through brew!

Create, configure, and load a new job in LaunchControl. The job should run the following script:

/usr/local/bin/gpg-agent --daemon --enable-ssh-support --write-env-file /Users/<you>/.gpg-agent-info

Your configuration panel should look like this when everything’s set up right:

LaunchControl for gpg-agent

Read the environment variables in your .bashrc. Since gpg-agent wrote its configuration to ~/.gpg-agent-info, I used a script to scrape that:

if [[ $(uname) == Darwin ]]; then
  if [ -f "${HOME}/.gpg-agent-info" ]; then
    . "${HOME}/.gpg-agent-info"
    export GPG_AGENT_INFO
    export SSH_AUTH_SOCK
  fi
fi

Set GPG_TTY in your .bashrc. This tells pinentry where to prompt for the password. If isn’t set pinentry will silently fail and you’ll be in for an extremely frustrating hour or so.

export GPG_TTY=$(tty)

Restart bash to reload the configuration. GPG should now cache passwords! If it doesn’t, you may need to reboot your machine to reset launchd.

Note that in the above snippet I first checked that $(uname) == Darwin. This isn’t necessary if you only use a Mac. However, if you share your dotfiles with non-Mac machines (like I do), and if you start your agent through your .xsession (or something similar) on those machines, you’ll want to avoid running this script, since your agents will be constantly stepping on each others’ toes.