【问题标题】:Verifying signed git commits?验证签名的 git 提交?
【发布时间】:2013-06-28 19:09:08
【问题描述】:

使用较新版本的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

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

【问题讨论】:

  • 我认为应该是git commit ...git log ...。据我所知,gpg 没有添加透明地传递给git 的子命令......我没有任何要测试的存储库,但git show --show-signature &lt;commitish&gt; 工作吗?
  • show_signature 仅在输出中添加内容(请参阅github.com/git/git/blob/master/log-tree.c#L370)。
  • 注意:您很快就会有--raw 用于git verify-tag/git verify-commit。见my answer below
  • 注意:对于 GIt 2.11(2016 年第四季度),git log 引入了额外的状态代码 EXYR 用于 ERRSIGEXPSIG、@ 987654348@和REVKEYSIG,以便%G?的用户获得更多信息。见my edited answer below
  • 使用 Git 2.26(2020 年第一季度),新配置 gpg.minTrustLevel 可以在使用 git verify-tag/verify -commit 时提供帮助。见my edited answer below

标签: git


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    参见commit e18443ecommit aeff29dcommit ca194d5commit 434060ecommit 8e98e5fcommit a4cc18fcommit d66aeff(2015 年 6 月 21 日)brian m. carlson (bk2204)
    (由Junio C Hamano -- gitster --commit 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-commit 共享verify-tag 的行为。


    git 2.9(2016 年 6 月)更新 git merge doc:

    commit 05a5869(2016 年 5 月 13 日)Keller Fuchs (``)
    帮助者:Junio C Hamano (gitster)
    (由 Junio C Hamano -- gitster -- 合并于 commit be6ec17,2016 年 5 月 17 日)

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

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


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

    参见Linus Torvalds (torvalds)commit b624a3e(2016 年 8 月 16 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit 83d9eb0,2016 年 8 月 19 日)

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

    git log --show-signature”和其他显示 PGP 签名验证状态的命令现在显示更长的 key-id,就像上个世纪的 32 位 key-id 一样。

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


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

    参见Michael J Gruber (mjg)commit 661a180(2016 年 10 月 12 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit 56d268b,2016 年 10 月 26 日)

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

    根据gpg2's doc/DETAILS

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

    git pretty-format documentation 现在包括:

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

    Git 2.12(2017 年第一季度)“git tag”和“git verify-tag学会将 GPG 验证状态放入其“--format=&lt;placeholders&gt;”输出格式

    请参阅commit 4fea72fcommit 02c5433commit ff3c8c8(2017 年 1 月 17 日)Santiago Torres (SantiagoTorres)
    请参阅 Lukas Puehringer (``)commit 07d347ccommit 2111aa7commit 94240b9(2017 年 1 月 17 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit 237bdd9,2017 年 1 月 31 日)

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


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

    参见Hans Jerry Illikainen (``)commit 7f8ca20commit ca779e8(2017 年 12 月 10 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit 0433d53,2017 年 12 月 28 日)

    merge:为verifySignatures 添加配置选项

    git merge --verify-signatures 可用于验证提示提交 被合并的分支已正确签名,但很麻烦 每次都必须指定。

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

    git merge config man page 现在改为:

    merge.verifySignatures:
    

    如果为真,则相当于--verify-signatures 命令行选项。


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

    注意:使用 Git 2.19,gpg.format that can be set to "openpgp" or "x509", and gpg.&lt;format&gt;.program that is used to specify what program to use to deal with the format) to allow x.509 certs with CMS via "gpgsm" to be used instead of openpgp via "gnupg".

    commit 4e5dc9c(2018 年 8 月 9 日)Junio C Hamano (gitster)
    帮助者:Vojtech Myslivec (VojtechMyslivec)brian m. carlson (bk2204)Jeff King (peff)
    (由 Junio C Hamano -- gitster -- 合并到 commit 4d34122,2018 年 8 月 20 日)

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

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

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

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

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

    请注意,如果输出未说明签名良好或计算正确但使用不受信任的密钥生成,则代码仍会覆盖从“gpg”(或gpg.program)获得的零退出状态,以捕获写得不好的包装用户可以给我们的“gpg”。

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


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

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

    参见Hans Jerry Illikainen (illikainen)commit 54887b4(2019 年 12 月 27 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit 11ad30b,2020 年 1 月 30 日)

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

    签字人:Hans Jerry Illikainen

    以前,合并和拉取操作的签名验证检查密钥是否在verify_merge_signature() 中具有TRUST_NEVERTRUST_UNDEFINED 的信任级别。

    如果是这样,进程die()'d。

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

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

    这种行为差异可能导致用户错误地认为 Git 始终会考虑其密钥环中密钥的信任级别,即使对于不考虑的操作(例如在 verify-commitverify-tag )

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

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

    GPG 文档在TRUST_ status codes 上说:


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

    - 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_NEVER 或@987654520 @ 都被认为是成功的。

    'U' 的结果具有特殊含义的两种情况是在verify_merge_signature()(这导致gitdie())和format_commit_one()(它影响@987654526 的输出) @格式说明符)。

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

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

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

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

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

    通过在verify_merge_signature() 中引入特殊情况来保持向后兼容性,这样如果没有设置用户可配置的gpg.minTrustLevel,则会强制执行拒绝TRUST_UNDEFINEDTRUST_NEVER 的旧行为。

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

    同样,%G? 格式说明符将继续显示“U”用于使用信任级别为 TRUST_UNDEFINEDTRUST_NEVER, 的密钥创建的签名,即使“U”字符不再存在在signature_check 结构的result 成员中。

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

    另一种方法是简单地删除 verify_merge_signature() 中的信任级别要求。

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

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

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

    git config gpg man page 现在包括:

    gpg.minTrustLevel:

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

    • undefined
    • never
    • marginal
    • fully
    • ultimate

    Git 2.26 (Q1 2020)、“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(2020 年第二季度)中,与 GnuPG 接口的代码已被重构。

    参见Hans Jerry Illikainen (illikainen)commit 6794898commit f1e3df3(2020 年 3 月 4 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit fa82be9,2020 年 3 月 27 日)

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

    签字人:Hans Jerry Illikainen

    此提交将verify_signed_buffer()gpg-interface.c 之外的使用重构为使用check_signature()

    它还将verify_signed_buffer() 转换为文件本地函数,因为它现在仅由check_signature() 在内部调用。

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

    现在只使用check_signature()

    verify_signed_buffer() 函数不能防范 duplicate signatures as described by 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(2021 年第一季度)中,签名提交和标签现在允许验证对象,其两个对象名称(一个在 SHA-1 中,另一个在 SHA-256 中)都已签名。

    请参阅commit 9b27b49commit 88bce0ecommit 937032ecommit 482c119(2021 年 2 月 11 日)和commit 1fb5cf0commit 83dff3e(2021 年 1 月 18 日)brian m. carlson (bk2204)
    (由Junio C Hamano -- gitster --commit 15af6e6 合并,2021 年 2 月 22 日)

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

    签字人:brian m.卡尔森

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


    Brandon 提议 in the comments 一个 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"
    

    【讨论】:

    • 干得好
    • 有没有办法根据返回的内容为%G? 着色?
    • @Brandon 我不知道。
    • 我可以从stackoverflow.com/a/62772985/1356754 获得灵感,通过 sed 进行管道传输
    • @Brandon 确实可以,做得很好。你有没有创建一个 git 别名?如果是这样,它看起来像什么?
    【解决方案3】:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2019-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-02
      • 1970-01-01
      • 2017-12-19
      相关资源
      最近更新 更多