My Perfect GnuPG / SSH Agent Setup

Now that I’m using OpenPGP cards for GnuPG, I may as well start using them for their other bells and whistles too. The first and most useful such extra feature of those cards is using the authentication key for SSH.

NOTE: I have started rewriting this post over on my wiki, so that anyone can update it. Newer instructions will be added there; this post will not see further updates. (2017-10-25)

Update 2017-06-28: You should also look at Lars Wirzenius’s post on Using a Yubikey 4 for ensafening one’s encryption.

Getting this working is actually surprisingly simple. You simply need to make sure you have an authentication-only RSA key loaded in the card’s 3rd (Authentication key) slot, and use gpg-agent‘s SSH agent:

bootc@ripley ~ $ gpg2 --card-status 
Application ID ...: D276000124010200000500001BDE0000
Version ..........: 2.0
Manufacturer .....: ZeitControl
Serial number ....: 00001BDE
[...]
Authentication key: 087A F7A1 0BA2 A8E2 7419  5FB6 3F19 5C4B 5DC6 DEA7
      created ....: 2013-06-03 22:56:41
[...]
bootc@ripley ~ $ killall ssh-agent gpg-agent
bootc@ripley ~ $ unset GPG_AGENT_INFO SSH_AGENT_PID SSH_AUTH_SOCK
bootc@ripley ~ $ eval $(gpg-agent --daemon --enable-ssh-support)
bootc@ripley ~ $ ssh-add -l
4096 4b:92:1e:c9:3f:b2:72:74:13:78:94:43:48:d6:66:2b cardno:000500001BDE (RSA)
bootc@ripley ~ $

If you followed my previous guide to setting up an OpenPGP card, you’ll already have the right key in the correct slot, so you’re all set.

The hard part isn’t getting the agent to talk to the card or anything like that, but using the correct agent when you login to your session. On Linux you’ll generally either get the normal OpenSSH ssh-agent running, or maybe GNOME Keyring’s SSH agent, or some other agent from a variety of other packages; so we’ll need to work out how to disable those agents and use the GnuPG agent instead.

1. Linux: Starting the correct agent at login

TL;DR (quick setup):

$ echo enable-ssh-support >> ~/.gnupg/gpg-agent.conf
# editor /etc/X11/Xsession.options
< comment out "use-ssh-agent" >
# aptitude purge libpam-gnome-keyring

Long version:

When you installed the gpg-agent package, it installed an Xsession script into /etc/X11/Xsession.d/90gpg-agent that automatically starts the GnuPG agent for you. You probably already have the gpg-agent running without knowing, so part one of the equation has already been sorted out for you.

Xfce4 also starts the agent for you in /etc/xdg/xfce4/xinitrc in a slightly different way to the above, as long as you don’t already have an agent running. This is perfectly adequate for us as well.

By default, the gpg-agent doesn’t enable its SSH agent. You have to explicitly enable it yourself, either by passing --enable-ssh-support on the command-line (which we could do by editing the Xsession script) or by adding a line to a configuration file. The latter is a much tidier way of doing this, so here goes:

bootc@ripley ~ $ echo enable-ssh-support >> .gnupg/gpg-agent.conf
bootc@ripley ~ $

Next time we login and start the gpg-agent, it will enable its SSH mode for us. Great!

Finally, we have the problem of running other SSH agents. The OpenSSH agent starts by default, set up by /etc/X11/Xsession.d/90x11-common_ssh-agent from the x11-common package. If you’re running GNOME or any of its services, it’s likely you’ll be running the GNOME Keyring agent as well. Both of those will interfere with our GnuPG agent.

Fortunately, disabling the OpenSSH agent is very simple: edit /etc/X11/Xsession.options (as root) and comment out the use-ssh-agent line. Job done.

Disabling the GNOME Keyring agent is potentially a bit more complicated. I don’t know what the implications are of doing this if you’re actually running full-fat GNOME on your system, but as I’m using Xfce I can easily get away without. In my case, I had to un-install the libpam-gnome-keyring package (aptitude purge libpam-gnome-keyring as root) and make sure that Xfce didn’t start GNOME services (SettingsSession and StartupAdvancedLaunch GNOME services on startup – untick if ticked).

Now you should be able to logout of your desktop session, login again, and immediately see your key loaded in your agent as long as your card is plugged-in:

bootc@ripley ~ $ ssh-add -l
4096 4b:92:1e:c9:3f:b2:72:74:13:78:94:43:48:d6:66:2b cardno:000500001BDE (RSA)

If that isn’t the case, first make sure your SSH_AUTH_SOCK environment variable is pointing at your gpg-agent. You should see something along the lines of this:

bootc@ripley ~ $ echo $SSH_AUTH_SOCK
/tmp/gpg-QDGtCE/S.gpg-agent.ssh

The important part is the last path element, S.gpg-agent.ssh. If it doesn’t look like that, you may have another agent interfering with your GnuPG agent. The SSH_AGENT_PID variable should contain the PID of the process acting as the agent, which may help pinpoint the culprit:

bootc@ripley ~ $ ps u -p $SSH_AGENT_PID
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
bootc    12731  0.0  0.0  19148  1144 ?        Ss   11:04   0:00 gpg-agent --daemon --enable-ssh-support --write-env-file /home/bootc/.cache/gpg-agent-info

That’s it, your agent is running correctly.

2. Windows: Using PuTTY and Pageant (Updated 2013-09-28)

PuTTY and Pageant know nothing about smart cards at all. Since Gpg4win 2.2.0 (GnuPG 2.0.21), the gpg-agent can natively interact with PuTTY; you just need to explicitly enable PuTTY agent support and start the agent at login.

To enable PuTTY agent support in gpg-agent, you need to explicitly enable it. You can do this with GPA (GNU Privacy Assistant, part of Gpg4win): look in Backend Preferences in the Edit menu. Alternatively, you can manually edit the gpg-agent.conf file. It usually resides in %appdata%\gnupg (or C:\Users\%username%\AppData\Roaming); make sure the gpg-agent.conf file has the line enable-putty-support in it somewhere. Then, you have to restart the agent for this to take effect: open a command prompt and run gpg-connect-agent killagent /bye to stop the agent, then gpg-connect-agent /bye should start it again.

The final step in the puzzle is to get the gpg-agent to start when you login to Windows, otherwise you need to do something GPG-related to get the agent to auto-start before you can SSH anywhere. You can do this by creating a shortcut to "C:\Program Files (x86)\GNU\GnuPG\gpg-connect-agent.exe" /bye and placing it in your Startup program group in your Start menu. Changing the Run: setting from Normal window to Minimised makes it slightly less obtrusive at login.

Thanks to Stephanie Daugherty, who emailed me to tell me about PuTTY agent support having been added in GnuPG 2.0.21. Discussion ensued, and I updated this post in response (as well as my own SSH agent setup in Windows).

Below is older information that has been superseded by the above:

There is a fork of PuTTY known as KiTTY that includes some support, but requires additional PKCS#11 modules to function correctly, and I never really got on with it. By far the simplest method I found is to replace PuTTY’s Pageant with a custom version with smart card support. This has the advantage of working with other tools that can speak to Pageant like WinSCP. You’ll want the first of the pageant.exe downloads.

Note that there appears to be some issue with running both the custom Pageant program and GnuPG; you essentially have to choose between running one or the other. If you want to use GnuPG (via Enigmail in Thunderbird for example), you’ll need to close the Pageant program. If you then want to use your SSH key again, unplug your card reader, start Pageant again and plug the reader back in.

3. Mac OS X: GPGTools and SSH

The standard GPGTools distribution of GPG for Mac OS X, including the GPGMail plugin, works fine with the smart cards. Unfortunately, while the gpg-agent has support for running from launchd, the most recent GPGTools doesn’t install a LaunchAgent for it. I’m still trying to come up with a good way of getting this working properly; once I settle on a good solution I’ll post an update.

So far, I have written a custom LaunchAgent that runs gpg-agent at login, but the agent appears to have some bugs related to USB and sleeping. When my laptop has been asleep for some time, the gpg-agent seems to cause USB to lock up for seconds at a time, during which I can’t use the keyboard or touchpad. The only way to get thing to behave again is to kill gpg-agent and re-launch it, but because of its launchd integration you then lose access to the agent for SSH.

More to come once I’ve worked out a good way around these issues.

4. Setting up public keys / authorized_keys

You may have noticed that the output of ssh-add -l isn’t useful for putting into .ssh/authorized_keys. There is a slightly different option you can pass to ssh-add to make it spit out the public key in a useful format:

bootc@ripley ~ $ ssh-add -L
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDxAZn1IQ2cBxIbgwksWOfkAMKKLa3cUYMkbQBaR9Nw4CfoLs8xiu0Kb8oN4JH6p+E4C1MrlmFQuMZbVzs9JseV2pe6kw0xKQgLINopyF6letzCOEzPH7THicxyQc35vMIa8JTAMU6X3hpzzSUVSQGKDljj+c4XayTZCVQVg2Yqc67Vdm+4q4OQCU7Fns73KWmqwsdYtuyk74yPWjAvKkEaW7I9d3TLKVI8LLdzC6FoP2jJyGEoqxWEf2yL0eWelmJi/ikLJFSdXvdVCzvyI3dTeNqEdaisKQ0SJ7W0ysH1Os2hYyxBazWonMYI/T8Sh9J21xcWGmBumFTIcsbLEP17tojR4ttFq69ebtJIMkbPo0e+u4gWdvM44MyWsDm8jkKDuqNcduGIhF0dFY57niq4TEv5+Yvya2gwqBS4ttq/NlUAseL4zAcaP+kpDae4GMiRXwpFAiKA3ctn6/gf5QLvcAHMz62ASHeo9gG9t6n0eGUzBD/lv0qMsaYgmxfgIpqoU6Sr1w2EVp8TYjIVAaO/96Kljb2v9mB+0/BTO7gxJicxUNYQLOhEYdMnbr0bFNAG93hlUiq5eGTTG7nn1mre2OHWyGB8fZN9EukbMeFicgFTxgl3ddQawjn1Qb6u//ZpSCD++IH4HQCjz1fI9r+yZ+6CqfUrM0PI+dwAfcL4pw== cardno:000500001BDE
bootc@ripley ~ $

You can then put that into your authorized_keys file and start using your OpenPGP smart card for password-less logins (you will be asked for your card PIN the first time you use your key in a session).

5. SSH Agent within screen sessions

If you use screen, you’ll know that screen sessions will often out-live logins on your desktop. screen will inherit variables passed to it at login so you can access your SSH agent within your screen, but if you re-attach an old screen to a new desktop session you’ll find your SSH agent no longer works because the SSH_AUTH_SOCK is pointing to a long-gone socket.

The way I fix this problem is via some minor symlink trickery. I have configured my .profile to create/update a symlink in /tmp that always has the same name and points at the current SSH agent socket. screen can then use the symlink in its SSH_AUTH_SOCK variable, and always have access to the current agent.

Note that recent Linux kernels (3.6 onwards) have enabled certain protections for symlinks within world-writeable sticky directories (exactly like /tmp), so we need to create a directory within /tmp to put our symlink in instead. This is what my code does.

I added the following to my .profile:

# set up SSH agent socket symlink
export SSH_AUTH_SOCK_LINK="/tmp/ssh-$USER/agent"
if ! [ -r $(readlink -m $SSH_AUTH_SOCK_LINK) ] && [ -r $SSH_AUTH_SOCK ]; then
	mkdir -p "$(dirname $SSH_AUTH_SOCK_LINK)" &&
	chmod go= "$(dirname $SSH_AUTH_SOCK_LINK)" &&
	ln -sfn $SSH_AUTH_SOCK $SSH_AUTH_SOCK_LINK
fi

And the following to my .screenrc:

setenv SSH_AUTH_SOCK "$SSH_AUTH_SOCK_LINK"

Now my screen sessions always use the same path, but the symlink changes when I login:

bootc@jiji ~ $ echo $SSH_AUTH_SOCK
/tmp/ssh-bootc/agent
bootc@jiji ~ $ ls -l $SSH_AUTH_SOCK
lrwxrwxrwx 1 bootc bootc 30 Jun  9 12:08 /tmp/ssh-bootc/agent -> /tmp/ssh-xCF06knRMQ/agent.5587

Note that this also works for remote screen sessions with SSH agent forwarding, not just local sessions.

6. SSH Agent through sudo

By default, sudo doesn’t pass through environment variables. Generally, this is a very good security measure, so you shouldn’t just go and disable this functionality. It protects against attacks using LD_PRELOAD for example. That being said, wouldn’t it be great if our SSH agent worked from within sudo?

The good news is that it’s very easy to enable passing through of the SSH_AUTH_SOCK variable only, and we can do it per-user or per-command as well rather than allowing just everyone to do this. My example (below) allows a single user, bootc (me!) to pass-through the environment variable for all commands I run.

Simply create a file in /etc/sudoers.d (I called mine /etc/sudoers.d/bootc-ssh_auth_sock) that reads:

Defaults:bootc env_keep += "SSH_AUTH_SOCK"

You then need to make sure it has the correct permissions:

jiji bootc # chown root: /etc/sudoers.d/bootc-ssh_auth_sock
jiji bootc # chmod ug=r,o= /etc/sudoers.d/bootc-ssh_auth_sock
jiji bootc # ll /etc/sudoers.d/bootc-ssh_auth_sock
-r--r----- 1 root root 43 Jun  9 12:06 /etc/sudoers.d/bootc-ssh_auth_sock

You can then see that everything works as expected:

bootc@jiji ~ $ echo $SSH_AUTH_SOCK
/tmp/ssh-bootc/agent
bootc@jiji ~ $ sudo -s
jiji bootc # echo $SSH_AUTH_SOCK
/tmp/ssh-bootc/agent
jiji bootc # ssh-add -l
4096 4b:92:1e:c9:3f:b2:72:74:13:78:94:43:48:d6:66:2b cardno:000500001BDE (RSA)

2 thoughts on “My Perfect GnuPG / SSH Agent Setup

  1. You seem to be having the exact same problems I’m having re: launchd and ssh. I get the same freezes when I unplug my reader (Gemalto shell token, same as yours). I’ve found that killing scdaemon is enough to stop the freezes from happening, but like you say, it’s necessary to start a whole new instance of gpg-agent to get it all working again.

    If you come up with a good solution to the problem I’ll be very interested to hear.

  2. If you are the Chris Boot who was at Village Camps in Leysin, Switzerland — I would love to hear from you.

Comments are closed.