141

使用较新的版本,git可以使用 PGP 密钥对单个提交(除了标签)进行签名:

git commit -m "some message" -S

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

$ 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

但是有没有办法以编程方式验证给定提交上的签名,而不是通过 grepping 的输出git log?我正在寻找与提交等效的git tag -v东西——它将提供一个退出代码,指示给定提交上是否存在有效签名。

4

3 回答 3

162

以防万一有人像我一样通过搜索引擎访问此页面:自从问题发布以来的两年内,已经提供了新工具:现在有用于此任务的 git 命令:git verify-commit并且git verify-tag可用于验证提交和标签,分别。

于 2015-05-14T11:44:15.943 回答
54

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

请参阅提交 e18443e提交 aeff29d提交 ca194d5提交 434060e提交 8e98e5f提交 a4cc18f提交 d66aeff(2015 年 6 月 21 日),作者为brian m。卡尔森 ( bk2204) .
(由Junio C Hamano 合并gitster——提交 ba12cb2中,2015 年 8 月 3 日)

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

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

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

加:

verify-tag如果签名良好但密钥不受信任,则成功退出。verify-commit退出失败。
这种行为上的差异是出乎意料和不受欢迎的。
由于verify-tag较早存在,添加一个失败的测试以具有verify-commitshareverify-tag的行为。


git 2.9(2016 年 6 月)更新git 合并文档

请参阅Keller Fuchs (``)的提交 05a5869(2016 年 5 月 13 日) 。 帮助者:Junio C Hamano ( )(由Junio C Hamano 合并——提交 be6ec17中,2016 年 5 月 17 日)
gitster
gitster

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

验证正在合并的侧分支的提示提交是否使用有效密钥签名,即具有有效 uid 的密钥:在默认信任模型中,这意味着签名密钥已由可信密钥签名。
如果侧分支的提示提交未使用有效密钥签名,则合并被中止


更新 Git 2.10(2016 年第三季度)

请参阅Linus Torvalds ( )的提交 b624a3e(2016 年 8 月 16 日) 。(由Junio C Hamano 合并 -- --83d9eb0 提交中,2016 年 8 月 19 日)torvalds
gitster

gpg-interface: 验证 pgp 签名时首选“长”密钥格式输出

" git log --show-signature" 和其他显示 PGP 签名验证状态的命令现在显示更长的 key-id,因为上世纪的 32 位 key-id 也是如此。

Linus 的原始版本被重新定位以适用于维护轨道,以防万一过去陷入困境的二进制分发者想要将其带到他们较旧的代码库中。


Git 2.11+(2016 年第四季度)将更加精确。

请参阅Michael J Gruber ( )的提交 661a180(2016 年 10 月 12 日) 。(由Junio C Hamano 合并——提交 56d268b中,2016 年 10 月 26 日)mjg
gitster

“”漂亮格式说明符中显示的 GPG 验证状态%G?不足以区分过期密钥的签名、撤销密钥的签名等。
已分配新的输出字母来表达它们

根据gpg2的doc/DETAILS

对于每个签名,只会发出代码、 、 、或GOODSIGBADSIGEXPSIG一个。EXPKEYSIGREVKEYSIGERRSIG

git pretty-format文档现在包括:

  • ' %G?': 显示
  • " G" 代表一个好的(有效的)签名,
  • " B" 代表错误的签名,
  • " U" 用于具有未知有效性的良好签名,
  • " X" 对于一个已经过期的好的签名,
  • " Y" 用于由过期密钥生成的良好签名,
  • " R" 用于由撤销的密钥生成的良好签名,
  • E”如果无法检查签名(例如缺少密钥),“ N”表示没有签名

Git 2.12 (Q1 2017) " git tag" 和 " git verify-tag"学会了将 GPG 验证状态放在他们的 " --format=<placeholders>" 输出格式中。

请参阅Santiago Torres ( )的commit 4fea72fcommit 02c5433commit ff3c8c8(2017 年 1 月 17 日) 。 请参阅Lukas Puehringer (``)的提交 07d347c提交 2111aa7提交 94240b9(2017 年 1 月 17 日) 。(由Junio C Hamano 合并——提交 237bdd9中,2017 年 1 月 31 日)SantiagoTorres

gitster

添加--formatgit tag -v静音 GPG 验证的默认输出,而是打印格式化的标签对象。
这允许调用者在 GPG 验证时交叉检查来自 refs/tags 的标记名与来自标记对象头的标记名。


Git 2.16(2018 年第一季度)将允许提交签名验证更加自动化,并带有merge.verifySignatures配置变量。

请参阅Hans Jerry Illikainen (``)的commit 7f8ca20commit ca779e8(2017 年 12 月 10 日) 。(由Junio C Hamano 合并 -- --0433d53 提交中,2017 年 12 月 28 日)
gitster

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 中,gpg.format可以设置为“ openpgp”或“ x509”,gpg.<format>.program用于指定使用什么程序来处理格式)以允许通过“ gpgsm”而不是openpgp通过CMS 使用 x.509 证书“ gnupg”。

请参阅Junio C Hamano ( ) 的提交 4e5dc9c(2018 年 8 月 9 日。 帮助者:Vojtech Myslivec ( ) , brian m. 卡尔森 ( )杰夫金 ( )(由Junio C Hamano 合并 -- --提交 4d34122中,2018 年 8 月 20 日)gitster
VojtechMyslivecbk2204peff
gitster

gpg-interface: 将退出状态从gpg后面传播给调用者

当 gpg-interface API 在 2015 年中期 v2.6.0-rc0~114 左右统一​​支持签名标签和签名提交的签名验证代码路径时,我们不小心放松了 GPG 签名验证。

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

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

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

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

我们可以从这个回退代码中排除 " U"ntrusted 支持,但这会在一次提交中进行两个向后不兼容的更改,所以现在让我们避免这种情况。
如果需要,可以进行后续更改。


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

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

请参阅Hans Jerry Illikainen ( ) 的提交 54887b4(2019 年 12 月 27 日(由Junio C Hamano 合并 -- --提交 11ad30b中,2020 年 1 月 30 日)illikainen
gitster

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

签字人:汉斯·杰里·伊利凯宁

以前,合并和拉取操作的签名验证会检查密钥的信任级别是否为TRUST_NEVERor 或TRUST_UNDEFINEDin verify_merge_signature()

如果是这样的话,这个过程die()'d。

其他进行签名验证的代码路径完全依赖于check_commit_signature().

使用好的密钥制作的签名,无论其信任级别如何,都被认为是有效的check_commit_signature()

这种行为差异可能会导致用户错误地假设 Git 始终考虑其密钥环中密钥的信任级别,即使对于不考虑的操作(例如在 averify-commit或期间verify-tag也是如此。

它的工作方式是gpg-interface.c将密钥/签名状态的结果result结构成员中最低的两个信任级别存储起来signature_check(遇到的这些状态行中的最后一个被写入result)。

这些在 GPG 中分别记录在General status codes和小节下Key related

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' ' 的结果具有特殊含义的两种情况是 in (这会verify_merge_signature()导致)和 in (它影响格式说明符的输出)。gitdie()format_commit_one()%G?

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

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

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

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

它将信任级别验证合并到结构中并向结构中gpg-interface.c添加新trust_level成员signature_check

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

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

类似地,%G?格式说明符将继续U为使用具有信任级别的密钥创建的签名显示 ' ',TRUST_UNDEFINED或者TRUST_NEVER,即使结构成员中U不再存在“ ”字符。resultsignature_check

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

另一种方法是简单地将信任级别要求放在verify_merge_signature().

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

但是,要求签署密钥的最低信任级别似乎确实有一个真实的用例。

例如,Qubes OS 项目使用的构建系统当前解析来自 verify-tag 的原始输出,以便为用于签署 git tags的密钥声明最低信任级别。

git config gpg手册页现在包括:

gpg.minTrustLevel:

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

  • undefined
  • never
  • marginal
  • fully
  • ultimate

Git 2.26 (Q1 2020)中," git show" 和其他人在其错误输出中以原始格式提供了一个对象名称,该名称已更正为以十六进制形式提供。

show_one_mergetag:以十六进制形式打印非父级。

当一个合并标签命名一个非父级时,这可能发生在一个浅克隆之后,它的哈希之前被打印为原始数据。
改为以十六进制形式打印。

git -C shallow log --graph --show-signature -n1 plain-shallow经过一段时间的测试git clone --depth 1 --no-local . shallow


在 Git 2.27(2020 年第二季度)中,与 GnuPG 交互的代码已经过重构。

参见提交 6794898提交 f1e3df3(2020 年 3 月 4 日),作者Hans Jerry Illikainen ( illikainen)
(由Junio C Hamano 合并 -- gitster--fa82be9 提交中,2020 年 3 月 27 日)

gpg-interface: 首选check_signature()GPG 验证

签字人:汉斯·杰里·伊利凯宁

此提交重构了使用verify_signed_buffer()outside ofgpg-interface.ccheck_signature()代替使用。

它也变成verify_signed_buffer()了文件本地函数,因为它现在仅由check_signature().

以前在 Git 的不同部分中使用了两个全局范围的函数来执行 GPG 签名验证:verify_signed_buffer()check_signature().

现在只check_signature()使用。

如 Michał Górny 所述,verify_signed_buffer()函数不能防止重复签名。

相反,它只确保 GPG 的退出代码正确无误,并且至少存在一个GOODSIG状态字段。

这与check_signature()遇到多个签名时返回错误相反。

verify_signed_buffer()如果调用者自己不解析和验证 GPG 状态消息的各个部分,则较低程度的验证会导致使用问题。

处理这些消息似乎是一项应该保留给gpg-interface.c函数的任务check_signature()

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

现在所有进行签名验证的操作都共享一个到gpg-interface.c.

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


使用 Git 2.31(2021 年第一季度),签名提交和标签现在允许验证对象,其两个对象名称(一个在 SHA-1 中,另一个在 SHA-256 中)都已签名。

请参阅提交 9b27b49提交 88bce0e提交 937032e提交 482c119(2021 年 2 月 11 日)和提交 1fb5cf0提交 83dff3e(2021 年 1 月 18 日),作者为brian m。卡尔森 ( bk2204) .
(由Junio C Hamano 合并 -- gitster--提交 15af6e6中,2021 年 2 月 22 日)

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

签字人:brian m. 卡尔森

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


Brandon在评论中提出了一个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"
于 2015-08-16T18:58:21.980 回答
5

对代码的粗略检查表明没有这种直接的方法。

git 源中的所有测试都依赖于grepping 的输出git show(有关测试,请参见t/t7510-signed-commit.sh)。

您可以使用类似的方法自定义输出--pretty "%H %G?%"以使其易于解析。

看来您可以要求git merge验证签名,但同样,它的测试依赖于grep(参见t/t7612-merge-verify-signatures.sh)。看起来无效的签名会导致git merge以错误的签名退出,因此您今天可能会通过在某处进行测试合并并丢弃该合并来解决此问题,但这似乎比仅调用 grep 更糟糕。

于 2013-06-28T19:40:33.043 回答