强化信任既然你已经确定了适合具体项目/仓库的安全策略(至少假设是这样),那么接下来就需要有某种方式来加强签名策略。手工强化是可行的,不过可能容易出现人为错误,而且需要(“只是让其通过”)同行评审,甚至还不必要的浪费时间。非常幸运,其中有一种方法就是:你编写脚本,然后坐下来休息并乐呵呵地运行这个脚本。 首先我们看看自动任务中较简单的---检查并确认每个提交都既签了名,又得到(信任站点的)信任。这种实现还满足了方法3里合并方面的要求。然而,也许并不是所有的提交都考虑进来。不过,如果你有一个具有相当可观数量提交的代码库,那么你就可以做到所有的提交都既签了名又得到信任。如果你想进行回溯,并对所有这些提交进行签名,那么你就彻底地更改了整个代码库中的历史信息,这会让其他用户头痛不已。相反,你可以考虑在某个提交之后开始进行安全检查。 简说提交的历史信息Git中每一个提交的SHA-1哈希值是根据每一个提交的增量和头信息来生成的。头信息里包含着这一提交的父提交的头信息,父提交的头信息里又包含它的父提交的头信息----以此类推。另外,Git根据代码库里的整个历史信息来生成请求修改的提交信息。这也就意味着历史信息在没有通知某个人的情况下不得更改(实际上,不全是这样的;我们稍后讨论这个问题)。例如,看看下面代码分支: Pre-attack: ---o---o---A---B---o---o---H a1b2c3d^ 如上,H表示的是当前的头信息,标识为A的提交是B提交的父提交。为了讨论方便,我们假设提交A是由SHA-1哈希值a1b2c3来确认的。我们再假设攻击者决定用另一个提交替换A提交。要进行替换的话,这个提交的SHA-1哈希值就会发生变更,以匹配头中新的增量和内容信息。新提交标识为X: Post-attack: ---o---o---X---B---o---o---H d4e5f6a^ ^!expects parent a1b2c3d 现在我们会遇到问题;当对提交B运行git时(记住:Git一定会用产生H的所有历史信息来构造H的。),Git将会检查SHA-1哈希值,然后就会注意到这个哈希值已经与其父提交的哈希值不一致了。攻击者是无法更改提交B里的哈希值的,因为用来生成某个提交SHA-1的头信息是针对某一提交的,这也就意味着B已经有一个完全不同的SHA-1哈希值了(技术上来说,这个提交已经不在是B提交了---它已经是另一个完全不同的提交了;为了方便说明,我们忍让使用B标识)。这将会使得任何B的子提交无效,以此类推。因此要对某个提交的历史信息重写,那么位于这个提交后的所有提交都必须进行重写(通过git rebase来完成)。要想这么做的话,H的SHA-1哈希值也必须得到更改。否则,H的历史信息将是无效的,而且在在你试图对代码进行检出的时候,Git会立即抛出错误信息。 这里有一个非常重要的结论——对于任何的提交。我们可以放心,如果它在本地存储器上存在,Git总是会重构,使之提交的能与被创建(包括所有之前的历史创建及提交)时一致,除非不这样做。的确,Linus提及了在Google的一次展示, 他只 需要记住SHA-1散列上的一个提交,放心吧,它会把它发送到其他的存储器上,倘若我们的东西丢失,之前的提交会发送一份完全一致的提交到其他人的存储器上。这对我们意味着什么?是的,这意味着我们不需要强制重写历史记录到每个单一的提交上,因为我们其他的历史提交是被保证的。唯一的缺点是,提交历史本身可能已经被利用起来,类似于我们开头讲的故事,但是许多过去的提交记录是被自动签名的,这样对于一个给定的作者将不能抓住类似的事情。 这就是说,明白存储的完整性保障是重要的,尽管哈希碰撞不会发生——就是说,如果攻击者能对不同的数据创建一样的SHA-1哈希,那么子提交将仍然是有效的,而存储库就已经成功地被破解了。从2005年开始,可用的哈希计算速度快得超过了强力破解,这样,在SHA-1上的缺陷就变得众所周知了,尽管利用这一点并不廉价。基于这样一个事实,为了你的储存库的安全,将来的某个时候,SHA-1将会瘫痪,就像现在的MD5一样。在那个时间点上,Git可能会提供一个安全的迁移方案类似SHA-256算法或者更好的算法。的确,SHA-1哈希不能保证Git的密码安全。 正是如此,大部分人可能会不再去看他/她的历史记录,我们将会在这个假设下实现我们的操作,这提供了能去忽略所有之前确认提交的能力。如果某人希望去验证所有的提交,只是参考提交可能就会遗漏。 自动进行签名验证验证某些提交是可信任的想法非常简单: 假定要用到的提交是r(可为空),C为所有提交的集合,此时C=r..Head(范围说明),同时K是给定GPG密钥链中所有公钥 的集合。我们断言:对C中的每个提交c,密钥链K中一定存在一个密钥k可信任,同时可用来对c的签名进行验证。这个断言 是由函数g(GPG)来表示的,如下表达式:∀c∈Cg(c)。 很幸运,就像我们在前一节在git log上使用--show-signature选项后看到的,Git帮助我们验证了签名;这样就把我们的验证签名实现简化为一个简单的shell脚本。不过我们得到的输出不是很适合于解析。如果我们可以让每个提交的提交和签名信息出现在单行上就很适合解析了。这可以通过--pretty选项完成,不过还有这样一个问题--在编写(Git 1.7.10)文档的时候,GPG --pretty选项没有写入文档中。 format_commit_one() in pretty.c 有三个不同的格式:
我们感兴趣的是使用最精确和最小限度的表达---¥G?。因为这个占位符只是匹配GPG输出的内容,字符窜“gpg: Can’t check signature: public key not found”不能对应 insignature_check, 不能识别的字符讲会输出空字符窜,不是"B".这点并不明显,所以我不确信是否这个在以后的版本会改变。幸运的是,我们只是对”G"感兴趣,所以这个细节对于我们的实施来说不关键。记住这点,我们能够做提交一次输出某个有用的一行。下面是基于演示上面的merge option #3 的输出的结果: $ git log --pretty="format:%H %aN %s %G?"afb1e7373ae5e7dae3caab2c64cbb18db3d96fba Mike Gerwitz Modified bar G f227c90b116cc1d6770988a6ca359a8c92a83ce2 Mike Gerwitz Added bar G 652f9aed906a646650c1e24914c94043ae99a407 John Doe Signed off G 16ddd46b0c191b0e130d0d7d34c7fc7af03f2d3e John Doe Added feature X G cf43808e85399467885c444d2a37e609b7d9e99d Mike Gerwitz Test commit of foo G 注意每一行的后缀"G",它表明签名有效(这可以理解,因为是我们自己的签名)。再增添一个提交,我们看看进行未签名提交时会出现什么情况: $ echo foo >> foo $ git commit -am 'Yet another foo' $ git log --pretty="format:%H %aN %s %G?" HEAD^.. f72924356896ab95a542c495b796555d016cbddd Mike Gerwitz Yet another foo 注意:就像前面提到那样,在进行未签名提交时,%G?被替换为空字符串。那签了名但不可信任(即不在站点的信任内)的提交会出现什么情况? $ gpg --edit-key 8EE30EAB [...] gpg> trust [...] Please decide how far you trust this user to correctly verify other users' keys (by looking at passports, checking fingerprints from different sources, etc.) 1 = I don't know or won't say 2 = I do NOT trust 3 = I trust marginally 4 = I trust fully 5 = I trust ultimately m = back to the main menu Your decision? 2 [...] gpg> save Key not changed so no update needed. $ git log --pretty="format:%H %aN %s %G?" HEAD~2.. f72924356896ab95a542c495b796555d016cbddd Mike Gerwitz Yet another foo afb1e7373ae5e7dae3caab2c64cbb18db3d96fba Mike Gerwitz Modified bar G 哦,哦,Git似乎没有核查签名是否可信。我们看一看完整的GPG输出: $ git log --show-signature HEAD~2..HEAD^ commit afb1e7373ae5e7dae3caab2c64cbb18db3d96fba gpg: Signature made Sun 22 Apr 2012 01:37:26 PM EDT using RSA key ID 8EE30EAB gpg: Good signature from "Mike Gerwitz (Free Software Developer) <mike@mikegerwitz.com>" gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 2217 5B02 E626 BC98 D7C0 C2E5 F22B B815 8EE3 0EAB Author: Mike Gerwitz <mike@mikegerwitz.com> Date: Sat Apr 21 17:35:27 2012 -0400 Modified bar 我们可以看到GPG给出了明确的警告信息。不幸的是 pretty.c中的parse_signature_lines()引用了struct signature_check结构里的一个简单映射,并忘乎所以地忽略了警告信息,只匹配了"Good signatrue from",生成了"G"。为不信任密钥提供单独的符号,这样的补丁程序很简单,但目前我们暂时使用的是两个不同的实现方法---一个方法是对忽略是否可信任的单行输出进行解析,另一个是上面提到的对GPG输出进行解析的非简洁化实现方法。[假若采纳了这个补丁,那么这篇文档就会立即更新,使用新的符号。] |