merge/pull: verify GPG signatures of commits being merged
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 <jaseg@physik-pool.tu-berlin.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Sebastian Götte committed
Mar 31, 2013 at 18:02 UTC
efed0022492b81bf59d29193c4ffe96492dd9e9b
4 files changed
+98
-3
Documentation/merge-options.txt
+5
@@ -83,6 +83,11 @@ option can be used to override --squash.
83
Pass merge strategy specific option through to the merge
84
strategy.
85
86
+--verify-signatures::
87
+--no-verify-signatures::
88
+ Verify that the commits being merged have good GPG signatures and abort the
89
+ merge in case they do not.
90
+
91
--summary::
92
--no-summary::
93
Synonyms to --stat and --no-stat; these are deprecated and will be
builtin/merge.c
+33
-1
@@ -49,7 +49,7 @@ static const char * const builtin_merge_usage[] = {
49
static int show_diffstat = 1, shortlog_len = -1, squash;
50
static int option_commit = 1, allow_fast_forward = 1;
51
static int fast_forward_only, option_edit = -1;
52
-static int allow_trivial = 1, have_message;
52
+static int allow_trivial = 1, have_message, verify_signatures;
53
static int overwrite_ignore = 1;
54
static struct strbuf merge_msg = STRBUF_INIT;
55
static struct strategy **use_strategies;
@@ -199,6 +199,8 @@ static struct option builtin_merge_options[] = {
199
OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
200
N_("abort if fast-forward is not possible")),
201
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
202
+ OPT_BOOL(0, "verify-signatures", &verify_signatures,
203
+ N_("Verify that the named commit has a valid GPG signature")),
204
OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"),
205
N_("merge strategy to use"), option_parse_strategy),
206
OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
@@ -1233,6 +1235,36 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
1235
usage_with_options(builtin_merge_usage,
1236
builtin_merge_options);
1237
1238
+ if (verify_signatures) {
1239
+ for (p = remoteheads; p; p = p->next) {
1240
+ struct commit *commit = p->item;
1241
+ char hex[41];
1242
+ struct signature_check signature_check;
1243
+ memset(&signature_check, 0, sizeof(signature_check));
1244
+
1245
+ check_commit_signature(commit, &signature_check);
1246
+
1247
+ strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
1248
+ switch (signature_check.result) {
1249
+ case 'G':
1250
+ break;
1251
+ case 'B':
1252
+ die(_("Commit %s has a bad GPG signature "
1253
+ "allegedly by %s."), hex, signature_check.signer);
1254
+ default: /* 'N' */
1255
+ die(_("Commit %s does not have a GPG signature."), hex);
1256
+ }
1257
+ if (verbosity >= 0 && signature_check.result == 'G')
1258
+ printf(_("Commit %s has a good GPG signature by %s\n"),
1259
+ hex, signature_check.signer);
1260
+
1261
+ free(signature_check.gpg_output);
1262
+ free(signature_check.gpg_status);
1263
+ free(signature_check.signer);
1264
+ free(signature_check.key);
1265
+ }
1266
+ }
1267
+
1268
strbuf_addstr(&buf, "merge");
1269
for (p = remoteheads; p; p = p->next)
1270
strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name);
git-pull.sh
+8
-2
@@ -39,7 +39,7 @@ test -z "$(git ls-files -u)" || die_conflict
39
test -f "$GIT_DIR/MERGE_HEAD" && die_merge
40
41
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
42
-log_arg= verbosity= progress= recurse_submodules=
42
+log_arg= verbosity= progress= recurse_submodules= verify_signatures=
43
merge_args= edit=
44
curr_branch=$(git symbolic-ref -q HEAD)
45
curr_branch_short="${curr_branch#refs/heads/}"
@@ -125,6 +125,12 @@ do
125
--no-recurse-submodules)
126
recurse_submodules=--no-recurse-submodules
127
;;
128
+ --verify-signatures)
129
+ verify_signatures=--verify-signatures
130
+ ;;
131
+ --no-verify-signatures)
132
+ verify_signatures=--no-verify-signatures
133
+ ;;
134
--d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
135
dry_run=--dry-run
136
;;
@@ -283,7 +289,7 @@ true)
289
eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
290
;;
291
*)
286
- eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only"
292
+ eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only"
293
eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
294
eval="$eval \"\$merge_name\" HEAD $merge_head"
295
;;
t/t7612-merge-verify-signatures.sh
new
+52
@@ -0,0 +1,52 @@
1
+#!/bin/sh
2
+
3
+test_description='merge signature verification tests'
4
+. ./test-lib.sh
5
+. "$TEST_DIRECTORY/lib-gpg.sh"
6
+
7
+test_expect_success GPG 'create signed commits' '
8
+ echo 1 >file && git add file &&
9
+ test_tick && git commit -m initial &&
10
+ git tag initial &&
11
+
12
+ git checkout -b side-signed &&
13
+ echo 3 >elif && git add elif &&
14
+ test_tick && git commit -S -m "signed on side" &&
15
+ git checkout initial &&
16
+
17
+ git checkout -b side-unsigned &&
18
+ echo 3 >foo && git add foo &&
19
+ test_tick && git commit -m "unsigned on side" &&
20
+ git checkout initial &&
21
+
22
+ git checkout -b side-bad &&
23
+ echo 3 >bar && git add bar &&
24
+ test_tick && git commit -S -m "bad on side" &&
25
+ git cat-file commit side-bad >raw &&
26
+ sed -e "s/bad/forged bad/" raw >forged &&
27
+ git hash-object -w -t commit forged >forged.commit &&
28
+ git checkout initial &&
29
+
30
+ git checkout master
31
+'
32
+
33
+test_expect_success GPG 'merge unsigned commit with verification' '
34
+ test_must_fail git merge --ff-only --verify-signatures side-unsigned 2>mergeerror &&
35
+ test_i18ngrep "does not have a GPG signature" mergeerror
36
+'
37
+
38
+test_expect_success GPG 'merge commit with bad signature with verification' '
39
+ test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2>mergeerror &&
40
+ test_i18ngrep "has a bad GPG signature" mergeerror
41
+'
42
+
43
+test_expect_success GPG 'merge signed commit with verification' '
44
+ git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
45
+ test_i18ngrep "has a good GPG signature" mergeoutput
46
+'
47
+
48
+test_expect_success GPG 'merge commit with bad signature without verification' '
49
+ git merge $(cat forged.commit)
50
+'
51
+
52
+test_done