Yubikey, gpg, ssh and WSL2

Update on 07-07-2020

Thanks to the work of Anders Ingemann, the setup process has been simplified.

Using a smartcard to hold your GPG key can be great. Personnaly, I use a Yubikey. Although, by default, you cannot use any device from WSL2, including your smartcard.

To circumvent this issue, I came across several articles treating this topic. Here is what worked for me and a summarized how-to.

We will consider you have your yubikey with gpg keys setup on it, we also consider you use Gpg4Win.



As explained in justyn blog article, we will use npiperelay to use gpg4win gpg-agent from WSL2 environment.

Enable windows gpg ssh support and prepare pageant

If you use the GPG keys in your smartcard to authenticate through SSH, you will have to make changes too. Several approaches exists, this one uses https://github.com/benpye/wsl-ssh-pageant and npiperelay (as seen previously) to use windows GnuPG’s ssh pageant.

First, enable SSH support by:


Then, visit wsl-ssh-pageant, download the latest version to:

Setup the daemon in the WSL2’s side

Thanks to Anders Ingemann’s work, you’ll have a scipt in charge of launching the required tools as a daemon.

Tested on Ubuntu 20.04 with socat utility installed. You’ll need to adapt the script yourself for older version. Also, you will want to double check on the provided paths

Visit Anders Ingemann’s gist for the most up to date version:

#!/usr/bin/env bash
# Inspired by https://blog.nimamoh.net/yubi-key-gpg-wsl2/

# Guide:
# Install GPG on windows & Unix
# Add "enable-putty-support" to gpg-agent.conf
# Download wsl-ssh-pageant and npiperelay and place the executables in "C:\Users\[USER]\AppData\Roaming\" under wsl-ssh-pageant & npiperelay
# https://github.com/benpye/wsl-ssh-pageant/releases/tag/20190513.14
# https://github.com/NZSmartie/npiperelay/releases/tag/v0.1
# Adjust relay() below if you alter those paths
# Place this script in WSL at ~/.local/bin/gpg-agent-relay
# Start it on login by calling it from your .bashrc: "$HOME/.local/bin/gpg-agent-relay start"


die() {
  # shellcheck disable=SC2059
  printf "$1\n" >&2
  exit 1

main() {
  case $1 in
    if ! start-stop-daemon --pidfile "$PIDFILE" --background --notify-await --notify-timeout 5 --make-pidfile --exec "$0" --start -- foreground; then
      die 'Failed to start. Run `gpg-agent-relay foreground` to see output.'
    start-stop-daemon --pidfile "$PIDFILE" --remove-pidfile --stop ;;
    start-stop-daemon --pidfile "$PIDFILE" --status
    local result=$?
    case $result in
      0) printf "gpg-agent-relay is running\n" ;;
      1 | 3) printf "gpg-agent-relay is not running\n" ;;
      4) printf "unable to determine status\n" ;;
    return $result
    relay ;;
    die "Usage:\n  gpg-agent-relay start\n  gpg-agent-relay stop\n  gpg-agent-relay status\n  gpg-agent-relay foreground" ;;

relay() {
  set -e
  local winuser
  winuser=$(/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe '$env:UserName')
  local winhome="/mnt/c/Users/$winuser"
  local wingnupghome="C:/Users/$winuser/AppData/Roaming/gnupg"
  local npiperelay="$winhome/AppData/Roaming/npiperelay/npiperelay.exe"
  local wslsshpageant="$winhome/AppData/Roaming/wsl-ssh-pageant/wsl-ssh-pageant-amd64-gui.exe"
  local gpgconnectagent="/mnt/c/Program Files (x86)/GnuPG/bin/gpg-connect-agent.exe"
  local gpgagentsocket="$GNUPGHOME/S.gpg-agent"
  local sshagentsocket="$GNUPGHOME/S.gpg-agent.ssh"

  killsocket "$gpgagentsocket"
  killsocket "$sshagentsocket"

  "$gpgconnectagent" /bye

  "$wslsshpageant" --systray --winssh ssh-pageant 2>/dev/null &

  socat UNIX-LISTEN:"$gpgagentsocket,unlink-close,fork,umask=177" EXEC:"$npiperelay -ep -ei -s -a '${wingnupghome}/S.gpg-agent'",nofork &
  # shellcheck disable=SC2064
  trap "kill -TERM $GNUPID" EXIT

  socat UNIX-LISTEN:"$sshagentsocket,unlink-close,fork,umask=177" EXEC:"$npiperelay /\/\./\pipe/\ssh-pageant" &

  set +e
  # shellcheck disable=SC2064
  trap "kill -TERM $GNUPID; kill -TERM $SSHPID" EXIT

  systemd-notify --ready 2>/dev/null
  trap - EXIT

killsocket() {
  local socketpath=$1
  if [[ -e $socketpath ]]; then
    local socketpid
    if socketpid=$(lsof +E -taU -- "$socketpath"); then
      timeout .5s tail --pid=$socketpid -f /dev/null &
      local timeoutpid=$!
      kill "$socketpid"
      if ! wait $timeoutpid; then
        die "Timed out waiting for pid $socketpid listening at $socketpath"
      rm "$socketpath"

checkdeps() {
  local deps=(socat start-stop-daemon lsof timeout)
  local dep
  local out
  for dep in "${deps[@]}"; do
    if ! out=$(type "$dep" 2>&1); then
      printf -- "Dependency %s not found:\n%s\n" "$dep" "$out"
      return 1

main "$@"
Code Snippet 1: Anders Ingermann's script.

Then, update your .bashrc to start the script, considering your script location to ~/.local/bin/gpg-relay-agent:

$HOME/.local/bin/gpg-agent-relay start
export SSH_AUTH_SOCK=$HOME/.gnupg/S.gpg-agent.ssh
Code Snippet 2: .bashrc excerpt.

Now, on computer boot, from WSL2, you should be able to see the SSH keys of you yubikey when you type

ssh-add -L
Code Snippet 3: Command to check ssh paegant integration.


~/.local/bin/gpg-agent-relay status
Code Snippet 4: Status command.
~/.local/bin/gpg-agent-relay foreground
Code Snippet 5: Foreground command.


The content of this post reflects my opinion, current state of knowledge and error can slip through its content.
Knowing so, if you think you found an error or inexact content, you are more than welcome to notify it through comment below ⏬.
Also, if you found the content useful and it helped you, consider leaving a comment too or, better, give me fuel buying me a coffee with the link on the top of the website. 🙏