可信任签名验证

信任网络可用在具有许多贡献者的情况;此时,CI系统在需要密钥的时候就会试图从预先配置好的密钥服务器中下载公钥如果需要可信任签名,就必须更新密钥)。依据由CI系统直接信任的公钥组建的信任网络,你就可自动确定提交是否可信任,即便提交所对应的公钥没有存放在密钥服务器上也可以确定是否可信任。

为了完成这样的工作,我们把脚本分割成两个部分---获取或者更新给定范围内的所有密钥,接着是真正的签名验证部分。我们先看看密钥收集部分,这个工作实际上微不足道:

  1. $ git log --show-signature \  
  2.   | grep 'key ID' \  
  3.   | grep -o '[A-Z0-9]\+$' \  
  4.   | sort \  
  5.   | uniq \  
  6.   | xargs gpg --keyserver key.server.org --recv-keys $keys 

上面的命令字符串只是通过grep命令从git log的输出即使用--show-signature选项后生成的GPG输出)中提交密钥ID,然后向给定的密钥服务器只请求不重复的密钥。通篇文章里我们使用的代码库只有一个签名---即我自己的签名。而针对大型的代码库,所有不重复的密钥都会被罗列出来。注意:上面的例子没有指定提交的范围;你可以按照自己的意愿把它嵌入到signchk脚本,这样就可以使用同样的范围了,不过严格的来说,并不需要这么做这么做也许在性能上有些许提高,而且这种性能上的提高还取决于你所忽略的提交的数量)。

有了这些更新的密钥,我们就可以根据信任网络对提交进行验证了。某个密钥是否可信任取决于你个人的设置。理念是信任你所配置的信任网络里用户的哪些用户例如Linus的“助理们”)就是可信任的,即便你个人不信任他们也如此。同样的理念也适用于CI服务器,此时你可以用CI服务器的密钥链替换你自己的密钥链这样,你就不需要运行CI服务器,可以自己运行这个脚本)。

很不幸,由于受到目前Git的%G?实现限制, 我们不能够通过检查单行输出给出结果。相反,我们必须解析每个关联提交的--show-signature的输出 如上所示)。把现在的输出和 不带可信任验证的脚本结合起来,我们就会得到以下输出,这才是我们要解析的:

  1. $ git log --pretty="format:%H$t%aN$t%s$t%G?" --show-signature  
  2. f72924356896ab95a542c495b796555d016cbddd       Mike Gerwitz    Yet another foo  
  3. gpg: Signature made Sun 22 Apr 2012 01:37:26 PM EDT using RSA key ID 8EE30EAB 
  4. gpg: Good signature from "Mike Gerwitz (Free Software Developer) <mike@mikegerwitz.com>" 
  5. gpg: WARNING: This key is not certified with a trusted signature!  
  6. gpg:          There is no indication that the signature belongs to the owner.  
  7. Primary key fingerprint: 2217 5B02 E626 BC98 D7C0  C2E5 F22B B815 8EE3 0EAB 
  8. afb1e7373ae5e7dae3caab2c64cbb18db3d96fba       Mike Gerwitz    Modified bar    G  
  9. [...]  

看看上面部分运行结果,你应当注意到第一个提交(f7292)是未签名的,而第二个afb1e)是签了名的。因此,GPG输出应在提交行之前。现在看看我们的目标:

前面的脚本很好地执行了第1个目标,因此我们只需要对这个脚本进行代码提交,使得它能够实现第2个目标。实际上---如果提交行前面的GPG输出表明这个签名是不可信任的,那么我们希望转换其中以"G"结尾的行为其他别的东西。

达到第2个目标有许多方法,我们选择的是在前面脚本上增家几个非常简洁的命令的方法。为了不使输出过滤掉以"G"结尾的行这样的提交都是不可信任的),我们给这样的不信任行后增加了"U"。看-看下面输出:

  1. $ git log --pretty="format:^%H$t%aN$t%s$t%G?" --show-signature \  
  2. > | grep '^\^\|gpg: .*not certified' \  
  3. > | awk '>   /^gpg:/ {  
  4. >     getline;  
  5. >     printf "%s U\n", $0;  
  6. >     next;>   }  
  7. >   { print; }  
  8. > ' \  
  9. > | sed 's/^\^//' 
  10. f72924356896ab95a542c495b796555d016cbddd        Mike Gerwitz    Yet another foo  
  11. afb1e7373ae5e7dae3caab2c64cbb18db3d96fba        Mike Gerwitz    Modified bar    G U  
  12. f227c90b116cc1d6770988a6ca359a8c92a83ce2        Mike Gerwitz    Added bar       G U  
  13. 652f9aed906a646650c1e24914c94043ae99a407        John Doe        Signed off      G U  
  14. 16ddd46b0c191b0e130d0d7d34c7fc7af03f2d3e        John Doe        Added feature X G U  
  15. cf43808e85399467885c444d2a37e609b7d9e99d        Mike Gerwitz    Test commit of foo      G U  

在这儿,我们发现如果我们过滤前天提及的以"G"结尾的行,我们将得到的就像%G?所表示的那样:是不信任的提交,以及错误提交("B")或者未签名提交结尾是空白)。要做到这些,我们首先使用--show-signature选项,给日志输出中增加GPG输出,为了更容易地进行过滤,我们给所有的提交行前加上控制符(^),后面我们会删除这个符号。然后我们过滤所有以控制符开头的行或者包含有"not certified"字符串的行GPG输出中存在这样的行)。如果提交时不可信任的,那么这个就会在提交行之前出现一个"gpg:"行。 接着我们把得到的结果传递给awk命令,它将删除所有以"gpg:"作为前缀的行,然后给下一行也就是提交行)添加上"U"。最后,我们将删除在处理开始时添加的前导控制符(^),得到最终的输出结果。

请注意,通常使用的PGP/GPG我声明我知道这个人就是他们宣称的那个样子“)信托和信任某人提交代码之间有巨大差别。同样地,可能你最大的兴趣是,维护一个完整的独立的信誉网页,给你的CI服务商或者使用的任一个用户进行签名验证。

自动合并签名验证

如果你希望检验每个提交的有效与否,前文提到的脚本非常棒,但是并非每个人都希望做那么多努力。反之,维护的人可能更喜欢只需要标记合并的提交上面提到的选项2),而不是每次合并引入的提交。我们来考虑下对这种情况我们要采用的方法。

假定要用到的提交时r可为空),C'为所有第一级父提交的集合,此时C'=r..Head(范围说明),同时K是给定GPG密钥链中所有公钥   的集合。我们断言:对C'中的每个提交c,密钥链K中一定存在一个密钥k可信任,同时可用来对c的签名进行验证。这个断言   是由函数gGPG)来表示的,如下表达式:∀c∈C' g(c)。

这个脚本与只对单个提交进行签名验证的脚本的唯一不同是 这个脚本只对特定代码分支比如master分支)下的提交进行验证。这一点非常重要---如果我们直接在master上提交,那么我们需要确保这个提交是签了名的(因为,不会存在合并提交)。如果我们需要合并到master分支上,那么就会创建合并提交,这时我们可以
 

对合并提交签名,同时不对合并的所涉及的提交进行签名。如果合并运行的非常快,我们就使用--no-ff选项强制创建合并提交,以避免给每个所涉及到的提交进行签名。

为了模拟能验证此种提交的脚本,咱们先做一些修改来触发合并功能:

  1. $ git checkout -b diverge  
  2. $ echo foo > diverged  
  3. $ git add diverged  
  4. $ git commit -m 'Added content to diverged'[diverge cfe7389] Added content to diverged 1 file changed, 1 insertion(+)  
  5.  create mode 100644 diverged  
  6. $ echo foo2 >> diverged  
  7. $ git commit -am 'Added additional content to diverged'[diverge 996cf32] Added additional content to diverged 1 file changed, 1 insertion(+)$ git checkout master  
  8. Switched to branch 'master'$ echo foo >> foo  
  9. $ git commit -S -am 'Added data to master'You need a passphrase to unlock the secret key foruser: "Mike Gerwitz (Free Software Developer) <mike@mikegerwitz.com>"4096-bit RSA key, ID 8EE30EAB, created 2011-06-16[master 3cbc6d2] Added data to master 1 file changed, 1 insertion(+)$ git merge -S diverge  
  10.  
  11. You need a passphrase to unlock the secret key foruser: "Mike Gerwitz (Free Software Developer) <mike@mikegerwitz.com>"4096-bit RSA key, ID 8EE30EAB, created 2011-06-16Merge made by the 'recursive' strategy.  
  12.  diverged |    2 ++  
  13.  1 file changed, 2 insertions(+)  
  14.  create mode 100644 diverged  

上述操作,确保master和分支都有相应的合并提交以避免快进 (也可以通过用 --no-ff 选项实现). 结果如下 (你本机的哈希结果可能不同):

  1. $ git log --oneline --graph  
  2. *   9307dc5 Merge branch 'diverge' 
  3. |\  
  4. | * 996cf32 Added additional content to diverged  
  5. | * cfe7389 Added content to diverged  
  6. * | 3cbc6d2 Added data to master  
  7. |/  
  8. * f729243 Yet another foo  
  9. * afb1e73 Modified bar  
  10. * f227c90 Added bar  
  11. 652f9ae Signed off  
  12. 16ddd46 Added feature X  
  13. * cf43808 Test commit of foo  

从上图中可以看出,只有两处需要签名: 3cbc6d2, 在master直接创建, 9307dc5---合并提交后生成.  另外两处提交 (996cf32 和 cfe7389) 不需要签名,在合并时就确保了其有效性 (假设提交者是谨慎的).  但怎么忽略这些提交呢?

  1. $ git log --oneline --graph --first-parent  
  2. 9307dc5 Merge branch 'diverge' 
  3. 3cbc6d2 Added data to master  
  4. * f729243 Yet another foo  
  5. * afb1e73 Modified bar  
  6. * f227c90 Added bar  
  7. 652f9ae Signed off  
  8. 16ddd46 Added feature X  
  9. * cf43808 Test commit of foo 

上述例子简单的添加了 --first-parent选项, 这样在遇到有合并提交时只会显示最初的提交记录. 重点就是, 这就只剩下master上的提交记录 (或是你需要参照的分支).这些是需要验证的.

现在的验证工作仅仅需要微调原来的脚本即可:

  1. #!/bin/sh## Validate signatures on only direct commits and merge commits for a particular# branch (current branch)### if a ref is provided, append range spec to include all childrenchkafter="${1+$1..}"# note: bash users may instead use $'\t'; the echo statement below is a more# portable option (-e is unsupported with /bin/sh)t=$( echo '\t' )# Check every commit after chkafter (or all commits if chkafter was not# provided) for a trusted signature, listing invalid commits. %G? will output# "G" if the signature is trusted.git log --pretty="format:%H$t%aN$t%s$t%G?" "${chkafter:-HEAD}" --first-parent \  
  2.   | grep -v "${t}G$"# grep will exit with a non-zero status if no matches are found, which we# consider a success, so invert it[ $? -gt 0 ]  

如果你在刚建好的分支上运行上述脚本, 你会发现分支中不会包含相应的历史提交记录.由于合并提交的自带标记,结果中也不会显示相应的记录(剩下的就是那些未做标记的提交记录).要展示未标记的合并提交, 可以使用以下命令 (忽略 -S选项):

  1. $ git commit --amend[master 9ee66e9] Merge branch 'diverge'$ ./signchk  
  2. 9ee66e900265d82f5389e403a894e8d06830e463        Mike Gerwitz    Merge branch 'diverge'f72924356896ab95a542c495b796555d016cbddd        Mike Gerwitz    Yet another foo  
  3. $ echo $?1 

合并提交将被列出来,需要签名验证.  

[如果需要验证签名的有效性,可以参照以下文章 the section on verifying commits within a web of trust.]

总结

  • 注意安全性. PC机上的资源库是否安全? 哪些用户可以信赖?

    • 主机并没有想象的那么安全。谨慎使用远端资源库.

  • 用GPG 签名你的提交 可以确保身份认证,从而远离危害.

  • 对于大规模的代码合并,最好验证出合适的规则. 具体地说,可以验证每次提交 , 只对合并功能提供签名, 或者在 一次提交完成后 再签名相应的提交.

  • 如果有现成的资源库, 没必要再重写原有的提交记录.

  • 在确定了你的安全策略后,最好能 自动化签名验证以确保提交的安全性.

译文出自:http://www.oschina.net/translate/git-horror-story?lang=chs&page=1#


相关内容