Skip to content

Instantly share code, notes, and snippets.

@Speedy37
Created July 10, 2020 11:02
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Speedy37/1833b6b8c73a7768a266f0b903ab3678 to your computer and use it in GitHub Desktop.
Save Speedy37/1833b6b8c73a7768a266f0b903ab3678 to your computer and use it in GitHub Desktop.
WSL2 gpg agent relay (Yubikey)
#!/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=Vincent
# 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="${HOME}/npiperelay.exe"
PIDFILE="${GPGDIR}/.gpg-agent-relay.pid"
OLDPID=$(cat "${PIDFILE}")
if [ ! -z "${OLDPID}" ]; then
ps -p "${OLDPID}" >/dev/null && exit 0
fi
rm -f "${GPGDIR}/S.gpg-agent" "${GPGDIR}/S.gpg-agent.ssh"
# 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=$!
echo $AGENTPID > ${PIDFILE}
@Hegi
Copy link

Hegi commented Mar 4, 2023

Please note that Gpg4Win has changed the socket folder location from %APPDATA%/gnupg to %LOCALAPPDATA%/gnupg

This means that instead of AppData/Roaming/gnupg you need to use AppData/Local/gnupg

This small change cost me more days than I care to admit. Hopefully this note helps at least one person out there... :)

Finally, here's a version that dynamically identifies the username (instead of hardcoding it)

#!/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"

# In a newer WSL installation the socket folder changed from the $HOME/.gnupg to the /run/user/1000
# the active folder can be identified with the `gpgconf --list-dirs` command
# GNUPGHOME="$HOME/.gnupg"
export GNUPGHOME="/run/user/1000/gnupg/"
PIDFILE="$GNUPGHOME/gpg-agent-relay.pid"

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

main() {
  checkdeps
  case $1 in
  start)
    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.'
    fi
    ;;
  stop)
    start-stop-daemon --pidfile "$PIDFILE" --remove-pidfile --stop ;;
  status)
    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" ;;
    esac
    return $result
    ;;
  foreground)
    relay ;;
  *)
    die "Usage:\n  gpg-agent-relay start\n  gpg-agent-relay stop\n  gpg-agent-relay status\n  gpg-agent-relay foreground" ;;
  esac
}

relay() {
  set -e
  local appdatafolder
  local localappdatafolder
  localappdatafolder=$(/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe '$env:LocalAppData')
  localappdatafolder=${localappdatafolder//\\/\/}
  localappdatafolder=${localappdatafolder//$'\r'}
  appdatafolder=$(/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe '$env:AppData')
  appdatafolder=${appdatafolder//\\/\/}
  appdatafolder=${appdatafolder//C:/\/mnt\/c}
  appdatafolder=${appdatafolder//$'\r'}
  local wingnupghome="${localappdatafolder}/gnupg"
  local npiperelay="${appdatafolder}/npiperelay/npiperelay.exe"
  local wslsshpageant="${appdatafolder}/wsl-ssh-pageant/wsl-ssh-pageant-amd64-gui.exe"
  local gpgconnectagent="/mnt/c/Programs/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 &
  WSPPID=$!

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

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

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

  systemd-notify --ready 2>/dev/null
  wait $GNUPID $SSHPID
  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"
      fi
    else
      rm "$socketpath"
    fi
  fi
}

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
    fi
  done
}

main "$@"

@shinji257
Copy link

Your comment confused me but I think you meant it got moved from %APPDATA% (Roaming) over to %LOCALAPPDATA% (Local). You initially mentioned it the other way around.

@Hegi
Copy link

Hegi commented Mar 1, 2024

@shinji257 Right you are. Fixed & thanks! 😄

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