开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

GPG 签名 Git 提交

我们正在努力将ZendFramework迁移到Git。我们试图解决的一个问题是强制提交来自CLA签名者。

向我们提出的一种可能性是利用GPG签名提交消息的可能性。不幸的是,我在网上几乎找不到关于如何做到这一点的信息,所以我开始尝试一些解决方案。

我选择的方法利用了githooks,特别是commit-msg钩子客户端,以及pre-receive钩子服务器端。

客户端提交消息挂钩

commit-msg钩子接收一个参数,即包含提交消息的临时文件的路径。这允许您在完成提交之前检查它或修改它。与所有git挂钩一样,非零退出状态将中止提交。

我的commit-msg挂钩如下所示:

#!/bin/sh
echo -n "GPG Signing message... ";
PASSPHRASE=$(git config --get hooks.gpg.passphrase)
if [ "" = "$PASSPHRASE" ];then
    echo "no passphrase found! Set it with git config --add hooks.gpg.passphrase <passphrase>"
    exit 1
fi
gpg --clearsign --yes --passphrase $PASSPHRASE -o $1.asc $1
mv $1.asc $1
echo "[DONE]"

此挂钩要求您首先将GPG密钥的密码添加到您的localgit配置中,这可以按如下方式完成:

$ git config --add hooks.gpg.passphrase "mySecret"

一旦这个钩子就位,所有的提交消息都会被明确签名,导致提交日志如下所示:

commit f921f0defb18f8a5218d5c3346693dbb4179920e
Author: Matthew Weier O'Phinney <somebody@example.com>
Date:   Tue Mar 23 17:18:35 2010 -0400

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA1
    
    how now, brown cow
    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.9 (GNU/Linux)
    
    iEYEARECAAYFAkupMCsACgkQtUV5aSPtKdqERQCeN5taRATpB4/XJZiP9Vs5FVNY
    PcoAn0OZbIIcn7nC01yxp9tY7HbxVVFu
    =C/Ju
    -----END PGP SIGNATURE-----

服务器端预接收钩子

pre-receive钩子就没那么直接了。此挂钩通过STDIN接收输入。每行由三个项目组成,由一个空格分隔:

[previous commit's sha1] [new commit's sha1] [refspec]

通常,只有新的sha1对我们有用。在内部,git实际上在跟踪新的提交,即使它在技术上还没有被存储库接受。这允许我们使用诸如gitshow之类的工具来获取有关提交的信息并根据该信息采取行动。

我需要做的是检查GPG签名消息的提交消息;如果没有找到,则直接拒绝提交,但如果存在,则根据我的密钥环对其进行验证,如果签名消息无效则中止。

我最初使用gitshow--pretty="format:%b"[sha1]但是,我发现git做了一些……奇怪的事情……来提交消息。前50个左右的字符被认为是提交的“主题”——在主题中发现的任何换行符都被默默地删除。这意味着,出于我的目的,我得到了一条永远无法验证的截断消息(因为GPG签名头被剥离);即使在格式中包含主题也不起作用,因为其中的换行符丢失了。我发现获取完整提交消息的唯一方法是使用gitshow--pretty=raw[sha1]。然而,这也为我提供了提交标头和差异—这意味着我必须解析响应。

接下来是我做的一个PHP实现,它就是这样做的:抓取完整的消息并将其重定向到一个临时文件,解析该文件以获取提交消息,然后对其执行操作。

#!/usr/bin/php
<?php
echo "Checking for GPG signature... ";
$fh     = fopen('php://stdin', 'r');
$tmpdir = sys_get_temp_dir();
while (!feof($fh)) {
    $line = fgets($fh);
    list($old, $new, $ref) = explode(' ', $line);

    // Create a tmp file with the commit log
    $logTmp   = tempnam($tmpdir, 'LOG_');
    $body     = shell_exec('git show --pretty=raw ' . $new . ' > ' . $logTmp);

    $msgTmp   = tempnam($tmpdir, 'MESSAGE_');

    // Scan the commit log for a commit message
    $log = fopen($logTmp, 'r');
    $msg = fopen($msgTmp, 'a');
    $signatureDetected = false;
    while (!feof($log)) {
        $line = fgets($log);
        if (preg_match('/^(commit(ter)?|tree|parent|author)\s/', $line)) {
            // Skip the commit log headers
            continue;
        }
        if (preg_match('/^diff\s/', $line)) {
            // Stop scanning when we reach the diff
            break;
        }
        if (preg_match('/^\s+-+BEGIN [A-Z]+ SIGNED MESSAGE/', $line)) {
            // We have a signed message, so start appending it 
            // to a separate tmp file
            $signatureDetected = true;
            $line = preg_replace('/^\s+/', '', $line);
            fwrite($msg, $line);
            continue;
        }
        if ($signatureDetected) {
            // If we have detected a signed message, continue appending lines to
            // it. Commit message lines are indented, so strip indentation.
            $line = preg_replace('/^\s+/', '', $line);
            if ('' === $line) {
                $line = "\n";"
            }
            fwrite($msg, $line);
        }
    }
    fclose($log);
    fclose($msg);

    if (!signatureDetected) {
        // No signed message detected; report and abort
        unlink($logTmp);
        unlink($msgTmp);
        echo "no GPG signature detected; commit aborted\n";
        exit(1);
    }

    $verification = shell_exec('gpg --verify ' . $msgTmp . ' 2>&1');
    if (!preg_match('/Good signature/s', $verification)) {
        // Failed to verify signed message; report and abort
        unlink($logTmp);
        unlink($msgTmp);
        echo "invalid GPG signature; commit aborted\n";
        exit(1);
    }

    unlink($logTmp);
    unlink($msgTmp);
}
echo "verified!\n";
exit(0);

可能有更优雅的方法来实现这一点,包括其他语言的解决方案。但是,它工作得很好。

结论

Git钩子非常强大,深入研究它们让我有信心在我们准备好向公众开放时,我可以为ZFgit存储库创建一些不错的自动化。

也就是说,我不知道我们是否真的会使用这样的提交签名,因为它有一些缺点:

  • 提交签名并不是真正的跨平台。这可能是可以补救的,但需要使用不同操作系统和使用不同工具(例如EGit、TortoiseGit等)的人为客户端开发和提供签名机制。
  • 它为那些引入了复杂性开发补丁。如果开发人员在没有安装commit-msg挂钩的情况下开始,那么他们必须创建一个新分支并随后进行压缩提交,以确保最终补丁可以进入规范存储库。
  • 上述两个原因有点违背了转向分布式VCS的初衷——即简化开发并使其更加民主。

不管我们是否决定使用这种技术,在研究这个问题时,我看到很多人发帖希望实施提交签名,但不确定如何实现。也许这篇文章将成为许多人的起点。

未经允许不得转载:我爱分享网 » GPG 签名 Git 提交

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏