[PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged
From: Sebastian Götte <hidden>
Date: 2016-06-15 22:56:31
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
When --verify-signatures is specified on the command-line of git-merge or git-pull, check whether the commits being merged have good gpg signatures and abort the merge in case they do not. This allows e.g. auto-deployment from untrusted repo hosts. Signed-off-by: Sebastian Götte <redacted> --- Documentation/merge-options.txt | 4 +++ builtin/merge.c | 33 +++++++++++++++++++++++- git-pull.sh | 10 ++++++-- t/t7612-merge-verify-signatures.sh | 52 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 0bcbe0a..2f76ab5 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt@@ -83,6 +83,10 @@ option can be used to override --squash. Pass merge strategy specific option through to the merge strategy. +--verify-signatures:: +--no-verify-signatures:: + Verify that the commits being merged have good trusted GPG signatures + --summary:: --no-summary:: Synonyms to --stat and --no-stat; these are deprecated and will be
diff --git a/builtin/merge.c b/builtin/merge.c
index 7c8922c..227040b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c@@ -49,7 +49,7 @@ static const char * const builtin_merge_usage[] = { static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only, option_edit = -1; -static int allow_trivial = 1, have_message; +static int allow_trivial = 1, have_message, verify_signatures; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies;
@@ -199,6 +199,8 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "ff-only", &fast_forward_only, N_("abort if fast-forward is not possible")), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), + OPT_BOOLEAN(0, "verify-signatures", &verify_signatures, + N_("Verify that the named commit has a valid GPG signature")), OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"), N_("merge strategy to use"), option_parse_strategy), OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
@@ -1233,6 +1235,35 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_with_options(builtin_merge_usage, builtin_merge_options); + if (verify_signatures) { + /* Verify the commit signatures */ + for (p = remoteheads; p; p = p->next) { + struct commit *commit = p->item; + struct signature signature; + signature.check_result = 0; + + check_commit_signature(commit, &signature); + + char hex[41]; + strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + switch(signature.check_result){ + case 'G': + if (verbosity >= 0) + printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature.signer, signature.key); + break; + case 'B': + die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature.signer, signature.key); + default: /* 'N' */ + die(_("Commit %s does not have a good GPG signature. In fact, commit %s does not have a GPG signature at all."), hex, hex); + } + + free(signature.gpg_output); + free(signature.gpg_status); + free(signature.signer); + free(signature.key); + } + } + strbuf_addstr(&buf, "merge"); for (p = remoteheads; p; p = p->next) strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name);
diff --git a/git-pull.sh b/git-pull.sh
index 266e682..705940d 100755
--- a/git-pull.sh
+++ b/git-pull.sh@@ -39,7 +39,7 @@ test -z "$(git ls-files -u)" || die_conflict test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= -log_arg= verbosity= progress= recurse_submodules= +log_arg= verbosity= progress= recurse_submodules= verify_signatures= merge_args= edit= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short="${curr_branch#refs/heads/}"
@@ -125,6 +125,12 @@ do --no-recurse-submodules) recurse_submodules=--no-recurse-submodules ;; + --verify-signatures) + verify_signatures=--verify-signatures + ;; + --no-verify-signatures) + verify_signatures=--no-verify-signatures + ;; --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) dry_run=--dry-run ;;
@@ -283,7 +289,7 @@ true) eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}" ;; *) - eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only" + eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only" eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress" eval="$eval \"\$merge_name\" HEAD $merge_head" ;;
diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
new file mode 100755
index 0000000..31b67dd
--- /dev/null
+++ b/t/t7612-merge-verify-signatures.sh@@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='merge signature verification tests' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPG 'create signed commits' ' + echo 1 >file && git add file && + test_tick && git commit -m initial && + git tag initial && + + git checkout -b side-signed && + echo 3 >elif && git add elif && + test_tick && git commit -S -m "signed on side" && + git checkout initial && + + git checkout -b side-unsigned && + echo 3 >foo && git add foo && + test_tick && git commit -m "unsigned on side" && + git checkout initial && + + git checkout -b side-bad && + echo 3 >bar && git add bar && + test_tick && git commit -S -m "bad on side" && + git cat-file commit side-bad >raw && + sed -e "s/bad/forged bad/" raw >forged && + git hash-object -w -t commit forged >forged.commit && + git checkout initial && + + git checkout master +' + +test_expect_success GPG 'merge unsigned commit with verification' ' + test_must_fail git merge --ff-only --verify-signatures side-unsigned 2> mergeerror && + grep "does not have a GPG signature" mergeerror +' + +test_expect_success GPG 'merge commit with bad signature with verification' ' + test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2> mergeerror && + grep "has a bad GPG signature" mergeerror +' + +test_expect_success GPG 'merge signed commit with verification' ' + git merge -v --ff-only --verify-signatures side-signed > mergeoutput && + grep "has a good GPG signature" mergeoutput +' + +test_expect_success GPG 'merge commit with bad signature without verification' ' + git merge $(cat forged.commit) +' + +test_done
--
1.8.1.5