用 PGP 保护代码完整性(六):在 Git 上使用 PGP
我们继续我们的 PGP 实践系列,来看看签名标签的标签和提交,这可以帮你确保你的仓库没有被篡改。
在本系列教程中,我们提供了一个使用 PGP 的实用指南,包括基本概念和工具、生成和保护你的密钥。如果你错过了前面的文章,你可以查看下面的链接。在这篇文章中,我们谈一谈在 Git 中如何集成 PGP、使用签名的标签,然后介绍签名提交,最后添加签名推送的支持。
Git 的核心特性之一就是它的去中心化本质 —— 一旦仓库克隆到你的本地系统,你就拥有了项目的完整历史,包括所有的标签、提交和分支。然而由于存在着成百上千的克隆仓库,如何才能验证你下载的仓库没有被恶意的第三方做过篡改?你可以从 GitHub 或一些貌似官方的位置来克隆它们,但是如果有些人故意欺骗了你怎么办?
或者在你参与的一些项目上发现了后门,而 “Author” 行显示是你干的,然而你很确定 不是你干的,会发生什么情况?
为解决上述问题,Git 添加了 PGP 集成。签名的标签通过确认它的内容与创建这个标签的开发者的工作站上的内容完全一致来证明仓库的完整性,而签名的提交几乎是不可能在不访问你的 PGP 密钥的情况下能够假冒你。
清单
- 了解签名的标签、提交和推送(必要)
- 配置 git 使用你的密钥(必要)
- 学习标签如何签名和验证(必要)
- 配置 git 总是签名带注释标签(推荐)
- 学习提交如何签名和验证工作(必要)
- 配置 git 总是签名提交(推荐)
- 配置 gpg-agent 选项(必要)
考虑事项
git 实现了 PGP 的多级集成,首先从签名标签开始,接着介绍签名提交,最后添加签名推送的支持。
了解 Git 哈希
git 是一个复杂的东西,为了你能够更好地掌握它如何集成 PGP,你需要了解什么是”哈希“。我们将它归纳为两种类型的哈希:树哈希和提交哈希。
树哈希
每次你向仓库提交一个变更,对于仓库中的每个子目录,git 都会记录它里面所有对象的校验和哈希 —— 内容(blobs)、目录(trees)、文件名和许可等等。它只对每次提交中发生变更的树和内容做此操作,这样在只变更树的一小部分时就不必去重新计算整个树的校验和。
然后再计算和存储处于顶级的树的校验和,这样如果仓库的任何一部分发生变化,校验和将不可避免地发生变化。
提交哈希
一旦创建了树哈希,git 将计算提交哈希,它将包含有关仓库和变更的下列信息:
- 树哈希的校验和
- 变更前树哈希的校验和(父级)
- 有关作者的信息(名字、email、创作时间)
- 有关提交者的信息(名字、email、提交时间)
- 提交信息
哈希函数
在写这篇文章时,虽然研究一种更强大的、抗碰撞的算法的工作正在进行,但 git 仍然使用的是 SHA1 哈希机制去计算校验和。注意,git 已经包含了碰撞防范程序,因此认为对 git 成功进行碰撞攻击仍然是不可行的。
带注释标签和标签签名
在每个 Git 仓库中,标签允许开发者标记特定的提交。标签可以是 “轻量级的” —— 几乎只是一个特定提交上的指针,或者它们可以是 “带注释的”,它自己将成为 git 树中的项目。一个带注释标签对象包含所有下列的信息:
- 成为标签的提交的哈希的校验和
- 标签名字
- 关于打标签的人的信息(名字、email、打标签时间)
- 标签信息
一个 PGP 签名的标签是一个带有将所有这些条目封装进一个 PGP 签名的带注释标签。当开发者签名他们的 git 标签时,他们实际上是向你保证了如下的信息:
- 他们是谁(以及他们为什么应该被信任)
他们在签名时的仓库状态是什么样:
标签包含的提交的哈希
- 提交的哈希包含了顶级树的哈希
- 顶级树哈希包含了所有文件、内容和子树的哈希
- 它也包含有关作者的所有信息
- 包含变更发生时的精确时间
当你克隆一个仓库并验证一个签名的标签时,就是向你以密码方式保证:仓库中的所有内容、包括所有它的历史,与开发者签名时在它的计算机上的仓库完全一致。
签名的提交
签名的提交与签名的标签非常类似 —— PGP 签名的是提交对象的内容,而不是标签对象的内容。一个提交签名也给你提供了开发者签名时开发者树上的全部可验证信息。标签签名和提交的 PGP 签名提供了有关仓库和它的完整历史的完全一致的安全保证。
签名的推送
为了完整起见,在这里包含了签名的推送这一功能,因为在你使用这个功能之前,需要在接收推送的服务器上先启用它。正如我们在上面所说过的,PGP 签名一个 git 对象就是提供了开发者的 git 树当时的可验证信息,但不提供开发者对那个树意图相关的信息。
比如,你可以在你自己复刻的 git 仓库的一个实验分支上尝试一个很酷的特性,为了评估它,你提交了你的工作,但是有人在你的代码中发现了一个恶意的 bug。由于你的提交是经过正确签名的,因此有人可能将包含有恶意 bug 的分支推入到 master 分支中,从而在生产系统中引入一个漏洞。由于提交是经过你的密钥正确签名的,所以一切看起来都是合理合法的,而当 bug 被发现时,你的声誉就会因此而受到影响。
在 git push
时,为了验证提交的意图而不仅仅是验证它的内容,添加了要求 PGP 推送签名的功能。
配置 git 使用你的 PGP 密钥
如果在你的钥匙环上只有一个密钥,那么你就不需要再做额外的事了,因为它是你的默认密钥。
然而,如果你有多个密钥,那么你必须要告诉 git 去使用哪一个密钥。([fpr]
是你的密钥的指纹):
$ git config --global user.signingKey [fpr]
注意:如果你有一个不同的 gpg2
命令,那么你应该告诉 git 总是去使用它,而不是传统的版本 1 的 gpg
:
$ git config --global gpg.program gpg2
如何使用签名标签
创建一个签名的标签,只要传递一个简单地 -s
开关给 tag
命令即可:
$ git tag -s [tagname]
我们建议始终对 git 标签签名,这样让其它的开发者确信他们使用的 git 仓库没有被恶意地修改过(比如,引入后门):
如何验证签名的标签
验证一个签名的标签,只需要简单地使用 verify-tag
命令即可:
$ git verify-tag [tagname]
如果你要验证其他人的 git 标签,那么就需要你导入他的 PGP 公钥。请参考 “可信任的团队沟通” 一文中关于此主题的指导。
在拉取时验证
如果你从项目仓库的其它复刻中拉取一个标签,git 将自动验证签名,并在合并操作时显示结果:
$ git pull [url] tags/sometag
合并信息将包含类似下面的内容:
Merge tag 'sometag' of [url]
[Tag message]
# gpg: Signature made [...]
# gpg: Good signature from [...]
配置 git 始终签名带注释标签
很可能的是,你正在创建一个带注释标签,你应该去签名它。强制 git 始终签名带注释的标签,你可以设置一个全局配置选项:
$ git config --global tag.forceSignAnnotated true
或者,你始终记得每次都传递一个 -s
开关:
$ git tag -asm "Tag message" tagname
如何使用签名的提交
创建一个签名的提交很容易,但是将它纳入到你的工作流中却很困难。许多项目使用签名的提交作为一种 “Committed-by:” 的等价行,它记录了代码来源 —— 除了跟踪项目历史外,签名很少有人去验证。在某种意义上,签名的提交用于 “篡改证据”,而不是 git 工作流的 “篡改证明”。
为创建一个签名的提交,你只需要 git commit
命令传递一个 -S
标志即可(由于它与另一个标志冲突,所以改为大写的 -S
):
$ git commit -S
我们建议始终使用签名提交,并要求项目所有成员都这样做,这样其它人就可以验证它们(下面就讲到如何验证)。
如何去验证签名的提交
验证签名的提交需要使用 verify-commit
命令:
$ git verify-commit [hash]
你也可以查看仓库日志,要求所有提交签名是被验证和显示的:
$ git log --pretty=short --show-signature
在 git 合并时验证提交
如果项目的所有成员都签名了他们的提交,你可以在合并时强制进行签名检查(然后使用 -S
标志对合并操作本身进行签名):
$ git merge --verify-signatures -S merged-branch
注意,如果有一个提交没有签名或验证失败,将导致合并操作失败。通常情况下,技术是最容易的部分 —— 而人的因素使得项目中很难采用严格的提交验证。
如果你的项目在补丁管理上采用邮件列表
如果你的项目在提交和处理补丁时使用一个邮件列表,那么一般很少使用签名提交,因为通过那种方式发送时,签名信息将会丢失。对提交进行签名仍然是非常有用的,这样其他人就能引用你托管在公开 git 树作为参考,但是上游项目接收你的补丁时,仍然不能直接使用 git 去验证它们。
尽管,你仍然可以签名包含补丁的电子邮件。
配置 git 始终签名提交
你可以告诉 git 总是签名提交:
git config --global commit.gpgSign true
或者你每次都记得给 git commit
操作传递一个 -S
标志(包括 —amend
)。
配置 gpg-agent 选项
GnuPG agent 是一个守护工具,它能在你使用 gpg 命令时随时自动启动,并运行在后台来缓存私钥的密码。这种方式让你只需要解锁一次密钥就可以重复地使用它(如果你需要在一个自动脚本中签署一组 git 操作,而不想重复输入密钥,这种方式就很方便)。
为了调整缓存中的密钥过期时间,你应该知道这两个选项:
default-cache-ttl
(秒):如果在 TTL 过期之前再次使用同一个密钥,这个倒计时将重置成另一个倒计时周期。缺省值是 600(10 分钟)。max-cache-ttl
(秒):自首次密钥输入以后,不论最近一次使用密钥是什么时间,只要最大值的 TTL 倒计时过期,你将被要求再次输入密码。它的缺省值是 30 分钟。
如果你认为这些缺省值过短(或过长),你可以编辑 ~/.gnupg/gpg-agent.conf
文件去设置你自己的值:
# set to 30 minutes for regular ttl, and 2 hours for max ttl
default-cache-ttl 1800
max-cache-ttl 7200
补充:与 ssh 一起使用 gpg-agent
如果你创建了一个 A(验证)密钥,并将它移到了智能卡,你可以将它用到 ssh 上,为你的 ssh 会话添加一个双因子验证。为了与 agent 沟通你只需要告诉你的环境去使用正确的套接字文件即可。
首先,添加下列行到你的 ~/.gnupg/gpg-agent.conf
文件中:
enable-ssh-support
接着,添加下列行到你的 .bashrc
文件中:
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
为了让改变生效,你需要杀掉正在运行的 gpg-agent 进程,并重新启动一个新的登入会话:
$ killall gpg-agent
$ bash
$ ssh-add -L
最后的命令将列出代表你的 PGP Auth 密钥的 SSH(注释应该会在结束的位置显示: cardno:XXXXXXXX,表示它来自智能卡)。
为了启用 ssh 的基于密钥的登入,只需要在你要登入的远程系统上添加 ssh-add -L
的输出到 ~/.ssh/authorized_keys
中。祝贺你,这将使你的 SSH 登入凭据更难以窃取。
此外,你可以从公共密钥服务器上下载其它人的基于 PGP 的 ssh 公钥,这样就可以赋予他登入 ssh 的权利:
$ gpg --export-ssh-key [keyid]
如果你有让开发人员通过 ssh 来访问 git 仓库的需要,这将让你非常方便。下一篇文章,我们将提供像保护你的密钥那样保护电子邮件帐户的小技巧。
via: https://www.linux.com/blog/learn/pgp/2018/3/protecting-code-integrity-pgp-part-6-using-pgp-git
作者:KONSTANTIN RYABITSEV 译者:qhwdw 校对:wxy