背景
在 2022 年全球国际局势紧张,网络安全环境高度受威胁的环境下,声明自己的代码主权尤为重要 。 我们的身份可能会被伪造,进而发布投毒的代码,然后攻击波及到某个100k star的项目,还需要自己背锅。为了避免这种情况发生,更为了拥有一个炫酷的小标志,在WSL里调用yubikey签名迫在眉睫,间不容发!
前置知识
要用硬件key跨VM签名,多少得了解GPG签名,yubikey是什么,以及如何在GitHub上使用yubikey。
那么这里是 简明 GPG 概念 – 知乎 (zhihu.com)
这里是 使用 Yubikey 存储 GPG Key 以及认证 SSH 登录 · Coda
这里是 在Github上使用GPG的全过程 – 知乎 (zhihu.com)
解决方案
WSL 1
先上结论,不要浪费时间:利用WSL 1
是 NT Kernel
with syscall trans
,系统端口相通,直接翻译socket访问端口。
gpg --import $your_key
保存下面的代码为脚本,并加入.bashrc
#!/bin/bash
WinUserName="change here plz!"
rm $HOME/.gnupg/S.gpg-agent*
GPG_AGENT="/mnt/c/Users/"$WinUserName"/AppData/Local/gnupg/S.gpg-agent"
PREPEND_FILE="/tmp/gpg_agent_prepend"
WINDOWS_GPG_AGENT_PORT=$(head -n1 "$GPG_AGENT")
tail -n+2 "$GPG_AGENT" >"$PREPEND_FILE"
nohup socat "UNIX-LISTEN:$HOME/.gnupg/S.gpg-agent,fork" "SYSTEM:cat \"$PREPEND_FILE\" - <&3 | socat STDIO \"TCP\:127.0.0.1\:$WINDOWS_GPG_AGENT_PORT\" >&4,fdin=3,fdout=4" >/dev/null 2>&1 &
其逻辑是:
- 导入你的GPG公钥,需要
pubring.kbx
和trustdb.gpg
中的公钥信息寻找密钥。 - 移除Linux自带的
gpg-agent
,因为VM内的Linux系统无法访问USB端口,所以需要劫持这里的socket,拦截GPG请求。 - 从windows下
Kleopatra
的agent提取端口(代表着监听端口) - 从win的agent中提取二进制内容,并且写入
$PREPEND_FILE
中,这个二进制内容代表什么我还没研究。 - 利用
socat
在$HOME/.gnupg/S.gpg-agent
(步骤2中移除的socket)监听请求。- UNIX-LISTEN:监听
socket
端口 - SYSTEM:转发输入输出,对象为后续的命令。
cat $PREPEND_FILE - < &3|
首先输出$PREPEND_FILE
的内容,后续转发fd=3
的输入到输出。socat STDIO \"TCP\:127.0.0.1\:$WINDOWS_GPG_AGENT_PORT\" >&4
监听标准输入输出stdin/stdout
,将其转发到Kleopatra
监听的TCP端口下。
此时的STDIO作用域仅限于这句子命令,STDOUT被重定向到fd=4
fdin=3,fdout=4
,传给SYSTEM
的stdin
输入重定向到fd=3
,将fd=4
的输出作为SYSTEM
字段的STDOUT
。- 这么做的原因是避免在标准输入输出上产生大量信息,发生干扰。
- UNIX-LISTEN:监听
nohup ** >/dev/null 2>&1 &
,后台执行,不输出日志和错误信息。如果你要调试,请删除这部分。
然后,加入.bashrc
后,可以使用gpg --card-status
快乐的查看结果辣。
WSL 2
同理,先上结论。原理和WSL1
类似,由于WSL2
的网络特性,略有调整。
首先在Windows下开启WSL2
的防火墙,允许访问主机端口(管理员权限,cmd
/PS
),只用执行一次
New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow
[不建议] 然后在每次开机之后,用管理员权限的终端,执行一个端口转发条例,其中需要自行找到 GPG_PORT
和WSL_ROUTER_IP
:
netsh.exe interface portproxy del v4tov4 listenport=$GPG_PORT listenaddress=$WSL_ROUTER_IP connectport=$GPG_PORT connectaddress=127.0.0.1
当然,这样非常不优雅。所以个人强烈建议安装一个gsudo
(可以通过scoop
),可以在WSL2中自动执行该指令
之后步骤同WSL1
导入gpg
共钥,添加脚本。但是脚本略有调整。
#!/bin/bash
WinUserName="change here plz!"
HOST_IP=$(cat /etc/resolv.conf |grep "nameserver" |cut -f 2 -d " ") # JUST FOR WINDOWS
rm $HOME/.gnupg/S.gpg-agent* # OR SYSTEMD PATH
GPG_AGENT="/mnt/c/Users/"$WinUserName"/AppData/Local/gnupg/S.gpg-agent"
PREPEND_FILE="/tmp/gpg_agent_prepend"
WINDOWS_GPG_AGENT_PORT=$(head -n1 "$GPG_AGENT")
WIN_FORWARD=$(netstat.exe -ano | grep "$HOST_IP:$WINDOWS_GPG_AGENT_PORT" | wc -l)
if [ $WIN_FORWARD -eq 0 ];then # PREVENT POP-UP WINDOW OF UAC EVERYTIME!
gsudo.exe netsh.exe interface portproxy add v4tov4 \
listenport=$WINDOWS_GPG_AGENT_PORT listenaddress=$HOST_IP \
connectport=$WINDOWS_GPG_AGENT_PORT connectaddress=127.0.0.1
fi
tail -n+2 "$GPG_AGENT" >"$PREPEND_FILE"
nohup socat "UNIX-LISTEN:$HOME/.gnupg/S.gpg-agent,fork" "SYSTEM:cat \"$PREPEND_FILE\" - <&3 | socat STDIO \"TCP\:$HOST_IP\:$WINDOWS_GPG_AGENT_PORT\" >&4,fdin=3,fdout=4" >/dev/null 2>&1 &
其中增加了如下几点:
- 使用
resolv.conf
获取宿主机的IP,因为WSL2
使用NAT的方式访问,所以需要访问网关。 - 使用
gsudo
临时获取管理员权限,netsh.exe
将gpg-agent
仅在127.0.0.1上的监听,转发给WSL2
的网关地址,让WSL2
可以直接访问。 - 使用
netstat.exe
检测netsh.exe
的转发规则是否工作,避免每次都要弹出UAC弹窗。
其余和WSL1原理基本相同。
常见问题
scdaemon问题
这个一般是由于没有成功监听端口导致的,可以去掉nohup ** >/dev/null 2>&1 &
看看报错信息,对应调试。
systemd 问题
如果开启了WSL的 systemd
,那么 S.gpg-agent
的位置会变为 /run/user/1000/gnupg/S.gpg-agent
这种模式。 你需要将 $HOME/.gnupg/S.gpg-agent
等路径全部改为/run/user/1000/gnupg/S.gpg-agent
,并禁用自带的socket(运行脚本时清空/run/user/1000/gnupg/
目录即可,记得使用root。
无反应
这个是监听成功,但是端口错误或者防火墙拦截导致的。
- 检查
netsh.exe
是否转发正确的端口。 - 检查
Kelopatra
是否正常识别到了你的硬件密钥。 - 如果都正常,重启
Kelopatra
,并打开一个新的bash
。
(´இ皿இ`) 博主,现在好像 WSL 2 转发没法用了呢
我的问题,似乎防火墙策略重启后重置,所要重新执行?我这里还可以的
😭也可能是我直接使用的 GnuPG 的原因吧,在 socat 转发套接字后。
gpg --card-status
一直卡在无响应(无内容)的状态。现在我直接用 usbipd 连接 WSL 了。虽然确实麻烦一点,不过操作上比较简单一些。