Merge branch 'bc/gpg-verify-raw'
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:12 +0000 (11:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:12 +0000 (11:01 -0700)
"git verify-tag" and "git verify-commit" have been taught to share
more code, and then learned to optionally show the verification
message from the underlying GPG implementation.

* bc/gpg-verify-raw:
verify-tag: add option to print raw gpg status information
verify-commit: add option to print raw gpg status information
gpg: centralize printing signature buffers
gpg: centralize signature check
verify-commit: add test for exit status on untrusted signature
verify-tag: share code with verify-commit
verify-tag: add tests

Documentation/git-verify-commit.txt
Documentation/git-verify-tag.txt
builtin/verify-commit.c
builtin/verify-tag.c
commit.c
commit.h
gpg-interface.c
gpg-interface.h
t/t7030-verify-tag.sh [new file with mode: 0755]
t/t7510-signed-commit.sh
index 9413e2802a718117bd0b7c9997d55619ea18ba59..ecf4da16cf0a270cda7916777524ac129b4bead1 100644 (file)
@@ -16,6 +16,10 @@ Validates the gpg signature created by 'git commit -S'.
 
 OPTIONS
 -------
+--raw::
+       Print the raw gpg status output to standard error instead of the normal
+       human-readable output.
+
 -v::
 --verbose::
        Print the contents of the commit object before validating it.
index f88ba96f023ac9f497f01ef699d0931a6d14e59a..d590edcebd99a3c9321cfdc00499aa6996b78a5b 100644 (file)
@@ -16,6 +16,10 @@ Validates the gpg signature created by 'git tag'.
 
 OPTIONS
 -------
+--raw::
+       Print the raw gpg status output to standard error instead of the normal
+       human-readable output.
+
 -v::
 --verbose::
        Print the contents of the tag object before validating it.
index ec0c4e3d836f9242a8e34dd7fffa5ddfb835907b..38bedf8f9fe6a718af704d8be16b7787881f8c11 100644 (file)
@@ -18,25 +18,21 @@ static const char * const verify_commit_usage[] = {
                NULL
 };
 
-static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
+static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags)
 {
        struct signature_check signature_check;
+       int ret;
 
        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);
+       ret = check_commit_signature(lookup_commit(sha1), &signature_check);
+       print_signature_buffer(&signature_check, flags);
 
        signature_check_clear(&signature_check);
-       return signature_check.result != 'G';
+       return ret;
 }
 
-static int verify_commit(const char *name, int verbose)
+static int verify_commit(const char *name, unsigned flags)
 {
        enum object_type type;
        unsigned char sha1[20];
@@ -54,7 +50,7 @@ static int verify_commit(const char *name, int verbose)
                return error("%s: cannot verify a non-commit object of type %s.",
                                name, typename(type));
 
-       ret = run_gpg_verify(sha1, buf, size, verbose);
+       ret = run_gpg_verify(sha1, buf, size, flags);
 
        free(buf);
        return ret;
@@ -71,8 +67,10 @@ static int git_verify_commit_config(const char *var, const char *value, void *cb
 int cmd_verify_commit(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
+       unsigned flags = 0;
        const struct option verify_commit_options[] = {
                OPT__VERBOSE(&verbose, N_("print commit contents")),
+               OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
                OPT_END()
        };
 
@@ -83,11 +81,14 @@ int cmd_verify_commit(int argc, const char **argv, const char *prefix)
        if (argc <= i)
                usage_with_options(verify_commit_usage, verify_commit_options);
 
+       if (verbose)
+               flags |= GPG_VERIFY_VERBOSE;
+
        /* 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))
+               if (verify_commit(argv[i++], flags))
                        had_error = 1;
        return had_error;
 }
index 53c68fce3ac182db907224d12d89659469f4b9d6..00663f6a3003976aaa33f08ae7309fc190ea4748 100644 (file)
@@ -18,21 +18,30 @@ static const char * const verify_tag_usage[] = {
                NULL
 };
 
-static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
+static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags)
 {
+       struct signature_check sigc;
        int len;
+       int ret;
+
+       memset(&sigc, 0, sizeof(sigc));
 
        len = parse_signature(buf, size);
-       if (verbose)
-               write_in_full(1, buf, len);
 
-       if (size == len)
+       if (size == len) {
+               if (flags & GPG_VERIFY_VERBOSE)
+                       write_in_full(1, buf, len);
                return error("no signature found");
+       }
+
+       ret = check_signature(buf, len, buf + len, size - len, &sigc);
+       print_signature_buffer(&sigc, flags);
 
-       return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL);
+       signature_check_clear(&sigc);
+       return ret;
 }
 
-static int verify_tag(const char *name, int verbose)
+static int verify_tag(const char *name, unsigned flags)
 {
        enum object_type type;
        unsigned char sha1[20];
@@ -52,7 +61,7 @@ static int verify_tag(const char *name, int verbose)
        if (!buf)
                return error("%s: unable to read file.", name);
 
-       ret = run_gpg_verify(buf, size, verbose);
+       ret = run_gpg_verify(buf, size, flags);
 
        free(buf);
        return ret;
@@ -69,8 +78,10 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb)
 int cmd_verify_tag(int argc, const char **argv, const char *prefix)
 {
        int i = 1, verbose = 0, had_error = 0;
+       unsigned flags = 0;
        const struct option verify_tag_options[] = {
                OPT__VERBOSE(&verbose, N_("print tag contents")),
+               OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
                OPT_END()
        };
 
@@ -81,11 +92,14 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
        if (argc <= i)
                usage_with_options(verify_tag_usage, verify_tag_options);
 
+       if (verbose)
+               flags |= GPG_VERIFY_VERBOSE;
+
        /* 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_tag(argv[i++], verbose))
+               if (verify_tag(argv[i++], flags))
                        had_error = 1;
        return had_error;
 }
index 6e2103cef60e8a0c2df18685280dbb5fda5c133f..62223a33e5d4d05a44efca48d5ea9fbfa2e957a3 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1232,33 +1232,24 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
        free(buf);
 }
 
-void check_commit_signature(const struct commit *commit, struct signature_check *sigc)
+int check_commit_signature(const struct commit *commit, struct signature_check *sigc)
 {
        struct strbuf payload = STRBUF_INIT;
        struct strbuf signature = STRBUF_INIT;
-       struct strbuf gpg_output = STRBUF_INIT;
-       struct strbuf gpg_status = STRBUF_INIT;
-       int status;
+       int ret = 1;
 
        sigc->result = 'N';
 
        if (parse_signed_commit(commit, &payload, &signature) <= 0)
                goto out;
-       status = verify_signed_buffer(payload.buf, payload.len,
-                                     signature.buf, signature.len,
-                                     &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);
+       ret = check_signature(payload.buf, payload.len, signature.buf,
+               signature.len, sigc);
 
  out:
-       strbuf_release(&gpg_status);
-       strbuf_release(&gpg_output);
        strbuf_release(&payload);
        strbuf_release(&signature);
+
+       return ret;
 }
 
 
index 9a1fa961d2ba0e3ec3eae9096ef49bc7155cdd92..4983bdd3d3e3236f3df487e8e37e85756da1e360 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -379,7 +379,7 @@ extern void print_commit_list(struct commit_list *list,
  * at all.  This may allocate memory for sig->gpg_output, sig->gpg_status,
  * sig->signer and sig->key.
  */
-extern void check_commit_signature(const struct commit *commit, struct signature_check *sigc);
+extern int check_commit_signature(const struct commit *commit, struct signature_check *sigc);
 
 int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
 
index 68b0c814f789f39151d380b1d318d6f80090d524..3dc2fe397e32d79713780596f0ef4666c14b5955 100644 (file)
@@ -60,6 +60,43 @@ void parse_gpg_output(struct signature_check *sigc)
        }
 }
 
+int check_signature(const char *payload, size_t plen, const char *signature,
+       size_t slen, struct signature_check *sigc)
+{
+       struct strbuf gpg_output = STRBUF_INIT;
+       struct strbuf gpg_status = STRBUF_INIT;
+       int status;
+
+       sigc->result = 'N';
+
+       status = verify_signed_buffer(payload, plen, signature, slen,
+                                     &gpg_output, &gpg_status);
+       if (status && !gpg_output.len)
+               goto out;
+       sigc->payload = xmemdupz(payload, plen);
+       sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
+       sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
+       parse_gpg_output(sigc);
+
+ out:
+       strbuf_release(&gpg_status);
+       strbuf_release(&gpg_output);
+
+       return sigc->result != 'G' && sigc->result != 'U';
+}
+
+void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
+{
+       const char *output = flags & GPG_VERIFY_RAW ?
+               sigc->gpg_status : sigc->gpg_output;
+
+       if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
+               fputs(sigc->payload, stdout);
+
+       if (output)
+               fputs(output, stderr);
+}
+
 /*
  * Look at GPG signed content (e.g. a signed tag object), whose
  * payload is followed by a detached signature on it.  Return the
index 87a4f2e3fad92b4622a40ef5612db293bf4c5dd4..ea68885ad5b73aa63acc936eee52a61252420892 100644 (file)
@@ -1,6 +1,9 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+#define GPG_VERIFY_VERBOSE     1
+#define GPG_VERIFY_RAW         2
+
 struct signature_check {
        char *payload;
        char *gpg_output;
@@ -27,5 +30,8 @@ extern int verify_signed_buffer(const char *payload, size_t payload_size, const
 extern int git_gpg_config(const char *, const char *, void *);
 extern void set_signing_key(const char *);
 extern const char *get_signing_key(void);
+extern int check_signature(const char *payload, size_t plen,
+       const char *signature, size_t slen, struct signature_check *sigc);
+void print_signature_buffer(const struct signature_check *sigc, unsigned flags);
 
 #endif
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
new file mode 100755 (executable)
index 0000000..4608e71
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+test_description='signed tag tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed tags' '
+       echo 1 >file && git add file &&
+       test_tick && git commit -m initial &&
+       git tag -s -m initial initial &&
+       git branch side &&
+
+       echo 2 >file && test_tick && git commit -a -m second &&
+       git tag -s -m second second &&
+
+       git checkout side &&
+       echo 3 >elif && git add elif &&
+       test_tick && git commit -m "third on side" &&
+
+       git checkout master &&
+       test_tick && git merge -S side &&
+       git tag -s -m merge merge &&
+
+       echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" &&
+       git tag -a -m fourth-unsigned fourth-unsigned &&
+
+       test_tick && git commit --amend -S -m "fourth signed" &&
+       git tag -s -m fourth fourth-signed &&
+
+       echo 5 >file && test_tick && git commit -a -m "fifth" &&
+       git tag fifth-unsigned &&
+
+       git config commit.gpgsign true &&
+       echo 6 >file && test_tick && git commit -a -m "sixth" &&
+       git tag -a -m sixth sixth-unsigned &&
+
+       test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ &&
+       git tag -m seventh -s seventh-signed &&
+
+       echo 8 >file && test_tick && git commit -a -m eighth &&
+       git tag -uB7227189 -m eighth eighth-signed-alt
+'
+
+test_expect_success GPG 'verify and show signatures' '
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag $tag 2>actual &&
+                       grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag $tag 2>actual &&
+                       ! grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       git verify-tag $tag 2>actual &&
+                       grep "Good signature from" actual &&
+                       ! grep "BAD signature from" actual &&
+                       grep "not certified" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_expect_success GPG 'detect fudged signature' '
+       git cat-file tag seventh-signed >raw &&
+       sed -e "s/seventh/7th forged/" raw >forged1 &&
+       git hash-object -w -t tag forged1 >forged1.tag &&
+       test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
+       grep "BAD signature from" actual1 &&
+       ! grep "Good signature from" actual1
+'
+
+test_expect_success GPG 'verify signatures with --raw' '
+       (
+               for tag in initial second merge fourth-signed sixth-signed seventh-signed
+               do
+                       git verify-tag --raw $tag 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+               do
+                       test_must_fail git verify-tag --raw $tag 2>actual &&
+                       ! grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $tag OK || exit 1
+               done
+       ) &&
+       (
+               for tag in eighth-signed-alt
+               do
+                       git verify-tag --raw $tag 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       grep "TRUST_UNDEFINED" actual &&
+                       echo $tag OK || exit 1
+               done
+       )
+'
+
+test_done
index 13331e533bf520b6f268df16f8a143b8661a1c72..18e5cf06630273a6131d13d9a82f085d33f15153 100755 (executable)
@@ -81,6 +81,44 @@ test_expect_success GPG 'verify and show signatures' '
        )
 '
 
+test_expect_success GPG 'verify-commit exits success on untrusted signature' '
+       git verify-commit eighth-signed-alt 2>actual &&
+       grep "Good signature from" actual &&
+       ! grep "BAD signature from" actual &&
+       grep "not certified" actual
+'
+
+test_expect_success GPG 'verify signatures with --raw' '
+       (
+               for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
+               do
+                       git verify-commit --raw $commit 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
+               do
+                       test_must_fail git verify-commit --raw $commit 2>actual &&
+                       ! grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       echo $commit OK || exit 1
+               done
+       ) &&
+       (
+               for commit in eighth-signed-alt
+               do
+                       git verify-commit --raw $commit 2>actual &&
+                       grep "GOODSIG" actual &&
+                       ! grep "BADSIG" actual &&
+                       grep "TRUST_UNDEFINED" actual &&
+                       echo $commit OK || exit 1
+               done
+       )
+'
+
 test_expect_success GPG 'show signed commit with signature' '
        git show -s initial >commit &&
        git show -s --show-signature initial >show &&