Yubikey GPG and SSH Auth on Windows and WSL

Coming up with this setup required a few steps and a few different guides. I thought it might be useful to put the whole setup online in one post for people to find. This post assumes you already have a GPG auth key set up on your YubiKey. YubiCo has a good writeup about it here.

The steps to do this have been brought together from several sources, including a large part from this post from justyn.io however I have done some things differently after running into the odd problem or difference of opinion.

Yubikey SSH Auth in Windows using GPG

The first step involves getting SSH authentication working in windows. There's a couple of gotchas I've encountered, mainly relating to the current version of GPG accessing the yubikey in Windows. This will provide ssh access to both putty and windows ssh client.


First step is to install GPG4Win. This can be downloaded from their website or using winget.

> winget install GnuPG.Gpg4win

Now start the gpg daemon with

> gpgconf --launch gpg-agent

Check that gpg can see your YubiKey by inserting it and running

> gpg --card-status

Some versions of GPG had trouble reading the YubiKey in which case you may need to configure scdaemon. YubiCo have documentation for this here. The current version I'm using (GPG4Win 3.1.16/GnuPG 2.3.3) doesn't seem to need this.

You can then import your key to the local keyring. With the YubiKey inserted, run:

> gpg --card-edit

Then you should set the trust of the key to ultimate. Run:

> gpg -k
> gpg --edit-key [keyID]


YubiCo's configuration instructions are available here however they include several options which are unnecessary. GPG on windows uses the path %appdata%\gnupg\ for its configuration files. Create a file gpg-agent.conf file and there's only one line that needs to be added:


(Other options provided by YubiCo can be included however the SSH support by enable-ssh-support doesn't seem to be usable by Windows and we'll be using another tool to provide that socket from the putty/pageant support. The use-standard-socket option is enabled by default on Windows and the cache-ttl options are also set to the defaults in their list of options.)

Of course you can add any other configuration relevant to your setup.

Autostart GPG with Windows

This can be achieved in several different ways, however the simplest seems to be just to create a shortcut in the startup directory. Open the run windows (win+R) and type shell:startup and hit enter. Create a new shortcut and set the target to:

"C:\Program Files (x86)\GnuPG\bin\gpgconf.exe" --launch gpg-agent

Adjust the install path to the executable as necessary.

Windows Bridge

The Windows side of the bridge provides a standard SSH Auth socket using wsl-ssh-pageant and sets the SSH_AUTH_SOCK environment variable. I use the -gui version of wsl-ssh-pageant so that it can be launched in the background without blocking the console window. I then use a basic powershell script to be run at startup to set up the environment. (Update the script to set the path to the executable)

$winsshSockName = "ssh-pageant"
setx SSH_AUTH_SOCK \\.\pipe\$winsshSockName
& [path-to-executable]\wsl-ssh-pageant-amd64-gui.exe --systray -winssh $winsshSockName

Then in the startup directory (shell:startup) I create another shortcut. This I set the target to:

powershell.exe -windowstyle hidden -ExecutionPolicy RemoteSigned -File [path-to-script]\wsl-ssh-pageant.ps1

(Remember to set the path correctly.)

Linux Bridge

This step requires an extra binary in Windows, and socat installed in your WSL environment.

In Windows, you'll need a copy of npiperelay from jstarks with support for libAssuan support (a version is available from here) or the fork wsl-relay from Lixcality.

(This information and tool was found from the post from justyn.io and this is the core tying WSL to Windows).

Then from your WSL environment, make sure you have socat available. Make sure you have a .gnupg directory in your home directory. Then in either your .profile or .bash_profile add the following, modifying the PIPE_RELAY_BIN to point to where you have put the chosen pipe-relay binary in Windows:

PIPE_RELAY_BIN=/mnt/c/[windows path to pipe relay].exe


if [[ ! -S $GPG_SOCKET ]]; then
	socat UNIX-LISTEN:$GPG_SOCKET,fork EXEC:$PIPE_RELAY_BIN' -ei -ep -s -a "C:/Users/[username]/AppData/Local/gnupg/S.gpg-agent"',nofork &

export SSH_AUTH_SOCK=$HOME/.misc/ssh-agent.sock
if [[ ! -S $SSH_AUTH_SOCK ]]; then
	socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:$PIPE_RELAY_BIN' -ei -s //./pipe/ssh-pageant',nofork &

(This code has issues with incorrectly terminated socat instances, see the update below for the fix)

The path to the pipe relay is using the wsl mount to windows, however the argument to the pipe relay for both the gpg-agent socket file and the UNC path to the SSH Auth socket are in the Windows format (although with forward slashes in file paths) as it is a Windows binary.

GnuPG on windows used to place the sockets in the remote app data folder, but this seems to have changed recently, so this setup has used the Local appdata folder. Make sure you also update the username field to match your Windows home directory.

You should finally copy your pubring.kbx and trustdb.gpg files from your Windows gnupg config folder to $HOME/.gnupg in your WSL environment


In some instances, such as rebooting with wsl open, socat processes may get uncleanly terminated, leaving behind the socket files, which stop the socat instances from launching. To handle this the following should replace the additions to .profile

PIPE_RELAY_BIN=/mnt/c/[windows path to pipe relay].exe


if ! pgrep -a socat | grep $GPG_SOCKET; then
	if [ -S $GPG_SOCKET ]; then rm $GPG_SOCKET; fi
	socat UNIX-LISTEN:$GPG_SOCKET,fork EXEC:$PIPE_RELAY_BIN' -ei -ep -s -a "C:/Users/[username]/AppData/Local/gnupg/S.gpg-agent"',nofork &

export SSH_AUTH_SOCK=$HOME/.misc/ssh-agent.sock
if ! pgrep -a socat | grep $SSH_AUTH_SOCK; then
	if [ -S $SSH_AUTH_SOCK ]; then rm $SSH_AUTH_SOCK; fi 
	socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:$PIPE_RELAY_BIN' -ei -s //./pipe/ssh-pageant',nofork &

Update 2:

I've had issues with the socat processes terminating at odd times, which requires them to be restarted. To help with this use setsid to launch the processes outside of the bash environment and I separated out the code to launch them into their own script which is sourced from within .profile with:

. gpg-wsl-bridge.sh



## Autorun for the gpg-relay bridge

if ! pgrep -a socat | grep -q $GPG_SOCKET; then
	if [ -S $GPG_SOCKET ]; then rm $GPG_SOCKET; fi
	setsid -f socat UNIX-LISTEN:$GPG_SOCKET,fork EXEC:$PIPE_RELAY_BIN' -ei -ep -s -a "C:/Users/shaun/AppData/Local/gnupg/S.gpg-agent"',nofork >/dev/null 2>&1 < /dev/null

export SSH_AUTH_SOCK=$HOME/.misc/ssh-agent.sock
if ! pgrep -a socat | grep -q $SSH_AUTH_SOCK; then
	if [ -S $SSH_AUTH_SOCK ]; then rm $SSH_AUTH_SOCK; fi 
	setsid -f socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:$PIPE_RELAY_BIN' -ei -s //./pipe/ssh-pageant',nofork >/dev/null 2>&1 </dev/null

This script can then be called to restart the processes as needed.

If you've found this article helpful and would like to support me, would you consider buying me a coffee?