{"thread":{"id":"33266","subject":"[PATCH v2 2/4] merge/pull: verify GPG signatures of commits being merged","startedAt":"2013-03-23T01:57:43Z","lastAt":"2013-03-23T01:57:43Z","messageCount":1,"participants":["Sebastian Götte"],"isPatch":true,"patchVersion":2,"patchTotal":4},"messages":[{"id":"212012","messageId":"514D0C17.5020207@physik.tu-berlin.de","threadId":"33266","inReplyTo":null,"subject":"[PATCH v2 2/4] merge/pull: verify GPG signatures of commits being merged","fromName":"Sebastian Götte","fromEmail":"jaseg@physik.tu-berlin.de","sentAt":"2013-03-23T01:57:43Z","receivedAt":"2013-03-23T01:57:43Z","isPatch":true,"sender":{"key":"jaseg@physik.tu-berlin.de","avatar":null},"body":"When --verify-signatures is specified on the command-line of git-merge\nor git-pull, check whether the commits being merged have good gpg\nsignatures and abort the merge in case they do not. This allows e.g.\nauto-deployment from untrusted repo hosts.\n\nSigned-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>\n---\n Documentation/merge-options.txt    |  4 +++\n builtin/merge.c                    | 31 ++++++++++++++++++++++-\n git-pull.sh                        | 10 ++++++--\n t/t7612-merge-verify-signatures.sh | 52 ++++++++++++++++++++++++++++++++++++++\n 4 files changed, 94 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex 0bcbe0a..2f76ab5 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -83,6 +83,10 @@ option can be used to override --squash.\n \tPass merge strategy specific option through to the merge\n \tstrategy.\n \n+--verify-signatures::\n+--no-verify-signatures::\n+\tVerify that the commits being merged have good trusted GPG signatures\n+\n --summary::\n --no-summary::\n \tSynonyms to --stat and --no-stat; these are deprecated and will be\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7c8922c..b3788aa 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -49,7 +49,7 @@ static const char * const builtin_merge_usage[] = {\n static int show_diffstat = 1, shortlog_len = -1, squash;\n static int option_commit = 1, allow_fast_forward = 1;\n static int fast_forward_only, option_edit = -1;\n-static int allow_trivial = 1, have_message;\n+static int allow_trivial = 1, have_message, verify_signatures;\n static int overwrite_ignore = 1;\n static struct strbuf merge_msg = STRBUF_INIT;\n static struct strategy **use_strategies;\n@@ -199,6 +199,8 @@ static struct option builtin_merge_options[] = {\n \tOPT_BOOLEAN(0, \"ff-only\", &fast_forward_only,\n \t\tN_(\"abort if fast-forward is not possible\")),\n \tOPT_RERERE_AUTOUPDATE(&allow_rerere_auto),\n+\tOPT_BOOLEAN(0, \"verify-signatures\", &verify_signatures,\n+\t\tN_(\"Verify that the named commit has a valid GPG signature\")),\n \tOPT_CALLBACK('s', \"strategy\", &use_strategies, N_(\"strategy\"),\n \t\tN_(\"merge strategy to use\"), option_parse_strategy),\n \tOPT_CALLBACK('X', \"strategy-option\", &xopts, N_(\"option=value\"),\n@@ -1233,6 +1235,33 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_merge_usage,\n \t\t\tbuiltin_merge_options);\n \n+\tif (verify_signatures) {\n+\t\t/* Verify the commit signatures */\n+\t\tfor (p = remoteheads; p; p = p->next) {\n+\t\t\tstruct commit *commit = p->item;\n+\t\t\tstruct signature signature;\n+\t\t\tsignature.check_result = 0;\n+\n+\t\t\tcheck_commit_signature(commit, &signature);\n+\n+\t\t\tchar hex[41];\n+\t\t\tstrcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));\n+\t\t\tswitch(signature.check_result){\n+\t\t\t\tcase 'G':\n+\t\t\t\t\tif (verbosity >= 0)\n+\t\t\t\t\t\tprintf(_(\"Commit %s has a good GPG signature by %s\\n\"), hex, signature.signer);\n+\t\t\t\t\tbreak;\n+\t\t\t\tcase 'B':\n+\t\t\t\t\tdie(_(\"Commmit %s has a bad GPG signature allegedly by %s.\"), hex, signature.signer);\n+\t\t\t\tdefault: /* 'N' */\n+\t\t\t\t\tdie(_(\"Commmit %s does not have a good GPG signature. In fact, commit %s does not have a GPG signature at all.\"), hex, hex);\n+\t\t\t}\n+\n+\t\t\tfree(signature.gpg_output);\n+\t\t\tfree(signature.signer);\n+\t\t}\n+\t}\n+\n \tstrbuf_addstr(&buf, \"merge\");\n \tfor (p = remoteheads; p; p = p->next)\n \t\tstrbuf_addf(&buf, \" %s\", merge_remote_util(p->item)->name);\ndiff --git a/git-pull.sh b/git-pull.sh\nindex 266e682..705940d 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -39,7 +39,7 @@ test -z \"$(git ls-files -u)\" || die_conflict\n test -f \"$GIT_DIR/MERGE_HEAD\" && die_merge\n \n strategy_args= diffstat= no_commit= squash= no_ff= ff_only=\n-log_arg= verbosity= progress= recurse_submodules=\n+log_arg= verbosity= progress= recurse_submodules= verify_signatures=\n merge_args= edit=\n curr_branch=$(git symbolic-ref -q HEAD)\n curr_branch_short=\"${curr_branch#refs/heads/}\"\n@@ -125,6 +125,12 @@ do\n \t--no-recurse-submodules)\n \t\trecurse_submodules=--no-recurse-submodules\n \t\t;;\n+\t--verify-signatures)\n+\t\tverify_signatures=--verify-signatures\n+\t\t;;\n+\t--no-verify-signatures)\n+\t\tverify_signatures=--no-verify-signatures\n+\t\t;;\n \t--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)\n \t\tdry_run=--dry-run\n \t\t;;\n@@ -283,7 +289,7 @@ true)\n \teval=\"$eval --onto $merge_head ${oldremoteref:-$merge_head}\"\n \t;;\n *)\n-\teval=\"git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only\"\n+\teval=\"git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only\"\n \teval=\"$eval  $log_arg $strategy_args $merge_args $verbosity $progress\"\n \teval=\"$eval \\\"\\$merge_name\\\" HEAD $merge_head\"\n \t;;\ndiff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh\nnew file mode 100755\nindex 0000000..31b67dd\n--- /dev/null\n+++ b/t/t7612-merge-verify-signatures.sh\n@@ -0,0 +1,52 @@\n+#!/bin/sh\n+\n+test_description='merge signature verification tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+test_expect_success GPG 'create signed commits' '\n+\techo 1 >file && git add file &&\n+\ttest_tick && git commit -m initial &&\n+\tgit tag initial &&\n+\n+\tgit checkout -b side-signed &&\n+\techo 3 >elif && git add elif &&\n+\ttest_tick && git commit -S -m \"signed on side\" &&\n+\tgit checkout initial &&\n+\n+\tgit checkout -b side-unsigned &&\n+\techo 3 >foo && git add foo &&\n+\ttest_tick && git commit -m \"unsigned on side\" &&\n+\tgit checkout initial &&\n+\n+\tgit checkout -b side-bad &&\n+\techo 3 >bar && git add bar &&\n+\ttest_tick && git commit -S -m \"bad on side\" &&\n+\tgit cat-file commit side-bad >raw &&\n+\tsed -e \"s/bad/forged bad/\" raw >forged &&\n+\tgit hash-object -w -t commit forged >forged.commit &&\n+\tgit checkout initial &&\n+\n+\tgit checkout master\n+'\n+\n+test_expect_success GPG 'merge unsigned commit with verification' '\n+\ttest_must_fail git merge --ff-only --verify-signatures side-unsigned 2> mergeerror &&\n+\tgrep \"does not have a GPG signature\" mergeerror\n+'\n+\n+test_expect_success GPG 'merge commit with bad signature with verification' '\n+\ttest_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2> mergeerror &&\n+\tgrep \"has a bad GPG signature\" mergeerror\n+'\n+\n+test_expect_success GPG 'merge signed commit with verification' '\n+\tgit merge -v --ff-only --verify-signatures side-signed > mergeoutput &&\n+\tgrep \"has a good GPG signature\" mergeoutput\n+'\n+\n+test_expect_success GPG 'merge commit with bad signature without verification' '\n+\tgit merge $(cat forged.commit)\n+'\n+\n+test_done\n-- \n1.8.1.5\n"}]}