verify-commit: scriptable commit signature verification

Commit signatures can be verified using "git show -s --show-signature" or the "%G?" pretty format and parsing the output, which is well suited for user inspection, but not for scripting. Provide a command "verify-commit" which is analogous to "verify-tag": It returns 0 for good signatures and non-zero otherwise, has the gpg output on stderr and (optionally) the commit object on stdout, sans the signature, just like "verify-tag" does. Signed-off-by: Michael J Gruber <git@drmicha.warpmail.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael J Gruber committed Jun 23, 2014 at 09:05 UTC d07b00b7f31d0cb6675b4bdba269ce7f087ddd24
6 files changed +125
Documentation/git-verify-commit.txt new
+28
@@ -0,0 +1,28 @@
1 +git-verify-commit(1)
2 +====================
3 +
4 +NAME
5 +----
6 +git-verify-commit - Check the GPG signature of commits
7 +
8 +SYNOPSIS
9 +--------
10 +[verse]
11 +'git verify-commit' <commit>...
12 +
13 +DESCRIPTION
14 +-----------
15 +Validates the gpg signature created by 'git commit -S'.
16 +
17 +OPTIONS
18 +-------
19 +-v::
20 +--verbose::
21 + Print the contents of the commit object before validating it.
22 +
23 +<commit>...::
24 + SHA-1 identifiers of Git commit objects.
25 +
26 +GIT
27 +---
28 +Part of the linkgit:git[1] suite
Makefile
+1
@@ -999,6 +999,7 @@ BUILTIN_OBJS += builtin/update-ref.o
999 BUILTIN_OBJS += builtin/update-server-info.o
1000 BUILTIN_OBJS += builtin/upload-archive.o
1001 BUILTIN_OBJS += builtin/var.o
1002 +BUILTIN_OBJS += builtin/verify-commit.o
1003 BUILTIN_OBJS += builtin/verify-pack.o
1004 BUILTIN_OBJS += builtin/verify-tag.o
1005 BUILTIN_OBJS += builtin/write-tree.o
builtin.h
+1
@@ -128,6 +128,7 @@ extern int cmd_update_server_info(int argc, const char **argv, const char *prefi
128 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
129 extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
130 extern int cmd_var(int argc, const char **argv, const char *prefix);
131 +extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
132 extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
133 extern int cmd_version(int argc, const char **argv, const char *prefix);
134 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
builtin/verify-commit.c new
+93
@@ -0,0 +1,93 @@
1 +/*
2 + * Builtin "git commit-commit"
3 + *
4 + * Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net>
5 + *
6 + * Based on git-verify-tag
7 + */
8 +#include "cache.h"
9 +#include "builtin.h"
10 +#include "commit.h"
11 +#include "run-command.h"
12 +#include <signal.h>
13 +#include "parse-options.h"
14 +#include "gpg-interface.h"
15 +
16 +static const char * const verify_commit_usage[] = {
17 + N_("git verify-commit [-v|--verbose] <commit>..."),
18 + NULL
19 +};
20 +
21 +static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
22 +{
23 + struct signature_check signature_check;
24 +
25 + memset(&signature_check, 0, sizeof(signature_check));
26 +
27 + check_commit_signature(lookup_commit(sha1), &signature_check);
28 +
29 + if (verbose && signature_check.payload)
30 + fputs(signature_check.payload, stdout);
31 +
32 + if (signature_check.gpg_output)
33 + fputs(signature_check.gpg_output, stderr);
34 +
35 + signature_check_clear(&signature_check);
36 + return signature_check.result != 'G';
37 +}
38 +
39 +static int verify_commit(const char *name, int verbose)
40 +{
41 + enum object_type type;
42 + unsigned char sha1[20];
43 + char *buf;
44 + unsigned long size;
45 + int ret;
46 +
47 + if (get_sha1(name, sha1))
48 + return error("commit '%s' not found.", name);
49 +
50 + buf = read_sha1_file(sha1, &type, &size);
51 + if (!buf)
52 + return error("%s: unable to read file.", name);
53 + if (type != OBJ_COMMIT)
54 + return error("%s: cannot verify a non-commit object of type %s.",
55 + name, typename(type));
56 +
57 + ret = run_gpg_verify(sha1, buf, size, verbose);
58 +
59 + free(buf);
60 + return ret;
61 +}
62 +
63 +static int git_verify_commit_config(const char *var, const char *value, void *cb)
64 +{
65 + int status = git_gpg_config(var, value, cb);
66 + if (status)
67 + return status;
68 + return git_default_config(var, value, cb);
69 +}
70 +
71 +int cmd_verify_commit(int argc, const char **argv, const char *prefix)
72 +{
73 + int i = 1, verbose = 0, had_error = 0;
74 + const struct option verify_commit_options[] = {
75 + OPT__VERBOSE(&verbose, N_("print commit contents")),
76 + OPT_END()
77 + };
78 +
79 + git_config(git_verify_commit_config, NULL);
80 +
81 + argc = parse_options(argc, argv, prefix, verify_commit_options,
82 + verify_commit_usage, PARSE_OPT_KEEP_ARGV0);
83 + if (argc <= i)
84 + usage_with_options(verify_commit_usage, verify_commit_options);
85 +
86 + /* sometimes the program was terminated because this signal
87 + * was received in the process of writing the gpg input: */
88 + signal(SIGPIPE, SIG_IGN);
89 + while (i < argc)
90 + if (verify_commit(argv[i++], verbose))
91 + had_error = 1;
92 + return had_error;
93 +}
command-list.txt
+1
@@ -132,6 +132,7 @@ git-update-server-info synchingrepositories
132 git-upload-archive synchelpers
133 git-upload-pack synchelpers
134 git-var plumbinginterrogators
135 +git-verify-commit ancillaryinterrogators
136 git-verify-pack plumbinginterrogators
137 git-verify-tag ancillaryinterrogators
138 gitweb ancillaryinterrogators
git.c
+1
@@ -441,6 +441,7 @@ static struct cmd_struct commands[] = {
441 { "upload-archive", cmd_upload_archive },
442 { "upload-archive--writer", cmd_upload_archive_writer },
443 { "var", cmd_var, RUN_SETUP_GENTLY },
444 + { "verify-commit", cmd_verify_commit, RUN_SETUP },
445 { "verify-pack", cmd_verify_pack },
446 { "verify-tag", cmd_verify_tag, RUN_SETUP },
447 { "version", cmd_version },