From 01e57b5d91b0c9f2ac93708c5c2cbcd4731ddd34 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Mon, 23 Jun 2014 09:05:47 +0200 Subject: gpg-interface: provide clear helper for struct signature_check The struct has been growing members whose malloced memory needs to be freed. Do this with one helper function so that no malloced memory shall be left unfreed. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano diff --git a/builtin/merge.c b/builtin/merge.c index 428ca24..e50323d 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -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/gpg-interface.c b/gpg-interface.c index 8b0e874..e71b59d 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -7,6 +7,18 @@ static char *configured_signing_key; static const char *gpg_program = "gpg"; +void signature_check_clear(struct signature_check *sigc) +{ + free(sigc->gpg_output); + free(sigc->gpg_status); + free(sigc->signer); + free(sigc->key); + 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); diff --git a/gpg-interface.h b/gpg-interface.h index a85cb5b..9f0784a 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -13,6 +13,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 *); diff --git a/pretty.c b/pretty.c index e1e2cad..f6a0023 100644 --- a/pretty.c +++ b/pretty.c @@ -1532,8 +1532,7 @@ void format_commit_message(const struct commit *commit, free(context.commit_encoding); logmsg_free(context.message, commit); - free(context.signature_check.gpg_output); - free(context.signature_check.signer); + signature_check_clear(&context.signature_check); } static void pp_header(struct pretty_print_context *pp, -- cgit v0.10.2-6-g49f6 From 71c214c840782a67801fc8dbf5fe8a4f4fc62d01 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Mon, 23 Jun 2014 09:05:48 +0200 Subject: gpg-interface: provide access to the payload In contrast to tag signatures, commit signatures are put into the header, that is between the other header parts and commit messages. Provide access to the commit content sans the signature, which is the payload that is actually signed. Commit signature verification does the parsing anyways, and callers may wish to act on or display the commit object sans the signature. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano diff --git a/commit.c b/commit.c index f479331..e9686b2 100644 --- a/commit.c +++ b/commit.c @@ -1219,6 +1219,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/gpg-interface.c b/gpg-interface.c index e71b59d..ff07012 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -9,10 +9,12 @@ 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; diff --git a/gpg-interface.h b/gpg-interface.h index 9f0784a..37c23da 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -2,6 +2,7 @@ #define GPG_INTERFACE_H struct signature_check { + char *payload; char *gpg_output; char *gpg_status; char result; /* 0 (not checked), -- cgit v0.10.2-6-g49f6 From d07b00b7f31d0cb6675b4bdba269ce7f087ddd24 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Mon, 23 Jun 2014 09:05:49 +0200 Subject: 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 Signed-off-by: Junio C Hamano diff --git a/Documentation/git-verify-commit.txt b/Documentation/git-verify-commit.txt new file mode 100644 index 0000000..9413e28 --- /dev/null +++ b/Documentation/git-verify-commit.txt @@ -0,0 +1,28 @@ +git-verify-commit(1) +==================== + +NAME +---- +git-verify-commit - Check the GPG signature of commits + +SYNOPSIS +-------- +[verse] +'git verify-commit' ... + +DESCRIPTION +----------- +Validates the gpg signature created by 'git commit -S'. + +OPTIONS +------- +-v:: +--verbose:: + Print the contents of the commit object before validating it. + +...:: + SHA-1 identifiers of Git commit objects. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index 07ea105..b92418d 100644 --- 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 diff --git a/builtin.h b/builtin.h index c47c110..5d91f31 100644 --- 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); diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c new file mode 100644 index 0000000..b0f85042 --- /dev/null +++ b/builtin/verify-commit.c @@ -0,0 +1,93 @@ +/* + * Builtin "git commit-commit" + * + * Copyright (c) 2014 Michael J Gruber + * + * Based on git-verify-tag + */ +#include "cache.h" +#include "builtin.h" +#include "commit.h" +#include "run-command.h" +#include +#include "parse-options.h" +#include "gpg-interface.h" + +static const char * const verify_commit_usage[] = { + N_("git verify-commit [-v|--verbose] ..."), + 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; +} diff --git a/command-list.txt b/command-list.txt index cf36c3d..a3ff0c9 100644 --- a/command-list.txt +++ b/command-list.txt @@ -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 diff --git a/git.c b/git.c index 7780572..bbc3293 100644 --- a/git.c +++ b/git.c @@ -441,6 +441,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 }, -- cgit v0.10.2-6-g49f6 From 0f109c92b07eb13ffc675a7e98e9cfc3fee42994 Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Mon, 23 Jun 2014 09:05:50 +0200 Subject: t7510: exit for loop with test result t7510 uses for loops in a subshell, which need to make sure that the test returns with the appropriate error code from within the loop. Restructure the loops as the usual && chains with a single point of "exit 1" at the end of the loop to make this clearer. Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 5ddac1a..96cfddf 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -50,7 +50,7 @@ test_expect_success GPG 'show signatures' ' for commit in initial second merge fourth-signed fifth-signed sixth-signed master do git show --pretty=short --show-signature $commit >actual && - grep "Good signature from" actual || exit 1 + grep "Good signature from" actual && ! grep "BAD signature from" actual || exit 1 echo $commit OK done @@ -59,7 +59,7 @@ test_expect_success GPG 'show signatures' ' for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned do git show --pretty=short --show-signature $commit >actual && - grep "Good signature from" actual && exit 1 + ! grep "Good signature from" actual && ! grep "BAD signature from" actual || exit 1 echo $commit OK done -- cgit v0.10.2-6-g49f6 From 8e92c2cf37038a3b3bb82724f018e4d0ab1180ff Mon Sep 17 00:00:00 2001 From: Michael J Gruber Date: Mon, 23 Jun 2014 09:05:51 +0200 Subject: t7510: test verify-commit This mixes the "git verify-commit" tests in with the "git show --show-signature" tests, to keep the tests more readable. The tests already mix in the "call show" tests with the "verify" tests. So in case of a test beakage, a '-v' run would be needed to reveal the exact point of breakage anyway. Additionally, test the actual output of "git verify-commit" and "git show --show-signature" and compare to "git cat-file". Signed-off-by: Michael J Gruber Signed-off-by: Junio C Hamano diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 96cfddf..dd4b948 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -45,10 +45,11 @@ test_expect_success GPG 'create signed commits' ' git tag seventh-signed ' -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 master do + git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && grep "Good signature from" actual && ! grep "BAD signature from" actual || exit 1 @@ -58,6 +59,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 || exit 1 @@ -66,11 +68,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 master >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 @@ -81,6 +97,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 @@ -89,6 +106,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 -- cgit v0.10.2-6-g49f6