wsl2-gpg-agent-bridge

WSL2 gpg-agent bridge #

简介 #

在使用 Windows 侧的 Gpg4win 提供的 gpg-agent 服务时,会生成管道或监听一些网络端口,如 \\.\pipe\openssh-ssh-agent%APPDATA%/Local/gnupg/S.gpg-agent 等,如果 WSL2 内部能够连接到这些管道或端口,则 Windows 侧和 WSL2 侧可以用同一套 gpg-agent 服务来管理密钥,减少不必要的麻烦,如反复输入私钥口令、Yubikey无法访问等。

一、安装 Gpg4win #

详见官网: https://gpg4win.org/

本文不涉及其他版本(如 linux 版本,https://gnupg.org/)

二、配置 gpg-agent.conf #

文件位置:%APPDATA%\Roaming\gnupg\gpg-agent.conf

添加以下配置内容:

text
enable-ssh-support
enable-putty-support
# To Enable support for the native Microsoft OpenSSH binaries (requires gpg 2.4.0 / Gpg4win 4.1.0 or higher)
enable-win32-openssh-support
use-standard-socket

重启 gpg-agent 服务:

ps1
gpg-connect-agent reloadagent /bye

三、配置环境和自启脚本 #

配置 PowerShell 的 profile 脚本 #

PowerShell 的 profile 脚本是一个可编辑的启动脚本,每次打开 PowerShell 窗口时自动执行一遍。 下面编辑这个脚本,添加 gpg-agent 启动命令。

(1)使用编辑器打开 profile 配置文件:

ps1
# 使用记事本打开:
notepad $PROFILE
# 或者使用VSCode打开:
code $PROFILE

(2)按需添加配置内容:

ps1
# 启动 gpg-agent 服务
gpg-connect-agent /bye

# 设置环境变量 SSH_AUTH_SOCK (ssh-agent管道地址)
# 方式一(添加到系统环境变量中,这样可以让VSCode、IDEA中的 ssh/git 也可以使用 ssh-agent 服务)。
# 方式二(添加变量赋值代码,但是仅对当前PowerShell窗口生效):
# $env:SSH_AUTH_SOCK="\\.\pipe\openssh-ssh-agent"

配置开机自启脚本 #

(1)添加PowerShell脚本文件,放到固定位置,如:C:\YourPath\init-script.ps1

ps1
# Start gpg-agent
gpg-connect-agent /bye

(2)添加vbs脚本文件,放到固定位置,如:C:\YourPath\init.vbs(使用vbs脚本是为了开机自启时不弹出黑窗口)

vbs
set ws=WScript.CreateObject("WScript.Shell")
ws.run "powershell -file C:\YourPath\init-script.ps1",vbhide

(3)给vbs脚本文件创建快捷方式init.vbs.lnk,放在系统开机自启目录下:C:\Users\YourName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\init.vbs.lnk

四、在 WSL 中配置管道转发服务 #

准备环境 #

bash
# 保证 socat 命令已经安装,socat 可用于转发套接字数据
sudo apt install socat

# 下载 wsl2-ssh-pageant.exe(用于WSL中打开win管道),放到固定位置,如 C:\YourPath\wsl2-ssh-pageant.exe
# 但是原作者已归档了项目,bug已不再修复(如进程无法退出、执行速度慢等问题)。
# 原项目:https://github.com/BlackReloaded/wsl2-ssh-pageant
# 本人fork项目(已修复部分问题):https://github.com/gui156/wsl2-ssh-pageant

# 添加权限、创建软连接
sudo chmod a+x "/mnt/c/YourPath/wsl2-ssh-pageant.exe"
sudo ln -s "/mnt/c/YourPath/wsl2-ssh-pageant.exe" /usr/local/bin/wsl2-ssh-pageant.exe

# 原理:
#   wsl2-ssh-pageant.exe 程序与 Windows 侧的 gpg-agent 进行通信,标准输入输出作为通信通道,
#   socat 将 wsl2-ssh-pageant.exe 程序的标准输入输出绑定到一个 socket 文件上(此命令会阻塞),
#   后续其他程序可使用此 socket 文件对 gpg-agent 通信,
#   只需配置环境变量 SSH_AUTH_SOCK=/tmp/bridge-S.gpg-agent.ssh 或 GPG_AGENT_SOCK=/tmp/bridge-S.gpg-agent 即可。
#
# 下面是命令样例:
#
# socat UNIX-LISTEN:/tmp/bridge-S.gpg-agent.ssh,fork,unlink-early,unlink-close EXEC:"/usr/local/bin/wsl2-ssh-pageant.exe"
#
# socat UNIX-LISTEN:/tmp/bridge-S.gpg-agent,fork,unlink-early,unlink-close EXEC:"/usr/local/bin/wsl2-ssh-pageant.exe --gpg S.gpg-agent --gpgConfigBasepath 'C:/Users/YourName/AppData/Local/gnupg'"

添加服务单元 #

Note

若 WSL2 未启用 systemd,则请跳过这一节,并参考 wsl2-ssh-pageant 项目中的 Bash/Zsh/Fish 配置方式。

服务单元配置文件可以放在任意固定地方(Windows侧和WSL2侧皆可)。

Part 1: S.gpg-agent 服务单元 #

(1)文件:gpg-agent.socket

text
[Unit]
Description=WSL2 gpg-agent socket

[Socket]
ListenStream=%t/gnupg/S.gpg-agent
SocketMode=0600
DirectoryMode=0700

[Install]
WantedBy=sockets.target

(2)文件:gpg-agent.service

text
[Unit]
Description=WSL2 gpg-agent service
BindsTo=gpg-agent.socket bridge-gpg-agent.service
After=gpg-agent.socket bridge-gpg-agent.service

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd %t/gnupg/bridge-S.gpg-agent

(3)文件:bridge-gpg-agent.service

text
[Unit]
Description=WSL2 gpg-agent bridge

[Service]
ExecStart=/usr/bin/socat UNIX-LISTEN:%t/gnupg/bridge-S.gpg-agent,fork,unlink-early,unlink-close EXEC:"/usr/local/bin/wsl2-ssh-pageant.exe --gpg S.gpg-agent --gpgConfigBasepath 'C:/Users/YourName/AppData/Local/gnupg'"
ExecStartPost=/usr/bin/sleep 0.2
Important

请修改以上配置中的 gpgConfigBasepath 参数部分。

Part 2: S.gpg-agent.ssh 服务单元 #

(1)文件:gpg-agent-ssh.socket

text
[Unit]
Description=WSL2 gpg-agent-ssh socket

[Socket]
ListenStream=%t/gnupg/S.gpg-agent.ssh
SocketMode=0600
DirectoryMode=0700

[Install]
WantedBy=sockets.target

(2)文件:gpg-agent-ssh.service

text
[Unit]
Description=WSL2 gpg-agent-ssh service
BindsTo=gpg-agent-ssh.socket bridge-gpg-agent-ssh.service
After=gpg-agent-ssh.socket bridge-gpg-agent-ssh.service

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd %t/gnupg/bridge-S.gpg-agent.ssh

(3)文件:bridge-gpg-agent-ssh.service

text
[Unit]
Description=WSL2 gpg-agent-ssh bridge

[Service]
ExecStart=/usr/bin/socat UNIX-LISTEN:%t/gnupg/bridge-S.gpg-agent.ssh,fork,unlink-early,unlink-close EXEC:"/usr/local/bin/wsl2-ssh-pageant.exe"
ExecStartPost=/usr/bin/sleep 0.2

Part 3: 配置服务单元 #

bash
# 链接服务单元
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/gpg-agent.socket
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/gpg-agent.service
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/gpg-agent-ssh.socket
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/gpg-agent-ssh.service
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/bridge-gpg-agent.service
systemctl --user link /mnt/c/dev/lib/wsl2-ssh-pageant/systemd/bridge-gpg-agent-ssh.service
systemctl --user add-wants sockets.target gpg-agent.socket
systemctl --user add-wants sockets.target gpg-agent-ssh.socket

# 屏蔽 WSL2 自带 gpg 提供的其他 gpg-agent 服务单元
systemctl --user mask gpg-agent-extra.socket
systemctl --user mask gpg-agent-browser.socket
systemctl --user mask dirmngr.service
systemctl --user mask dirmngr.socket

systemctl --user daemon-reload

配置 WSL #

(1)配置环境变量(在 ~/.bashrc 文件中添加以下内容):

bash
# Use ssh-agent
export SSH_AUTH_SOCK="/run/user/$UID/gnupg/S.gpg-agent.ssh"
# Use gpg-agent
export GPG_AGENT_SOCK="/run/user/$UID/gnupg/S.gpg-agent"

(2)配置内部gpg禁止自启(在 ~/.gnupg/gpg.conf 文件中添加以下内容):

no-autostart
Note

添加 no-autostart 是为了禁止内部gpg自动启动agent服务,否则这个服务启动后会覆盖自定义的 S.gpg-agent 等套接字文件。

五、配置 Git Bash #

PATH 环境变量 #

目的是修改 PATH 从而用 Windows 侧的 gpg.exe、ssh.exe 覆盖 Git Bash 自带的 gpg、ssh 程序。

添加rc配置(~/.bashrc):

bash
_exclude_paths='/c/WINDOWS/System32/OpenSSH
/c/Program Files (x86)/GnuPG/bin'
export PATH="$(echo "$PATH" | tr ':' '\n' | grep -Fxivf <(echo "$_exclude_paths") | sed ':t;N;s/\n/:/;t t')"
unset _exclude_paths

# Add paths
export PATH="/c/WINDOWS/System32/OpenSSH:/c/Program Files (x86)/GnuPG/bin:$PATH"
Warning

注意,这样改造之后,MINGW64 终端窗口 (是指 Open Git Bash here 打开的窗口而不是 Windows Terminal 打开的 Git Bash 窗口), 虽然可以执行远程命令或拉取git代码并且正常使用 Windows 侧的 gpg-agent、ssh-agent 服务来缓存密钥, 但是不推荐登录远程服务器,因为此时的 ssh 命令来自 Windows 侧的 OpenSSH, 它不兼容 MINGW64 终端窗口(当然你可以给 ssh 命令添加 -tt-t -t 选项来强制分配虚拟终端,但是不建议这么做,显示格式会有些问题)。

如果想要使用 Git Bash 自带的 gpg、ssh 程序,可以这样使用:/usr/bin/gpg .../usr/bin/ssh ...(不过它们无法利用 Windows 侧的 gpg-agent、ssh-agent 服务)。

git sshCommand #

环境变量配置完后就可以一劳永逸了,但是如果你不想配置上面的环境变量,也可以给git配置外部ssh命令:

bash
# PowerShell 或 Git Bash 中执行以下命令(相关配置文件 ~/.gitconfig):
git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe

同理,你也可以在其他你需要的地方指定外部的 gpg.exe、ssh.exe 程序。

注意事项 #

关于 C:\Users\YourName\.gnupg 目录 #

  1. 此目录一般是由 Git Bash 中自带的 gpg 客户端创建,但是经过以上配置之后不太会用到内部的 gpg 程序,因此这个目录也不再使用。
  2. 真正的 gpg 实例,是由 Windows 侧的 Gpg4win 创建,个人配置目录在 C:\Users\YourName\AppData\Roaming\gnupg,利用其 gpg-agent 进程来管理 gpg 密钥和 ssh 密钥。

关于 WSL2 侧安装的 gpg 配置 #

WSL2 侧安装的 gpg 配置目录在 ~/.gnupg,这里配置的公钥需要保持与 Windows 侧的 Gpg4win 配置的公钥一致,否则在 WSL2 侧(使用 WSL2 内部自带的 gpg 客户端 + Windows 侧的 gpg-agent 服务)进行加解密时可能会找不到对应ID的密钥。只需保证公钥存在即可,私钥可以不用导入。

关于公钥的导入导出,详见这篇文章: gpg-export-keys

参考资料 #

关于 Win32-OpenSSH 与 Gpg4win 的兼容问题的讨论 #

详见 GitHub Issue (Closed): https://github.com/PowerShell/Win32-OpenSSH/issues/827

关于将 Win 管道转发到 WSL2 内部的方案 #

wsl2-ssh-pageant:

实现原理1:对于 gpg-agent,找到 Windows 侧的 S.gpg-agent 文件第一行文本(即 gpg-agent 端口),打开套接字连接到该端口,然后将标准输入写入该连接,将该连接读取到的内容写入标准输出。

实现原理2:对于 gpg-agent-ssh,有两种方式,一种是打开参数中传入的 Windows 管道地址 \\.\pipe\openssh-ssh-agent 然后利用 Win API 直接打开管道进行读写,另一种是直接调用 Win 相关 API 来执行 gpg-connect-agent 程序进行通信。

关于 gpg-connect-agent 等命令 #

ps1
# 启动 gpg-agent 服务
gpg-connect-agent /bye
# 也可以这样:
gpgconf --launch gpg-agent

# 重载 gpg-agent 服务
gpg-connect-agent reloadagent /bye
# 也可以这样:
gpgconf --reload gpg-agent

# 关闭 gpg-agent 服务
gpg-connect-agent killagent /bye
# 也可以这样:
gpgconf --kill all

关于ssh密钥管理 #

具体细节详见 gpg 篇 ssh-add 部分: Wiki/linux/man/gpg/gpg

2025年8月13日