GPG subkeys marked with the "authenticate" capability can be used for public
key authentication with SSH. This is done using gpg-agent which, using the
--enable-ssh-support
option, can implement the agent protocol used by SSH.
A working gpg2 setup is required. It may be possible to use gpg 1.4 but with gpg-agent compiled from gpg2. If you are using OS X 10.9 (Mavericks) then you may find the instructions here useful.
For setup purposes, you also need to the openpgp2ssh
script; this can be
removed later if you wish. openpgp2ssh
is a perl script from the
monkeysphere project which transforms OpenPGP RSA keys into OpenSSH ones.
This requires the Crypt::OpenSSL::Bignum
perl module:
% sudo cpan install Crypt::OpenSSL::Bignum
% curl http://archive.monkeysphere.info/debian/pool/monkeysphere/m/monkeysphere/monkeysphere_0.36.orig.tar.gz |
> tar -x --strip-components 2 monkeysphere-0.36/src/openpgp2ssh
% chmod +x openpgp2ssh
% cp openpgp2ssh ~/bin
If you are using OS X then you will want to disable ssh-agent otherwise launchd will keep on spawning it whenever you do anything ssh-related:
% sudo launchctl unload -w /System/Library/LaunchAgents/org.openbsd.ssh-agent.plist
If you haven't already, then generate an authentication subkey with gpg --expert --edit-key 0xAF72A573
. Be sure to toggle authentication capability
on the subkey. For the rest of this document, as an example I will use my own
key, which has a keyid of AF72A573 with authentication subkey 2F959C28.
gpg-agent creates the environment variables GPG_AGENT_INFO
, SSH_AUTH_SOCK
and SSH_AGENT_PID
, which it prints out at startup.
% eval $( gpg-agent --daemon --disable-scdaemon --enable-ssh-support )
To identify the authentication subkey it is useful to have its fingerprint:
% gpg --list-secret-keys --with-colons --fingerprint --fingerprint 0xAF72A573 |
> awk -F: '$12 == "a" { getline; print $10 }'
56505466974969D71034D4F12A40F8F32F959C28
Export the secret key and convert into from an OpenPGP RSA key into one that SSH can understand:
% gpg \
> --export-options export-reset-subkey-passwd,export-minimal,no-export-attributes \
> --export-secret-subkeys 0x56505466974969D71034D4F12A40F8F32F959C28! |
> openpgp2ssh 56505466974969D71034D4F12A40F8F32F959C28 \
> > './Andrew Ho <andrewho@andrewho.co.uk>'
Add the key with ssh-add:
% ssh-add './Andrew Ho <andrewho@andrewho.co.uk>'
This will store a copy of the key in ~/.gnupg/private-keys-v1.d/
. It will
also add the keygrip of the subkey to ~/.gnupg/sshcontrol
.
To check that it has worked you can do ssh-add -l
. You can also use ssh-add
to generate the public key counterpart to be placed in the server's
authorized_keys file:
% ssh-add -L >> authorized_keys
% scp authorized_keys user@foo:.ssh/
It is useful to have the gpg-agent daemon ready for when you want to use it. It is also useful to be able to propagate the relevant environment variables across each session so that whenever you start a new shell it doesn't kill and respawn the daemon. This can be done by placing the following snippet in your shell's startup files (implemented, for example, here):
if [[ -a $HOME/.zshrc-ssh ]]; then
. $HOME/.zshrc-ssh
export GPG_AGENT_INFO SSH_AUTH_SOCK SSH_AGENT_PID
fi
kill -0 $SSH_AGENT_PID &> /dev/null
if [[ $? -eq 1 ]]; then
eval $( gpg-agent \
--daemon \
--enable-ssh-support \
--write-env-file=$HOME/.zshrc-ssh )
fi
This will tell gpg-agent to store the environment variables in ~/.zshrc-ssh
.
As each shell starts up, it will try to contact the daemon listed in those
environment variables. If it no longer exists (e.g. if the OS has been
restarted) then it will spawn a new daemon and rewrite the environment variables
to ~/.zshrc-ssh
. It is probably possible to achieve a similar feat using
launchd.
On the face of it, forwarding the agent over SSH works much the same as with the
native ssh-agent. Simply put the following in ~/.ssh/config
:
Host foo
ForwardAgent yes
The with agent forwarding is much the same as if you were to forward the native
ssh-agent: namely, that if you always reconnect to a long-running screen
session on login then $SSH_AUTH_SOCK
in each window will probably point to
a different (likely defunct) socket as the socket location changes with each
login. The answer is to have $SSH_AUTH_SOCK
point to a static location such
as ~/.ssh-agent-sock
. This is a symlink which points to the true location of
the socket which is updated on each login. This can be done with the following
code (again, implemented here):
if [[ $SSH_AUTH_SOCK == /tmp/* ]]; then
ln -sf $SSH_AUTH_SOCK $HOME/.ssh-agent-sock
export SSH_AUTH_SOCK=$HOME/.ssh-agent-sock
fi
Hi,
thanks for the tutorial, it helped a lot!
However, I guess it needs an update I think. In newer GnuPG versions, the export became much simpler[1], the version demonstrated here is deprecated (and didn't work for me).
Simply get the listing of your keys. If you have an authentication key with elliptic curves, it might not work (again, didn't for me), so as in the tutorial above, pay attention to keep the trailing
!
for the exact key, otherwise gpg will use the newest(/strongest?) one.A full and updated version of how it's done can be found here: https://incenp.org/notes/2015/gnupg-for-ssh-authentication.html
[1] https://lists.gnupg.org/pipermail/gnupg-devel/2016-January/030682.html