Using a Yubikey for GPG in WSL (Windows Subsystem for Linux) on Windows 10
Previously, I wrote about using bash (WSL) on Windows 10. I've changed some of my setup since then, but still use something similar for when I'm developing on a Windows machine. Until recently, I have been disappointed with the lack of gpg support in wsl. GnuPG itself works fine, but you can not use a hardware smartcard device like a Yubikey.
To get around this, I installed Keybase on my WSL instance, and set up a key just for wsl. It works fine for when I only need to encrypt something for myself or use Keybase's kbfs or git support. I can't use this method if I want to sign git commits or encrypt things using my regular gpg key (stored on a smartcard) though.
Recently, I learned that it is possible to forward gpg-agent from windows to wsl and even from wsl to another server via ssh. This guide should be enough to get this running for someone else as well.
Pre-reqs
Before getting started, the following is assumed:
- WSL (Windows Subsystem for Linux) set up and working
- GPG4Win installed, and preferably set up with a known-working config on Windows
- A Yubikey with appropriate subkeys on it already
socat
andgpg
installed on WSL
Windows Setup
GPG4Win
On Windows, we'll be using GPG4Win. It can be downloaded from https://www.gpg4win.org/download.html. I used 3.1.10, but I'm assuming any recent version should work just as well.
Once it's installed, open up Kleopatra and go to Tools -> Manage Smartcards. It should look similar to this if the yubikey is detected:
After checking that your yubikey is detected, go to Settings -> Configure Kleopatra -> GnuPG System. Ensure Enable ssh support and Enable putty support are both checked. Quit Kleopatra once it's configured. If you open up %APPDATA%\gnupg\gpg-agent.conf
, it should contain this at the top of the file:
###+++--- GPGConf ---+++###
enable-ssh-support
enable-putty-support
###+++--- GPGConf ---+++###
Now we need to start the gpg-agent on Windows. One way to do this is to open a command prompt and run this:
"C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" /bye
You can check that the agent is working on Windows by listing the keys with:
"C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" "keyinfo --list" /bye
S KEYINFO 2DE54A65626EFC544C9D83A49CD38FFFEE31CE30 D - - - P - - -
OK
At this point, gpg and the yubikey should be working fine under Windows. "C:\Program Files (x86)\GnuPG\bin\gpg.exe" --card-status
should also show details of the yubikey.
npiperelay
WSL does not currently support passing usb devices into it. There are several github issues open for it, and a UserVoice that can be voted up to add support for libusb. Because of this, you'll get an error like this if you try to run gpg --card-status
from within WSL:
$ gpg --card-status
gpg: error getting version from 'scdaemon': No SmartCard daemon
gpg: OpenPGP card not available: No SmartCard daemon
Unfortunately gpg inside of WSL can't see the yubikey or any other smartcard. However, it does have access to the host Windows filesystem. gpg-agent on windows also creates a socket file at %APPDATA%/gnupg/S.gpg-agent
. Unfortunately (again) Windows doesn't really support unix sockets, so we can't just use this socket file. The socket file it creates is actually just a plaintext file referencing a tcp port and nonce:
[justyns]$ file /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent
/c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent: ISO-8859 text
[justyns]$ cat /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent
55099
;D-+U
[justyns]$
Trying to use gpg with that "socket file" in wsl will give you an error like this:
[justyns]$ gpg -K
connect to /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent port -2 failed: Bad file descriptor
connect to /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent port -2 failed: Bad file descriptor
connect to /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent port -2 failed: Bad file descriptor
connect to /c/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent port -2 failed: Bad file descriptor
gpg: can't connect to the agent: End of file
npiperelay is a tool that allows you to access Windows named pipes inside of WSL. For example, it can be used to access Docker for Windows from within WSL. At the time of writing, this tool does not actually support LibAssuan file sockets that GnuPG uses on Windows. There is a github issue open to add support: Support for LibAssuan file sockets
NZSmartie from the above issue has a PR open that adds the initial support we need. You'll need to either download the pre-compiled release from https://github.com/NZSmartie/npiperelay/releases or compile it yourself using that branch.
Note: There's actually a second PR which fixes some issues with the one mentioned above. I haven't tested that one yet, but it is probably the one that should be getting used until they're both merged into master.
Once you've either compiled or downloaded the npiperelay.exe, put it in a path accessible to both WSL and Windows. I put mine in /mnt/c/Users/justyns/bin/npiperelay.exe
to make it easy to find later.
WSL
In a WSL shell, change the command below and run it:
socat UNIX-LISTEN:"$HOME/.gnupg/S.gpg-agent,fork" EXEC:'/mnt/c/Users/justyns/bin/npiperelay.exe -ei -ep -s -a "C:/Users/justyns/AppData/Roaming/gnupg/S.gpg-agent"',nofork
In a different WSL shell, check that there is S.gpg-agent
in the $HOME/.gnupg/
directory and that you're able to see the yubikey now:
$ ls -lah ~/.gnupg/S.gpg-agent
srwxrwxrwx 1 justyns justyns 0 Jul 14 18:35 /home/justyns/.gnupg/S.gpg-agent
$ gpg --card-status
Reader ...........: Yubico Yubikey 4 OTP U2F CCID 0
Application ID ...: x
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: x
Name of cardholder: Justyn S
Language prefs ...: en
Sex ..............: unspecified
URL of public key : https://keybase.io/justyns/key.asc
Login data .......: justyns
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 0 0 0
PIN retry counter : 0 0 0
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]
If all went well, gpg --card-status
should now work! gpg -K
should also work to show you your private keys. Something to keep in mind though is that you need to import the public gpg keys into your WSL gpg keyring. Just forwarding the gpg-agent isn't enough.
Automating everything
Once you're sure the above solution is working, I'd recommend setting everything up so that gpg
in WSL automatically works without having to do anything extra.
On Windows, the gpg-agent
needs to be started when you log in. Either create a scheduled task to start it on logon, or create a shortcut in your Start Menu/Programs/Startup directory:
- Open File Explorer and navigate to
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
- Create a new shortcut to run
"C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" /bye
- Right click -> properties on the new shortcut and change it to run minimized
On WSL, we need to create a script that will automatically execute socat/npiperelay when we login:
-
Create a new file at
~/bin/gpg-agent-relay.sh
with these contents. Make sure you change the usernamd and paths if needed:#!/bin/bash # Launches socat+npiperelay to relay the gpg-agent socket file for use in WSL # See https://justyn.io/blog/using-a-yubikey-for-gpg-in-windows-10-wsl-windows-subsystem-for-linux/ for details GPGDIR="${HOME}/.gnupg" USERNAME=justyns # I use the same username for wsl and windows, but feel free to modify the paths below if that isn't the case WIN_GPGDIR="C:/Users/${USERNAME}/AppData/Roaming/gnupg" NPIPERELAY="/mnt/c/Users/${USERNAME}/bin/npiperelay.exe" PIDFILE="${GPGDIR}/.gpg-agent-relay.pid" OLDPID=$(cat "${PIDFILE}") # Launch socat+npiperelay for the regular gpg-agent if [ ! -z "${OLDPID}" ]; then ps -p "${OLDPID}" >/dev/null && \ echo "gpg-agent-relay.sh already running with process id $(cat ${PIDFILE})" && \ exit 0 fi rm -f "${GPGDIR}/S.gpg-agent*" echo $$ > ${PIDFILE} # Relay the regular gpg-agent socket for gpg operations socat UNIX-LISTEN:"${GPGDIR}/S.gpg-agent,fork" EXEC:"${NPIPERELAY} -ep -ei -s -a '${WIN_GPGDIR}/S.gpg-agent'",nofork & AGENTPID=$! # Relay the gpg ssh-agent socat UNIX-LISTEN:"${GPGDIR}/S.gpg-agent.ssh,fork" EXEC:"${NPIPERELAY} -ep -ei -s -a '${WIN_GPGDIR}/S.gpg-agent.ssh'",nofork & SSHPID=$! wait ${AGENTPID} wait ${SSHPID}
-
Make the script executable:
chmod 750 ~/bin/gpg-agent-relay.sh
-
Update
~/.bashrc
,~/.zshrc
, or whichever file is run when a new shell is spawned. Add something like this to the bottom of it:# Launch the gpg-agent-relay.sh script in the background ~/bin/gpg-agent-relay.sh &
Now if you reboot your machine, you should be able to open WSL/bash and have gpg --card-status
immediately work if you have a Yubikey plugged in.
Forwarding Yubikey and gpg-agent over SSH
While getting my Yubikey to work in WSL was my main goal, I also figured out how to forward my gpg-agent from Windows to WSL and then to a remote server over ssh. I previously had this working with git-bash, but I'm happy that it works just as easily through WSL.
In my ~/.ssh/config
, I have a shell
host configured like this:
Host shell
HostName 1.2.3.4
ForwardAgent yes
SendEnv LANG LC_*
RemoteForward /home/justyns/.gnupg/S.gpg-agent /home/justyns/.gnupg/S.gpg-agent
RemoteForward /home/justyns/.gnupg/S.gpg-agent.ssh /home/justyns/.gnupg/S.gpg-agent.ssh
Now when I use ssh shell
to connect to this machine, ssh automatically forwards the S.gpg-agent
socket files above. It does depend on ~/.gnupg
already existing, but otherwise gives me the ability to ssh to this server and then use gpg (with private keys still on the Yubikey) as normal. I wouldn't recommend doing this with any servers you do not explicitly control or trust, but can be very handy for local servers.
References
Below is a list of links that helped me get parts of this solution working, and may have additional information helpful to anyone else struggling with the same issues.
- https://developers.yubico.com/PGP/SSH_authentication/Windows.html
- https://lists.gnupg.org/pipermail/gnupg-users/2016-July/056340.html
- https://lists.gnupg.org/pipermail/gnupg-users/2016-October/056836.html
- https://security.stackexchange.com/questions/166280/how-to-use-yubikey-through-gnupg-on-remote-server
- https://wiki.gnupg.org/AgentForwarding
- https://www.hanselman.com/blog/HowToSetupSignedGitCommitsWithAYubiKeyNEOAndGPGAndKeybaseOnWindows.aspx
- https://codingnest.com/how-to-use-gpg-with-yubikey
- https://blog.onefellow.com/post/180065697833/yubikey-forwarding-ssh-keys
Common Issues
I'll try to keep this section up to date with any errors I run across while setting this up on new machines. Hopefully it will help someone else as well.
If you run into any issues getting this working, please feel free to reach out to me and I'd be happy to help if I can!
gpg --card-status doesn't detect any devices
gpg: selecting openpgp failed: No such device
gpg: OpenPGP card not available: No such device
I kept running into the above error when doing gpg --card-status
from a windows command prompt or git-bash. The issue ended up being because git-bash installs its own version of gpg
and that takes precedence in the PATH
. You can either alter your PATH
variable to prefer GPG4Win or use full paths to the binaries like I did above.
gpg-agent socket already exists
2019/07/14 19:22:03 socat[11585] E "/home/justyns/.gnupg/S.gpg-agent" exists
The above error usually happens if gpg-agent
is already running in WSL. Just kill any existing gpg-agent
processes and then try the gpg-agent-relay.sh
script again.