Merge branch 'mg/verify-commit'
authorJunio C Hamano <gitster@pobox.com>
Thu, 10 Jul 2014 18:27:33 +0000 (11:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 10 Jul 2014 18:27:34 +0000 (11:27 -0700)
Add 'verify-commit' to be used in a way similar to 'verify-tag' is
used. Further work on verifying the mergetags might be needed.

* mg/verify-commit:
t7510: test verify-commit
t7510: exit for loop with test result
verify-commit: scriptable commit signature verification
gpg-interface: provide access to the payload
gpg-interface: provide clear helper for struct signature_check

12 files changed:
Documentation/git-verify-commit.txt [new file with mode: 0644]
Makefile
builtin.h
builtin/merge.c
builtin/verify-commit.c [new file with mode: 0644]
command-list.txt
commit.c
git.c
gpg-interface.c
gpg-interface.h
pretty.c
t/t7510-signed-commit.sh
diff --git a/Documentation/git-verify-commit.txt b/Documentation/git-verify-commit.txt
new file mode 100644 (file)
index 0000000..9413e28
--- /dev/null
@@ -0,0 +1,28 @@
+git-verify-commit(1)
+====================
+
+NAME
+----
+git-verify-commit - Check the GPG signature of commits
+
+SYNOPSIS
+--------
+[verse]
+'git verify-commit' <commit>...
+
+DESCRIPTION
+-----------
+Validates the gpg signature created by 'git commit -S'.
+
+OPTIONS
+-------
+-v::
+--verbose::
+       Print the contents of the commit object before validating it.
+
+<commit>...::
+       SHA-1 identifiers of Git commit objects.
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 07ea1058379ab963648d0df7fd2917e0d2efa8a7..b92418de7bc743a70f6113cb2af39c74a3cb312c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -999,6 +999,7 @@ BUILTIN_OBJS += builtin/update-ref.o
 BUILTIN_OBJS += builtin/update-server-info.o
 BUILTIN_OBJS += builtin/upload-archive.o
 BUILTIN_OBJS += builtin/var.o
+BUILTIN_OBJS += builtin/verify-commit.o
 BUILTIN_OBJS += builtin/verify-pack.o
 BUILTIN_OBJS += builtin/verify-tag.o
 BUILTIN_OBJS += builtin/write-tree.o
index c47c110e0f18f3183d54c9afb63cffef686a21db..5d91f31ca25e0c16fca0e8c23d83706840bf6c57 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -128,6 +128,7 @@ extern int cmd_update_server_info(int argc, const char **argv, const char *prefi
 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
 extern int cmd_var(int argc, const char **argv, const char *prefix);
+extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
index b49c310866f76434c3838484c0bf997a1230c7ed..86e9c61277990e2999d678af95824d5aec0b3df0 100644 (file)
@@ -1282,10 +1282,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                                printf(_("Commit %s has a good GPG signature by %s\n"),
                                       hex, signature_check.signer);
 
-                       free(signature_check.gpg_output);
-                       free(signature_check.gpg_status);
-                       free(signature_check.signer);
-                       free(signature_check.key);
+                       signature_check_clear(&signature_check);
                }
        }
 
diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c
new file mode 100644 (file)
index 0000000..b0f8504
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Builtin "git commit-commit"
+ *
+ * Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net>
+ *
+ * Based on git-verify-tag
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "run-command.h"
+#include <signal.h>
+#include "parse-options.h"
+#include "gpg-interface.h"
+
+static const char * const verify_commit_usage[] = {
+               N_("git verify-commit [-v|--verbose] <commit>..."),
+               NULL
+};
+
+static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
+{
+       struct signature_check signature_check;
+
+       memset(&signature_check, 0, sizeof(signature_check));
+
+       check_commit_signature(lookup_commit(sha1), &signature_check);
+
+       if (verbose && signature_check.payload)
+               fputs(signature_check.payload, stdout);
+
+       if (signature_check.gpg_output)
+               fputs(signature_check.gpg_output, stderr);
+
+       signature_check_clear(&signature_check);
+       return signature_check.result != 'G';
+}
+
+static int verify_commit(const char *name, int verbose)
+{
+       enum object_type type;
+       unsigned char sha1[20];
+       char *buf;
+       unsigned long size;
+       int ret;
+
+       if (get_sha1(name, sha1))
+               return error("commit '%s' not found.", name);
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("%s: unable to read file.", name);
+       if (type != OBJ_COMMIT)
+               return error("%s: cannot verify a non-commit object of type %s.",
+                               name, typename(type));
+
+       ret = run_gpg_verify(sha1, buf, size, verbose);
+
+       free(buf);
+       return ret;
+}
+
+static int git_verify_commit_config(const char *var, const char *value, void *cb)
+{
+       int status = git_gpg_config(var, value, cb);
+       if (status)
+               return status;
+       return git_default_config(var, value, cb);
+}
+
+int cmd_verify_commit(int argc, const char **argv, const char *prefix)
+{
+       int i = 1, verbose = 0, had_error = 0;
+       const struct option verify_commit_options[] = {
+               OPT__VERBOSE(&verbose, N_("print commit contents")),
+               OPT_END()
+       };
+
+       git_config(git_verify_commit_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, verify_commit_options,
+                            verify_commit_usage, PARSE_OPT_KEEP_ARGV0);
+       if (argc <= i)
+               usage_with_options(verify_commit_usage, verify_commit_options);
+
+       /* sometimes the program was terminated because this signal
+        * was received in the process of writing the gpg input: */
+       signal(SIGPIPE, SIG_IGN);
+       while (i < argc)
+               if (verify_commit(argv[i++], verbose))
+                       had_error = 1;
+       return had_error;
+}
index cf36c3d71e3b8fc3382162ef939f3ed1f7d20775..a3ff0c9e60148e6dc98a6bd0d71d98cba4a7eae2 100644 (file)
@@ -132,6 +132,7 @@ git-update-server-info                  synchingrepositories
 git-upload-archive                      synchelpers
 git-upload-pack                         synchelpers
 git-var                                 plumbinginterrogators
+git-verify-commit                       ancillaryinterrogators
 git-verify-pack                         plumbinginterrogators
 git-verify-tag                          ancillaryinterrogators
 gitweb                                  ancillaryinterrogators
index fb7897c2a4cef3298c014cbac86081b1d6adc837..acb74b55d4ee72ddf626754535a9febeb05ba945 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1270,6 +1270,7 @@ void check_commit_signature(const struct commit* commit, struct signature_check
                                      &gpg_output, &gpg_status);
        if (status && !gpg_output.len)
                goto out;
+       sigc->payload = strbuf_detach(&payload, NULL);
        sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
        sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
        parse_gpg_output(sigc);
diff --git a/git.c b/git.c
index dd54f5734a246d65436c823dd65fb224b240009f..5b6c7611be93c535491bbc9ba027e3acbad0db8f 100644 (file)
--- a/git.c
+++ b/git.c
@@ -478,6 +478,7 @@ static struct cmd_struct commands[] = {
        { "upload-archive", cmd_upload_archive },
        { "upload-archive--writer", cmd_upload_archive_writer },
        { "var", cmd_var, RUN_SETUP_GENTLY },
+       { "verify-commit", cmd_verify_commit, RUN_SETUP },
        { "verify-pack", cmd_verify_pack },
        { "verify-tag", cmd_verify_tag, RUN_SETUP },
        { "version", cmd_version },
index 8b0e87436b687ce26e3e4987129a2fc171069280..ff07012726ea28daa2551966d0555d2e8efa2375 100644 (file)
@@ -7,6 +7,20 @@
 static char *configured_signing_key;
 static const char *gpg_program = "gpg";
 
+void signature_check_clear(struct signature_check *sigc)
+{
+       free(sigc->payload);
+       free(sigc->gpg_output);
+       free(sigc->gpg_status);
+       free(sigc->signer);
+       free(sigc->key);
+       sigc->payload = NULL;
+       sigc->gpg_output = NULL;
+       sigc->gpg_status = NULL;
+       sigc->signer = NULL;
+       sigc->key = NULL;
+}
+
 void set_signing_key(const char *key)
 {
        free(configured_signing_key);
index a85cb5bc97cdd61000b4c48c54faa656aa3cfaca..37c23daff010b0de18fa12ff6a6167f45ff41ffc 100644 (file)
@@ -2,6 +2,7 @@
 #define GPG_INTERFACE_H
 
 struct signature_check {
+       char *payload;
        char *gpg_output;
        char *gpg_status;
        char result; /* 0 (not checked),
@@ -13,6 +14,7 @@ struct signature_check {
        char *key;
 };
 
+extern void signature_check_clear(struct signature_check *sigc);
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
index 8d201f6bda1bb9ccf97be4fa25660adbebcfe74f..14357e233f3174963add310a29bb35c3de229121 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1520,8 +1520,6 @@ void format_commit_message(const struct commit *commit,
 
        free(context.commit_encoding);
        unuse_commit_buffer(commit, context.message);
-       free(context.signature_check.gpg_output);
-       free(context.signature_check.signer);
 }
 
 static void pp_header(struct pretty_print_context *pp,
index e97477a3b98a49c80ce01f06b966eacff52a3bd1..474dab381aef027207026cb938df7a09cc7a9056 100755 (executable)
@@ -48,10 +48,11 @@ test_expect_success GPG 'create signed commits' '
        git tag eighth-signed-alt
 '
 
-test_expect_success GPG 'show signatures' '
+test_expect_success GPG 'verify and show signatures' '
        (
                for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
                do
+                       git verify-commit $commit &&
                        git show --pretty=short --show-signature $commit >actual &&
                        grep "Good signature from" actual &&
                        ! grep "BAD signature from" actual &&
@@ -61,6 +62,7 @@ test_expect_success GPG 'show signatures' '
        (
                for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
                do
+                       test_must_fail git verify-commit $commit &&
                        git show --pretty=short --show-signature $commit >actual &&
                        ! grep "Good signature from" actual &&
                        ! grep "BAD signature from" actual &&
@@ -79,11 +81,25 @@ test_expect_success GPG 'show signatures' '
        )
 '
 
+test_expect_success GPG 'show signed commit with signature' '
+       git show -s initial >commit &&
+       git show -s --show-signature initial >show &&
+       git verify-commit -v initial >verify.1 2>verify.2 &&
+       git cat-file commit initial >cat &&
+       grep -v "gpg: " show >show.commit &&
+       grep "gpg: " show >show.gpg &&
+       grep -v "^ " cat | grep -v "^gpgsig " >cat.commit &&
+       test_cmp show.commit commit &&
+       test_cmp show.gpg verify.2 &&
+       test_cmp cat.commit verify.1
+'
+
 test_expect_success GPG 'detect fudged signature' '
        git cat-file commit seventh-signed >raw &&
 
        sed -e "s/seventh/7th forged/" raw >forged1 &&
        git hash-object -w -t commit forged1 >forged1.commit &&
+       ! git verify-commit $(cat forged1.commit) &&
        git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
        grep "BAD signature from" actual1 &&
        ! grep "Good signature from" actual1
@@ -94,6 +110,7 @@ test_expect_success GPG 'detect fudged signature with NUL' '
        cat raw >forged2 &&
        echo Qwik | tr "Q" "\000" >>forged2 &&
        git hash-object -w -t commit forged2 >forged2.commit &&
+       ! git verify-commit $(cat forged2.commit) &&
        git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
        grep "BAD signature from" actual2 &&
        ! grep "Good signature from" actual2
@@ -102,6 +119,7 @@ test_expect_success GPG 'detect fudged signature with NUL' '
 test_expect_success GPG 'amending already signed commit' '
        git checkout fourth-signed^0 &&
        git commit --amend -S --no-edit &&
+       git verify-commit HEAD &&
        git show -s --show-signature HEAD >actual &&
        grep "Good signature from" actual &&
        ! grep "BAD signature from" actual