Since one of the recent upgrades of Enigmail (the GPG extension for Thunderbird) and completely switching to GPG version 2 on my Macbook I ended up with the situation that Enigmail did not cache the key passphrases anymore and I had to enter them over and over again. This is caused by the fact that GPG2 requires to use gpg-agent
and Enigmail’s internal passphrase management cannot be used anymore. Therefore, a setup is required that enabled the gpg
processes spawned by Enigmail to talk to a running gpg-agent
instance.
gpg-agent
is a small utility daemon that handles passphrase caching. A gpg
process can talk to a running gpg-agent
once it knows where the agent can be reached. This information is obtained from the environment variable GPG_AGENT_INFO
. The content of this variable looks something like this: /Users/youruser/.gnupg/S.gpg-agent:38959:1
. In case of programs started in a shell, starting gpg-agent
on demand and exporting the correct environment variable is easily possible with a few lines of shell configuration code. However, Thunderbird and therefore also Enigmail are usually started via the OSX GUI and not from within a shell. Therefore, the GPG_AGENT_INFO
variable needs to be exported by the process which manages launching graphical programs (via Spotlight). This is launchd
on OSX. Fortunately, launchd
has command line options to control the environment it uses to start new processes, which we can take advantage of.
For the setup I am using now, I start a gpg-agent
process with my graphical login. To do so, I have adapted this solution, which starts gpg-agent
at login, but does not export the environment variables inside launchd
.
The first step is to create a plist-file, which is a configuration for launchd
instructing it to start gpg-agent
at login. Create ~/Library/LaunchAgents/org.gnupg.gpg-agent.plist
with the following contents (shamelessly stolen from the aforementioned blog post):
<xml version="1.0" encoding="UTF-8"?>
<DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.gnupg.gpg-agent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/youruser/bin/start-gpg-agent.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Replace youruser
with your actual OSX username. launchd
automatically processes plist files from the ~/Library/LaunchAgents
directory.
As the plist only dispatches to a shell script called /Users/youruser/bin/start-gpg-agent.sh
, we need to create this script, which does the real magic of starting gpg-agent
and exporting the variables. Create it with the following contents (adapt the gpg-agent
path to your installation):
#!/bin/bash
if test -f "$HOME/.gpg-agent-info" && \
kill -0 "$(cut -d: -f 2 "$HOME/.gpg-agent-info")" 2>/dev/null
then
echo "already running" > /dev/null
else
/usr/local/bin/gpg-agent -c --daemon --write-env-file > /dev/null
fi
if [ -f "${HOME}/.gpg-agent-info" ]
then
socket="$(cut -d= -f2 "$HOME/.gpg-agent-info")"
launchctl setenv GPG_AGENT_INFO "${socket}"
else
echo "gpg-agent did not write info file"
fi
The first part of the script starts gpg-agent
in case no other gpg-agent
instance is already running. A running instance of gpg-agent
puts its connection information in a file called ~/.gpg-agent-info
in case it was started with the --write-env-file
option. The second half of the shell script parses this file and exports the GPG_AGENT_INFO
variable inside launchd
with the launchctl setenv
command. Afterwards, every graphically launched program has knowledge about the running gpg-agent
instance and can connect to it.
In case a gpg
process (e.g. spawned by Enigmail) now wants to interact with one of your keys, it will dispatch the passphrase work to gpg-agent
. If a passphrase has not been provided to gpg-agent
yet, or the last entry is longer ago than the configure TTL (time to live), gpg-agent
needs a way to prompt for a new passphrase. Therefore it is important that a graphical pinentry program is configured for gpg-agent
. This is done inside the file ~/.gnupg/gpg-agent.conf
. Ensure that in this file at least the following line is present (adapt the path as required):
pinentry-program /usr/local/bin/pinentry-mac
This instructs gpg-agent
to use the pinentry-mac program (from the GPGTools project, can e.g. be installed via homebrew) for requesting a passphrase.
After performing all these steps, you can logout and back in again. In a terminal you should now see that gpg-agent
is running, e.g. via ps -ef | grep gpg-agent
and also the GPG_AGENT_INFO
variable should be present (echo $GPG_AGENT_INFO
). Enigmail should be able to interact with gpg-agent
and passphrases will only be requested once per TTL.
Update:
Starting with GPG 2.2 this is probably not necessary anymore, since it seems that GPG itself now implemented a system to start the agent if it is not running.