Skip to content

Instantly share code, notes, and snippets.

@andrewlkho
Last active March 25, 2024 03:37
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save andrewlkho/7373190 to your computer and use it in GitHub Desktop.
Save andrewlkho/7373190 to your computer and use it in GitHub Desktop.
How to use authentication subkeys in gpg for SSH public key authentication

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.

Requirements

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

Generate an authentication subkey

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.

Start gpg-agent

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 )

Tell gpg-agent about the key

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/

How to start gpg-agent automatically

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.

Agent forwarding

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
@sc4ryb3ar
Copy link

I have a few questions about this.

Firstly though, my setup: Dell laptop running Manjaro Linux (Arch based distro) and gnupg 2.1.13(could be .15? Whichever is the newest)

Secondly, what I'm trying to do: 2 thing;. 1. have my gpg authentication subject, and most importantly 2. Have GitKraken recognize and use the ssh key output from the gpg authentication subject.

Things that I've found on the interwebs, that absolutely do not work, no matter what I try......: Monkeysphere, no matter how many different ways I try, I cannot get monkeysphere to actually install on my system, to date I've tried about 14 different ways.... Gnupg's own gpg2sshkey command, as of gnupg 2.1+, this has been removed and replaced with -export-ssh-key, this is supposed to strip the password and display the ssh formated public info, that part works, I can also use that command to report it into a id_rsa.pub file.... But now there is no way to get the id_rsa "private" key info. I've tried --export-secret-keys, and have been able to get an octet file name whatever I want, but I can't access because not knowing the password, --export-options -export-reset-subkey-passwd is no longer a valid command, so that's a dead end, unfortunately...working with this last bit has gotten me as close as I have gotten for id_rsa "private" key

What I've succeeded in doing, so far (after about 40 hrs of googling and trial and error....): I have used gpg2 --with-keygrip command to get and be able to place the gpg authentication subkey keygrip info into the gnupg sshcontrol file. I have been able to start the gpg-agent with the --enable-ssh-support, and I have been able to use the ssh RSA info from the --export-ssh-keys to connect and validate to GitHub through the CLI (btw, after so much trying this was huge win for me!!!! And at 3am out on my porch I "woohoo'd" right out loud.....it was awkward)

Here's where I'm currently stumped though.....GitKraken (and yes I know there are other git gui's available) to the most part it is a fully self contained program, that o my uses some of the .gitconfig, not all. In it's GUI interface, you can connect to github via ssh(only, no gpg option) by one of two ways. 1click the "use local ssh", in which it will create it's own ssh key internally, and connect you through that. Or 2, select and use your own public and private ssh key files.... So even though I was victorious with connecting my computer to github, I now have no physical private and public paired key files (although I could have a public one if I wanted, but it won't accept JUST the id_rsa.pub, it wants private too) to point GktKraken at.

So at this point, what in the world do I do? Have any thoughts, ideas, or suggestions for the making of paired ssh keys from the gpg authentication subkey for specific use inside the GitKraken program?

@TimRepke
Copy link

TimRepke commented Mar 18, 2017

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.

gpg --export-ssh-key 0x0x56505466974969D71034D4F12A40F8F32F959C28!

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

@trendsetter37
Copy link

Hey @TimRepke, I keep getting agent has no identities when opening a new zshell.

gpg-connect-agent /bye
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

SSH_ENV="$HOME/.ssh/environment"

function start_agent {
    echo "Initialising new SSH agent..."
    /usr/bin/ssh-agent | sed 's/^echo/#echo/' > "${SSH_ENV}"
    echo succeeded
    chmod 600 "${SSH_ENV}"
    . "${SSH_ENV}" > /dev/null
    /usr/bin/ssh-add;
}

# Source SSH settings, if applicable
if [ -f "${SSH_ENV}" ]; then
    . "${SSH_ENV}" > /dev/null
    #ps ${SSH_AGENT_PID} doesn't work under cywgin
    ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || {
        start_agent;
    }   
else
    start_agent;
fi

@TimRepke
Copy link

Hey @trendsetter37,
sorry, I totally didn't see your question but just found this thread via google trying to fix my setup...

I dont have all that stuff in my zshrc that you added. What I do have is the following:

~/.gnupg/gpg-agent.conf

default-cache-ttl 300
max-cache-ttl 1000 
enable-ssh-support

~/.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.

SOMELONGIDOFTHEKEY 0

~/.gnupg/gpg.conf

...
# Try to use the GnuPG-Agent. With this option, GnuPG first tries to connect to
# the agent before it asks for a passphrase.
use-agent
...

~/.ssh/config

Host localhost
ForwardAgent yes
AddKeysToAgent ask

Not sure if that's all it takes, I don't remember, sorry. But I can definitely confirm, that there's nothing in my zshrc relating to SSH or GPG!

@ezoer
Copy link

ezoer commented Feb 5, 2023

@trendsetter37 Today I learned that this is because you may not have key pairs which support authentication. If you're using gpg, just edit your primary key and then add a new key of type (8) RSA. Then edit the capabilities to support Authentication (A) only. This will be the key which will be recognized by ssh as usable. There is more to getting everything working depending on your particular setup. Mine currently is Windows based and I followed this guide to configure most of the GitHub SSH integration: https://www.dionysopoulos.me/windows-10-gpg-ssh-and-github.html

(And, yes, I hope you did solve it in the meantime. It has been almost 6 years since you asked this question. ;))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment