验证签名的 git 提交?

使用新版本的 git,可以使用 PGP 键来签署单独的提交(除了标记之外) :

git commit -m "some message" -S

您可以使用 --show-signature选项在 git log的输出中显示这些签名:

$ git log --show-signature
commit 93bd0a7529ef347f8dbca7efde43f7e99ab89515
gpg: Signature made Fri 28 Jun 2013 02:28:41 PM EDT using RSA key ID AC1964A8
gpg: Good signature from "Lars Kellogg-Stedman <lars@seas.harvard.edu>"
Author: Lars Kellogg-Stedman <lars@seas.harvard.edu>
Date:   Fri Jun 28 14:28:41 2013 -0400


this is a test

但是,除了通过抓取 git log的输出以外,还有其他方法可以通过编程验证给定提交上的签名吗?我正在寻找与 git tag -v等价的提交——它将提供一个退出代码,指示在给定的提交上是否存在有效的签名。

68272 次浏览

粗略地检查代码表明,没有这样的直接方法。

Git 源代码中的所有测试都依赖于 grepping 的 git show输出(参见 T7510-signed-commit.sh了解测试)。

您可以使用类似 --pretty "%H %G?%"的东西来自定义输出,以便于解析。

似乎您可以要求 git merge验证签名,但是它的测试依赖于 grep(参见 T7612-merge-verify-signatures.sh)。看起来一个无效的签名确实会导致 git merge退出时带有一个错误的签名,所以你今天可以通过在某个地方进行测试合并并抛出合并来解决这个问题,但是这似乎比仅仅调用 grep 更糟糕。

以防有人像我一样通过搜索引擎访问这个页面: 自问题发布以来的两年里,新的工具已经出现了: 现在这个任务有了 git 命令: git verify-commitgit verify-tag可以分别用来验证提交和标记。

注意: 直到 git 2.5,git verify-commitgit verify-tag只显示一个人类可读的消息。
如果您想自动化检查,git 2.6 + (Q32015)添加了另一个输出。

提交 e18443e犯罪犯罪犯下434060e第八季,第5集提交 a4cc18f提交 d66aeff(2015年6月21日) by 卡尔森(bk2204)
(由 朱尼奥 · C · 哈马诺 gitster于2015年8月3日在 提交 ba12cb2合并)

verify-tag/verify-commit: 添加打印原始 gpg 状态信息的选项

默认情况下,verify-tag/verify-commit在标准错误上显示人类可读的输出。
但是,访问原始的 gpg 状态信息也很有用,这些信息是机器可读的,允许自动实现签名策略

添加一个 --raw选项,使 verify-tag生成标准错误的 gpg 状态信息,而不是人类可读的格式。

另外:

如果签名是正确的,但是密钥是正确的,则 verify-tag成功退出 不可信 verify-commit退出失败。
这种行为上的分歧是意想不到的,也是不受欢迎的。
由于 verify-tag早已存在,因此添加一个失败的测试,以使 verify-commit共享 verify-tag的行为。


Git 2.9(2016年6月)更新 混蛋合并医生:

提交05a5869(2016年5月13日) by Keller Fuchs (“)
帮助者: 滨野俊男(gitster)
(由 朱尼奥 · C · 哈马诺 gitster于2016年5月17日在 从6月17日开始合并)

--verify-signatures:
--no-verify-signatures:

验证合并的边分支的提示提交是否使用有效的密钥签名,即 < strong > 具有有效 uid 的密钥: 在缺省信任模型中,这意味着签名密钥已经使用受信任密钥签名。
如果侧分支的提示提交没有使用有效的密钥签名,则合并将中止 .


更新 Git 2.10(Q32016)

犯下 b624a3e(2016年8月16日) by Linus Torvalds (torvalds)
(由 朱尼奥 · C · 哈马诺 gitster于2016年8月19日在 第83季第9集合并)

gpg-interface: 在验证 pgp 签名时更喜欢“ long”键格式输出

git log --show-signature”和其他显示 PGP 签名验证状态的命令现在显示较长的密钥 ID,因为32位密钥 ID 是上个世纪的做法。

Linus 的原始版本被重新设置为应用于维护轨道,以防止卡在过去的二进制分发服务器将其带到旧的代码库中。


Git 2.11 + (2016年第四季度)甚至会更精确。

犯下661a180(2016年10月12日) by 迈克尔 · J · 格鲁伯(mjg)
(由 朱尼奥 · C · 哈马诺 gitster于2016年10月26日在 提交56d268b合并)

在“ %G?”漂亮格式说明符中显示的 GPG 验证状态不够丰富,无法区分由过期密钥制作的签名、由被撤销密钥制作的签名等。
新的输出字母被指定用来表示它们

根据 Gpg2是 doc/DETAILS:

对于每个签名,只会发出代码 GOODSIGBADSIGEXPSIGEXPKEYSIGREVKEYSIGERRSIG中的一个。

git pretty-format文档现在包括:

  • %G?”: 显示
  • G”表示良好(有效)签名,
  • B”代表不良签名,
  • U”表示有效性未知的良好签名,
  • X”表示已过期的良好签名,
  • Y”表示由过期密钥产生的良好签名,
  • R”表示由被撤销的密钥产生的良好签名,
  • 如无法检查签名(例如遗失密钥) ,请输入“ E” “ N”代表没有签名

Git 2.12(2017年第一季)「 git tag」及「 git verify-tag」。

承认吧提交02c5433提交 ff3c8c8(2017年1月17日) by 圣地亚哥 · 托雷斯(SantiagoTorres)
提交07d347c承诺2111A7提交94240b9(2017年1月17日) by 卢卡斯 · 普林格(“)
(由 朱尼奥 · C · 哈马诺 gitster于2017年1月31日在 提交237bdd9合并)

--format添加到 git tag -v会减弱 GPG 的默认输出 验证,而是打印格式化的标记对象。
这允许调用者用 GPG 验证时标记对象头中的标记名。


Git 2.16(Q12018)将允许使用 merge.verifySignatures配置变量更自动地进行提交签名验证。

犯下7f8ca20犯罪(2017年12月10日) by Hans Jerry Illikainen (“)
(由 朱尼奥 · C · 哈马诺 gitster于2017年12月28日在 提交0433d53合并)

merge: 为 verifySignatures添加配置选项

git merge --verify-signatures可用于验证提示是否提交 正在合并的分支的正确签名,但是签名很麻烦 每次都必须说明。

添加一个默认情况下启用此行为的配置选项,该选项 可以被 --no-verify-signatures覆盖。

git merge配置手册页现在的内容是:

merge.verifySignatures:

如果为 true,则等效于 --verify-signatures命令行选项。


Git 2.19(2018年第三季度)甚至更有帮助,因为“ git verify-tag”和“ git verify-commit”已被教导使用底层“ gpg --verify”的退出状态来表示他们发现的不良或不可信签名。

注意: 使用 Git 2.19,ABC0可以设置为“ openpgp”或“ ABC2”,而 ABC3用于指定使用什么程序来处理格式) ,以允许通过“ ABC4”使用带有 CMS 的 X.509证书代替通过“ ABC6”使用 openpgp

犯罪现场调查,第四季,第5集(2018年8月9日) by 滨野俊男(gitster)
帮助: Vojtech Myslivec (VojtechMyslivec)卡尔森(bk2204)杰夫 · 金(peff)
(由 朱尼奥 · C · 哈马诺 gitster于2018年8月20日在 提交4d34122合并)

gpg-interface: 将退出状态从 gpg传播回调用方

当 GPG-interface API 在2015年中期在 v2.6.0-rc0 ~ 114版本左右统一支持签名标记和签名提交的签名验证代码路径时,我们意外地放松了 GPG 签名验证。

在更改之前,通过从 GPG 查找“ G”ood 签名来验证签名提交,而忽略“ gpg --verify”进程的退出状态,而通过简单地传递“ "gpg --verify”的退出状态来验证签名标记。

我们目前使用的统一代码忽略了“ gpg --verify”的退出状态,当签名与一个未过期的密钥匹配时返回成功验证,而不管密钥是否受信任(即除了“ G”好的密钥,我们还接受“ U”不受信任的密钥)。

当底层的“ gpg --verify”(或由“ gpg.program”配置变量指定的自定义命令)这样做时,使这些命令以退出状态发出失败信号。
这实质上改变了它们的行为,以一种向后不兼容的方式拒绝使用不受信任的密钥生成的签名,即使它们正确地验证了,这就是“ gpg --verify”的行为方式。

注意,如果输出没有说明签名是正确的,或者计算是正确的,但是使用了不受信任的密钥,那么代码仍然覆盖从“ gpg”(或者 gpg.program)获得的零退出状态,以捕获用户可能给我们的“ gpg”周围写得不好的包装。

我们可以从这个回退代码中排除“ U”不受信任的支持,但是这将在一个提交中产生两个向后不兼容的更改,所以现在我们要避免这种情况。
如果需要,可以进行后续变更。


在进行任何加密之前,需要对密钥进行信任/签名

在信任方面,取得了一些进展:
在 Git 2.26(Q12020)中,引入了 gpg.minTrustLevel配置变量来告诉各种签名验证代码路径所需的最小信任级别。

提交54887b4(2019年12月27日) by Hans Jerry Illikainen (illikainen)
(由 朱尼奥 · C · 哈马诺 gitster提交11ad30b合并,2020年1月30日)

gpg-interface : 添加 minTrustLevel 作为配置选项

签名: Hans Jerry Illikainen

以前,对于 merge 和 pull 操作的签名验证会检查密钥在 verify_merge_signature()中是否具有 TRUST_NEVERTRUST_UNDEFINED的信任级别。

如果是这样,进程 die()

完成签名验证的其他代码路径完全依赖于来自 check_commit_signature()的返回代码。

无论信任级别如何,使用好的密钥创建的签名被 check_commit_signature()认为是有效的。

这种行为上的差异可能是 导致用户错误地认为他们密钥环中的密钥的信任级别总是被 Git 考虑,即使对于那些没有考虑的操作(例如在 ABC0或 verify-tag期间)也是如此

它的工作方式是通过 gpg-interface.c存储来自键/签名状态 还有的结果,signature_check结构的 result成员的最低两个信任级别(遇到的最后一个状态行被写入到 result)。

这些分别在 General status codesKey related小节下的 GPG 中记录。

GPG 文档在 TRUST_ status密码上说明了以下内容:


这是几个类似的状态代码:

- TRUST_UNDEFINED <error_token>
- TRUST_NEVER     <error_token>
- TRUST_MARGINAL  [0  [<validation_model>]]
- TRUST_FULLY     [0  [<validation_model>]]
- TRUST_ULTIMATE  [0  [<validation_model>]]

对于好的签名,会发出这些状态行中的一行来指示用于创建签名的密钥的有效性。
错误令牌值目前只由 gpgsm 发出。


我的解释是,信任级别在概念上不同于密钥和/或签名的有效性。

这似乎也是 check_signature()中旧代码的假设,其中“ G”(如 GOODSIG)和“ U”(如 TRUST_NEVERTRUST_UNDEFINED))的结果都被认为是成功的。

U”的结果具有特殊意义的两种情况是在 verify_merge_signature()(这导致 gitdie())和 format_commit_one()(它影响 %G?格式说明符的输出)中。

我认为重构对 TRUST_ status行的处理是有意义的,这样用户就可以配置一个全局执行的最低信任级别,而不是让 git的各个部分(例如合并)自己来做(除了有向下兼容的宽限期)。

我还认为不将信任级别存储在与密钥/签名状态相同的结构成员中是有意义的。

虽然 TRUST_ status代码的存在确实意味着签名是好的(参见上面所包含的代码片段的第一段) ,但就我所知,来自 GPG 的状态行的顺序并没有得到很好的定义; 因此,如果它们存储在 signature_check结构的同一个成员中,信任级别似乎可以被密钥/签名状态覆盖。

这个补丁引入了一个新的配置选项: gpg.minTrustLevel

它将信任级验证合并到 gpg-interface.c,并向 signature_check结构添加一个新的 trust_level成员。

向后兼容性是通过在 verify_merge_signature()中引入一种特殊情况来维护的,如果没有设置用户可配置的 gpg.minTrustLevel,那么就会强制执行拒绝 TRUST_UNDEFINEDTRUST_NEVER的旧行为。

另一方面,如果设置了 gpg.minTrustLevel,则该值将覆盖旧的行为。

类似地,即使 signature_check结构的 result成员中不再存在‘ U’字符,%G?格式说明符也将继续显示用信任级别为 TRUST_UNDEFINEDTRUST_NEVER,的密钥制作的签名的‘ U’。

还为希望显示签名的所有可能信任级别的用户引入了新的格式说明符 %GT

另一种方法是简单地放弃 verify_merge_signature()中的信任级需求。

这也会使行为与 Git 中执行签名验证的其他部分保持一致。

然而,要求签名密钥的最低信任级别似乎有一个真实的用例。

例如,Quubes OS 项目所使用的构建系统当前解析了 valid- tag 的原始输出,以断言用于 在 Git 标签上签名的键的最小信任级别。

git config gpg手册页现在包括:

MinTrustLevel:

指定用于签名验证的最低信任级别。
如果未设置此选项,则合并操作的签名验证需要至少具有 marginal信任的密钥。
执行签名验证的其他操作需要至少具有 undefined信任的密钥。
设置此选项将覆盖所有操作所需的信任级别。支持的值按重要性递增顺序:

  • undefined
  • never
  • marginal
  • fully
  • ultimate

对于 Git 2.26(Q12020),“ git show”和其他一些对象在其错误输出中以原始格式给出了一个对象名,这个错误输出已经更正为以十六进制给出。

Show _ one _ mergetag: 以十六进制格式打印非父文件。

当合并标记命名非父标记时,这可能发生在浅表 它的散列以前是作为原始数据打印的。
而是以十六进制的形式打印出来。

git clone --depth 1 --no-local . shallow之后用 git -C shallow log --graph --show-signature -n1 plain-shallow测试


使用 Git 2.27(Q22020) ,与 GnuPG 接口的代码已经被重构。

提交6794898提交 f1e3df3(2020年3月4日) by Hans Jerry Illikainen (illikainen)
(由 朱尼奥 · C · 哈马诺 gitster承认吧合并,2020年3月27日)

gpg-interface : 更喜欢 check_signature()用于 GPG 验证

签名: Hans Jerry Illikainen

这个提交重构了 gpg-interface.c之外的 verify_signed_buffer()的使用,改为使用 check_signature()

它还将 verify_signed_buffer()转换为 file-local 函数,因为它现在只由 check_signature()在内部调用。

以前 Git 的不同部分使用两个全局作用域函数来执行 GPG 签名验证: verify_signed_buffer()check_signature()

现在只使用 check_signature()

verify_signed_buffer()函数不防范 如 Micha Górny 所描述的重复签名

相反,它只能确保 GPG 的退出代码是正确的,并且至少有一个 GOODSIG状态字段。

这与 check_signature()形成对比,check_signature()在遇到多个签名时返回错误。

如果调用方不解析和验证 GPG 状态消息本身的各个部分,验证程度较低会使 verify_signed_buffer()的使用产生问题。

处理这些消息看起来像是一个应该保留给 gpg-interface.c和函数 check_signature()的任务。

此外,使用 verify_signed_buffer()使得引入依赖于 GPG 状态行内容的新功能变得困难。

现在所有执行签名验证的操作共享到 gpg-interface.c的单个入口点。

这使得将 GPG 签名验证中更改过的或附加的功能传播到 Git 的所有部分变得更加容易,而无需执行不同程度的验证 的奇怪边界情况。


对于 Git 2.31(Q12021) ,已签名的提交和标记现在允许验证对象,其两个对象名称(一个在 SHA-1中,另一个在 SHA-256中)都是已签名的。

参见 犯下9b27b49犯罪提交937032e第四季,第119集(2021年2月11日)和 提交1fb5cf0犯下83dff3e(2021年1月18日)。
(由 朱尼奥 · C · 哈马诺 gitster第十五季,第6集合并,2021年2月22日)

commit : 解析签名提交时忽略其他签名

签名: Brian M. Carlson

当我们创建具有多个签名的提交时,这两个签名都不包括另一个签名。
因此,当我们生成已签名的有效负载以便验证提交时,我们必须去掉任何其他签名,否则有效负载将与已签名的有效负载不同。
这样做,并在准备用多个算法进行验证时,将要验证的算法传递给 parse_signed_commit


Brandon 提议使用 在评论中 a git log别名,其中显示关键州:

[alias]
lg = "!f() { \
git log --all --color --graph --pretty=format:'%C(bold yellow)<sig>%G?</sig>%C(reset) %C(red)%h%C(reset) -%C(yellow)%d%C(reset) %s %C(green)(%cr) %C(blue)<%an>%C(reset)' | \
sed \
-e 's#<sig>G</sig>#Good#' \
-e 's#<sig>B</sig>#\\nBAD \\nBAD \\nBAD \\nBAD \\nBAD#' \
-e 's#<sig>U</sig>#Unknown#' \
-e 's#<sig>X</sig>#Expired#' \
-e 's#<sig>Y</sig>#Expired Key#' \
-e 's#<sig>R</sig>#Revoked#' \
-e 's#<sig>E</sig>#Missing Key#' \
-e 's#<sig>N</sig>#None#' | \
less -r; \
}; f"