-
-
Save andsens/2ebd7b46c9712ac205267136dc677ac1 to your computer and use it in GitHub Desktop.
#!/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" | |
GNUPGHOME="$HOME/.gnupg" | |
PIDFILE="$GNUPGHOME/gpg-agent-relay.pid" | |
die() { | |
# shellcheck disable=SC2059 | |
printf "$1\n" >&2 | |
exit 1 | |
} | |
main() { | |
checkdeps socat start-stop-daemon lsof timeout | |
case $1 in | |
start) | |
if ! start-stop-daemon --pidfile "$PIDFILE" --background --notify-await --notify-timeout 5 --make-pidfile --exec "$0" --start -- foreground; then | |
# shellcheck disable=SC2016 | |
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 winhome | |
local wslwinhome | |
winhome=$(cmd.exe /c "<nul set /p=%UserProfile%" 2>/dev/null || true) | |
wslwinhome="$(wslpath -u "$winhome")" | |
local npiperelay="$wslwinhome/AppData/Roaming/npiperelay/npiperelay.exe" | |
local wslsshpageant="$wslwinhome/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" | |
# backslash escaping in socat EXEC doesn't seem to work very well, use forward slashes instead | |
# windows/npiperelay handle that just fine | |
local wingpgagentpath="${winhome//\\/\/}/AppData/Roaming/gnupg/S.gpg-agent" | |
killsocket "$gpgagentsocket" | |
killsocket "$sshagentsocket" | |
"$gpgconnectagent" /bye | |
"$wslsshpageant" --systray --winssh ssh-pageant 2>/dev/null & | |
# shellcheck disable=SC2034 | |
WSPPID=$! | |
socat UNIX-LISTEN:"$gpgagentsocket,unlink-close,fork,umask=177" EXEC:"$npiperelay -ep -ei -s -a '$wingpgagentpath'",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=("$@") | |
local dep | |
local out | |
local ret=0 | |
for dep in "${deps[@]}"; do | |
if ! out=$(type "$dep" 2>&1); then | |
printf -- "Dependency %s not found:\n%s\n" "$dep" "$out" | |
ret=1 | |
fi | |
done | |
return $ret | |
} | |
main "$@" |
I've tried the guide by @Nimamoh with this script, and I can't seem to get past the start-stop-daemon: timed out waiting for a notification
error when running gpg-agent-relay start
and running with foreground
gives no output at all.
My environment is Pengwin (Debian based, with fish shell) in WSL2, up-to-date Windows 10 (fresh install).
Kleopatra has found my Yubikey properly and seems to be configured correctly. Also, the systray icon for wsl-ssh-pageant
pops up as it should. Looking at the logs in Kleopatra, it seems some kind of handshake is happening and it looks fine to my untrained eyes:
gpg-agent[23720]: DBG: chan_0x000003b0 -> OK Pleased to meet you
gpg-agent[23720]: DBG: chan_0x000003b0 <- RESET
gpg-agent[23720]: DBG: chan_0x000003b0 -> OK
gpg-agent[23720]: DBG: chan_0x000003b0 <- [eof]
gpg-agent[23720]: DBG: chan_0x0000039c -> OK Pleased to meet you
gpg-agent[23720]: DBG: chan_0x0000039c <- GETINFO pid
gpg-agent[23720]: DBG: chan_0x0000039c -> D 23720
gpg-agent[23720]: DBG: chan_0x0000039c -> OK
gpg-agent[23720]: DBG: chan_0x0000039c <- BYE
gpg-agent[23720]: DBG: chan_0x0000039c -> OK closing connection
Any suggestions?
Apparently I should have checked further. ssh-add -L
actually shows my ssh key on the card so I guess it worked as it should anyway?
@strangnet try running bash -x gpg-agent-relay.sh foreground
and link to the output in a new gist. bash -x
just shows all commands being run in the script, so maybe you can also figure it out yourself :-)
One possibility, things just take longer than I anticipated. I have set the timeout to 5 seconds (line 27), try adjusting that.
If I do let the agent relay start
, it outputs the timeout error, and setting the timeout higher makes no difference. It does succeed, though. If I run foreground
as you suggested the output is nothing, but if I don't run start
on login, it starts the gpg-agent successfully.
My solution now, is to accept that it works, i.e. I get the result I'm after, and just send the output of start
to /dev/null
.
This script is fantastic, thanks for putting it together!
I ran into some issue b/c winuser
is detected as the user inside WSL (and they're different for me). However, changing that part to these lines works for me:
relay() {
set -e
local winhome="$(cmd.exe /c "<nul set /p=%UserProfile%" 2>/dev/null)"
local wslhome="$(wslpath $winhome)"
local wingnupghome="$winhome/AppData/Roaming/gnupg"
local npiperelay="$wslhome/AppData/Roaming/npiperelay/npiperelay.exe"
local wslsshpageant="$wslhome/AppData/Roaming/wsl-ssh-pageant/wsl-ssh-pageant-amd64-gui.exe"
...
@lekv thanks for the pointer. I have adjusted the script, I really like how your solution makes no assumption about the home dir location whatsoever :-)
There was an issue with the backslashes in the path returned by %UserProfile% though. I think socat or some other part of that chain re-interpretes the gpg-agent socket path argument and thereby makes it invalid. Instead of double escaping the backslashes I simply replaced them with forward slashes (local wingpgagentpath="${winhome//\\/\/}/AppData/Roaming/gnupg/S.gpg-agent"
).
Hi @andsens, thank you for the awesome script. I'm having a bit of trouble getting it to work. I've reached a stage where I had to slightly modify the script because my username contains a space and the script doesn't seem to be able to handle that. Now I just get a whole bunch of npiperelay.exe help messages. Here's my output of the foreground task when ran with bash -x
+ PIDFILE=/home/dannyverpoort/.gnupg/gpg-agent-relay.pid
+ main foreground
+ checkdeps socat start-stop-daemon lsof timeout
+ deps=("$@")
+ local deps
+ local dep
+ local out
+ local ret=0
+ for dep in "${deps[@]}"
++ type socat
+ out='socat is /usr/bin/socat'
+ for dep in "${deps[@]}"
++ type start-stop-daemon
+ out='start-stop-daemon is /usr/sbin/start-stop-daemon'
+ for dep in "${deps[@]}"
++ type lsof
+ out='lsof is /usr/bin/lsof'
+ for dep in "${deps[@]}"
++ type timeout
+ out='timeout is /usr/bin/timeout'
+ return 0
+ case $1 in
+ relay
+ set -e
+ local winhome
+ local wslwinhome
++ cmd.exe /c '<nul set /p=%UserProfile%'
++ true
+ winhome='C:\Users\Danny Verpoort'
++ wslpath -u 'C:\Users\Danny Verpoort'
+ wslwinhome='/mnt/c/Users/Danny Verpoort'
+ local npiperelay=/mnt/c/Users/Local/bin/npiperelay/npiperelay.exe
+ local wslsshpageant=/mnt/c/Users/Local/bin/wsl-ssh-pageant/wsl-ssh-pageant-amd64-gui.exe
+ local 'gpgconnectagent=/mnt/c/Program Files (x86)/GnuPG/bin/gpg-connect-agent.exe'
+ local gpgagentsocket=/home/dannyverpoort/.gnupg/S.gpg-agent
+ local sshagentsocket=/home/dannyverpoort/.gnupg/S.gpg-agent.ssh
+ local 'wingpgagentpath=C:/Users/Danny Verpoort/AppData/Roaming/gnupg/S.gpg-agent'
+ killsocket /home/dannyverpoort/.gnupg/S.gpg-agent
+ local socketpath=/home/dannyverpoort/.gnupg/S.gpg-agent
+ [[ -e /home/dannyverpoort/.gnupg/S.gpg-agent ]]
+ killsocket /home/dannyverpoort/.gnupg/S.gpg-agent.ssh
+ local socketpath=/home/dannyverpoort/.gnupg/S.gpg-agent.ssh
+ [[ -e /home/dannyverpoort/.gnupg/S.gpg-agent.ssh ]]
+ '/mnt/c/Program Files (x86)/GnuPG/bin/gpg-connect-agent.exe' /bye
+ WSPPID=4746
+ /mnt/c/Users/Local/bin/wsl-ssh-pageant/wsl-ssh-pageant-amd64-gui.exe --systray --winssh ssh-pageant
+ GNUPID=4747
+ trap 'kill -TERM 4747' EXIT
+ socat UNIX-LISTEN:/home/dannyverpoort/.gnupg/S.gpg-agent,unlink-close,fork,umask=177 'EXEC:/mnt/c/Users/Local/bin/npiperelay/npiperelay.exe -ep -ei -s -a '\''C:/Users/Danny Verpoort/AppData/Roaming/gnupg/S.gpg-agent'\'',nofork'
+ SSHPID=4748
+ socat UNIX-LISTEN:/home/dannyverpoort/.gnupg/S.gpg-agent.ssh,unlink-close,fork,umask=177 'EXEC:/mnt/c/Users/Local/bin/npiperelay/npiperelay.exe /\/\./\pipe/\ssh-pageant'
+ set +e
+ trap 'kill -TERM 4747; kill -TERM 4748' EXIT
+ systemd-notify --ready
+ wait 4747 4748
The agent-relay foreground
command is silent until I try to use gpg --card-status
in a second WSL window. Then the repeated help message shows up.
I'm running Ubuntu 20.04.1 on WSL, windows 10 as host and I'm trying to use a yubikey 5. If I try the card status in the power shell I can actually see the card. I have all the perquisites installed. Any suggestions on what's going on?
@dannyverp, socat EXEC is garbage at escaping the arguments for its invocations. I suggest fiddling around with different escaping mechanism and see what sticks (i.e. just run the socat command seperately).
The big question mark here is whether some of the argument parsing happens on the windows side of things when invoking .exe files, I have no clue about that.
To start off with try double-escaping wingpgagentpath
like so wingpgagentpath=$(printf -- '%q' "$wingpgagentpath")
Hey, so I've found out that somehow sometimes a socket is open by multiple processes and thus the killsocket
function fails as the socketpid
variable is a multiline string. I think replacing it with something like echo "$socketpid" | xargs kill
should do the trick.
@jmigual huh, curious. Of course, only on process can be listening on that socket. So the others must be clients. I'm not sure killing them is the best way to go, it would be better to have lsof
filter properly.
This isn't working for me as is. I moved a few files around, but other than that I'm running the script exactly as you have here. The only thing that fixed gpg for me was to symlink /mnt/c/Program Files (x86)/GnuPG/bin/gpg.exe
to /usr/local/sbin/gpg
. (Found here: https://blog.nimamoh.net/yubi-key-gpg-wsl2/#isso-4)
Is there any way I can help figure out why it doesn't work with stock gpg?
P.S. I'm also going to work on modifying the script to only setup the GPG relay. I want to use the built in Windows OpenSSH client with its ssh-agent.
I'm also not able to get it working. When I run any gpg
commands in WSL2 (Ubuntu 20.10 fwiw), it doesn't seem able to use the existing sockets. I added extra-socket /dev/null
and browser-socket /dev/null
to ~/.gnupg/gpg-agent.conf to keep it from creating any new sockets, and then any gpg commands return: gpg: can't connect to the agent: End of file
@alanivey Did you try the symlink method? That was the only thing that worked for me.
@mew1033 yes, I symlinked the Gpg4Win exe in WSL2 Linux as $HOME/.local/bin/gpg with ln -sv /mnt/c/Program\ Files\ \(x86\)/GnuPG/bin/gpg.exe ~/.local/bin/gpg
(in my WSL2 Linux environment, I have this directory at the front of $PATH). This binary does not attempt to use $HOME/.gnupg/S.gpg-agent when running gpg
commands, nor does it use $HOME/.gnupg for its database, so it's not necessary to do any GPG setup in the WSL2 Linux environment. B/c I'm not looking to use the SSH agent integration, I ultimately find this easier; I maintain a single GPG database in Windows and can use it in multiple WSL2 Linux environments without replicating.
I'll caveat that I have not used this configuration for longer than a day so I might end up being wrong!
In my Debian-WSL2, I can't access cmd.exe
(and I didn't investigate in this further, as there seems to be a cleaner way), so the script isn't working. I replaced line 55 and 56 with this (you have to install wslu for this):
username="$(wslvar USERPROFILE)"
wslwinhome="$(wslpath $username)"
winhome="$(wslpath -w $wslwinhome)"
additionally, I had to add export PATH=$PATH:/sbin
to my .bashrc
as otherwise start-stop-daemon
isn't found in path.
@andsens I was having difficulty with this script and multiple shells (ie, a windows terminal and VSCode, which opens another terminal). It was failing because multiple PIDs were being included in the socket file, and kill was not super happy about that.
I'd end up with an error like;
gpg-agent-relay foreground
gpg-agent-relay: line 99: kill: 768
2650
8610
23065
26471
27892
30552: arguments must be process or job IDs
tail: cannot open '2650' for reading: No such file or directory
tail: cannot open '8610' for reading: No such file or directory
tail: cannot open '23065' for reading: No such file or directory
tail: cannot open '26471' for reading: No such file or directory
tail: cannot open '27892' for reading: No such file or directory
tail: cannot open '30552' for reading: No such file or directory
==> /dev/null <==
To correct it, I modified the killsocket function;
killsocket() {
local socketpath=$1
if [[ -e $socketpath ]]; then
# local socketpid
lsof +E -taU -- "$socketpath" | awk 'length {print $1}' | xargs -rn1 kill
fi
}
This works and corrects my multiple-terminal issues, but the timeout is also removed. It could be added, but I am wondering if it is even necessary?
I've been using this for a couple of years now with few enough headaches that I never tried to look for a better way. But with systemd support now in WSL I decided to see if I could simplify this approach and I found that I could do it all just using systemd and npiperelay. Turns out I could so I packaged it all up in wsl-gpg-systemd if anyone is interested in giving it a shot.
@demonbane I tried it out on a fresh container this evening, and it works great! Thanks for simplifying it tenfold.
For anyone coming at systemd new; the socket set up by wsl-gpg-systemd
is /run/user/1000/gnupg/S.gpg-agent.ssh
. I had to pause for a second to figure out where the socket was being opened.
The installer takes care to always use gpgconfig to get any paths, and newer releases of gpg have been following the systemd spec and using /run
for transient user session storage, like the sockets. And surprisingly, there doesn't appear to be an easy way to override that location. From the gpg-agent manpage:
--use-standard-socket
--no-use-standard-socket
--use-standard-socket-p
Since GnuPG 2.1 the standard socket is always used. These options have no more effect. The command
gpg-agent --use-standard-socket-p will thus always return success.
Well done! I updated the blog post accordingly and used the last version of your script, so far it's good ^^