在服务器上使用本地的 GPG 密钥

在服务器上使用本地的 GPG 密钥

4 min read

{{< featuredImage >}}

我们经常会有在服务器上使用 GPG 密钥的需求,比如为编译好的软件签名等等。但在多人使用的服务器上配置 GPG 密钥并不方便,等何况还有安全风险。并且如果你使用了 Yubikey 等「智能卡」储存密钥,你根本没法将密钥导出来!!

那么有没有办法在密钥不离开本地的前提下远程使用 GPG 呢?答案是肯定的。

首先,在你的本地计算机下执行以下命令 (Linux):

$ ls -l ~/.gnupg | grep gpg-agent
srwx------ 1 mogeko mogeko    0 4月  25 21:40 S.gpg-agent
srwx------ 1 mogeko mogeko    0 4月  25 21:40 S.gpg-agent.browser
srwx------ 1 mogeko mogeko    0 4月  25 21:40 S.gpg-agent.extra
srwx------ 1 mogeko mogeko    0 4月  25 21:40 S.gpg-agent.ssh

我们可以看到一堆 socket,这些 socket 就是实现我们需求的关键。

GPG 从 gpg2 开始就实现了基于转发 UNIX Domain Socket 的代理 {{< spoiler >}}还在用第一代 GPG 的小伙伴快升级吧!{{< /spoiler >}},也就是说,只需要将服务器端的 S.gpg-agent 和本地的 S.gpg-agent.extra 建立连接,就可以实现在密钥不离开本地的前提下远程使用 GPG。

而负责转发 UNIX Domain Socket 的软件则是我们连接远程服务器必定会用到的 SSH。我们只需要让 SSH 将服务器上的一个 socket 转发到本地 gpg-agent 监听的 socket 上即可。

具体命令如下 (写绝对地址,我这里为了方便演示所以用了 ~):

$ ssh -R ~/.gnupg/S.gpg-agent:~/.gnupg/S.gpg-agent.extra \
      -o StreamLocalBindUnlink=yes user@example.com

这里的 StreamLocalBindUnlink 是让 SSH 断开的时候,把远端监听的 socket unlink 掉,不然下次连接会转发失败。

每次连接都打这么长一串,太麻烦了,我们可以直接在 ~/.ssh/config 中配置:

StreamLocalBindUnlink yes
RemoteForward ~/.gnupg/S.gpg-agent:~/.gnupg/S.gpg-agent.extra

然后在服务器上,让 GPG 从公钥服务器拉取公钥:

gpg --keyserver [公钥服务器 URL] --search-keys [用户 ID]

就大功告成了!(可以预防性的重启一下 gpg-agent)

远程主机上可以正常的签名、解密等,但没法 gpg --card-edit (毕竟服务器上没有私钥嘛)。

Windows

Windows 上 S.gpg-agent.extra 的位置在

%HOMEDRIVE%%HOMEPATH%\AppData\Roaming\gnupg\S.gpg-agent.extra

不过 Gpg4win 上用的 socket 好像和 UNIX Domain Socket 有点区别,可能有坑。

举一反三

既然我们可以用本地主机代理远程服务器上的 GPG,那么是否也可以代理虚拟机、WSL 和 Docker 中的 GPG 呢?

在本地通过 socat 等软件代替 SSH 转发 UNIX Domain Socket,理论上是可行的。