{"thread":{"id":"33262","subject":"[PATCH] merge/pull: verify GPG signatures of commits being merged","startedAt":"2013-03-22T21:51:40Z","lastAt":"2013-03-23T03:08:53Z","messageCount":4,"participants":["Sebastian Götte","Junio C Hamano","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"211992","messageId":"514CD26C.2070702@physik.tu-berlin.de","threadId":"33262","inReplyTo":null,"subject":"[PATCH] merge/pull: verify GPG signatures of commits being merged","fromName":"Sebastian Götte","fromEmail":"jaseg@physik.tu-berlin.de","sentAt":"2013-03-22T21:51:40Z","receivedAt":"2013-03-22T21:51:40Z","isPatch":true,"sender":{"key":"jaseg@physik.tu-berlin.de","avatar":null},"body":"git merge/pull:\nWhen --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\npretty printing:\nTell about an \"untrusted good signature\" in addition to the previous\n\"good signature\" and \"bad signature\". In case of a missing signature,\nexpand the pretty format string \"%G?\" to \"N\" in since this eases the\nwirting of anything parsing git-log output.\n\nSigned-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>\n---\nI moved the commit signature verification code from pretty.c to commit.c\nbecause it is used from pretty.c and builtin/merge.c. I include that pretty\nprinting change here because I needed to add the good/untrusted check for the\nmerge --verify-signatures option to really make sense.\n\nThose new %G? options are implicitly tested by t7612-merge-verify-signatures.sh\nbecause %G? is just replaced by the passed-through output of the commit\nverification function.\n\n Documentation/merge-options.txt    |   4 +++\n Documentation/pretty-formats.txt   |   2 +-\n builtin/merge.c                    |  33 +++++++++++++++++-\n commit.c                           |  55 +++++++++++++++++++++++++++++\n commit.h                           |   9 +++++\n git-pull.sh                        |  10 ++++--\n gpg-interface.h                    |   6 ++++\n pretty.c                           |  69 ++++---------------------------------\n t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes\n t/lib-gpg/random_seed              | Bin 600 -> 600 bytes\n t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes\n t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes\n t/t7612-merge-verify-signatures.sh |  61 ++++++++++++++++++++++++++++++++\n 13 files changed, 183 insertions(+), 66 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/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\nindex 105f18a..7297b1b 100644\n--- a/Documentation/pretty-formats.txt\n+++ b/Documentation/pretty-formats.txt\n@@ -131,7 +131,7 @@ The placeholders are:\n - '%B': raw body (unwrapped subject and body)\n - '%N': commit notes\n - '%GG': raw verification message from GPG for a signed commit\n-- '%G?': show either \"G\" for Good or \"B\" for Bad for a signed commit\n+- '%G?': show \"G\" for a Good signature, \"B\" for a Bad signature, \"U\" for a good, untrusted signature and \"N\" for no signature\n - '%GS': show the name of the signer for a signed commit\n - '%gD': reflog selector, e.g., `refs/stash@{1}`\n - '%gd': shortened reflog selector, e.g., `stash@{1}`\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7c8922c..37ece3d 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 = 0;\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,35 @@ 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+\t\t\t\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 'U':\n+\t\t\t\t\tdie(_(\"Commmit %s has a good GPG signature allegedly by %s, albeit from an untrusted key.\"), hex, signature.signer);\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/commit.c b/commit.c\nindex e8eb0ae..300cf98 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1023,6 +1023,61 @@ free_return:\n \tfree(buf);\n }\n \n+static struct {\n+\tchar result;\n+\tconst char *check;\n+} signature_check[] = {\n+\t{ 'U', \": WARNING: This key is not certified with a trusted signature!\" },\n+\t{ 'G', \": Good signature from \" },\n+\t{ 'B', \": BAD signature from \" },\n+};\n+\n+static void parse_signature_lines(struct signature *sig)\n+{\n+\tconst char *buf = sig->gpg_output;\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(signature_check); i++) {\n+\t\tconst char *found = strstr(buf, signature_check[i].check);\n+\t\tconst char *next;\n+\t\tif (!found)\n+\t\t\tcontinue;\n+\t\tsig->check_result = signature_check[i].result;\n+\t\tfound += strlen(signature_check[i].check);\n+\t\tnext = strchrnul(found, '\\n');\n+\t\tsig->signer = xmemdupz(found, next - found);\n+\t\tbreak;\n+\t}\n+}\n+\n+void check_commit_signature(const struct commit* commit, struct signature *sig)\n+{\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tstruct strbuf signature = STRBUF_INIT;\n+\tstruct strbuf gpg_output = STRBUF_INIT;\n+\tint status;\n+\n+\tsig->check_result = 'N';\n+\n+\tif (parse_signed_commit(commit->object.sha1,\n+\t\t\t\t&payload, &signature) <= 0)\n+\t\tgoto out;\n+\tstatus = verify_signed_buffer(payload.buf, payload.len,\n+\t\t\t\t      signature.buf, signature.len,\n+\t\t\t\t      &gpg_output);\n+\tif (status && !gpg_output.len)\n+\t\tgoto out;\n+\tsig->gpg_output = strbuf_detach(&gpg_output, NULL);\n+\tparse_signature_lines(sig);\n+\n+ out:\n+\tstrbuf_release(&gpg_output);\n+\tstrbuf_release(&payload);\n+\tstrbuf_release(&signature);\n+}\n+\n+\n+\n void append_merge_tag_headers(struct commit_list *parents,\n \t\t\t      struct commit_extra_header ***tail)\n {\ndiff --git a/commit.h b/commit.h\nindex 4138bb4..2189eb6 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -5,6 +5,7 @@\n #include \"tree.h\"\n #include \"strbuf.h\"\n #include \"decorate.h\"\n+#include \"gpg-interface.h\"\n \n struct commit_list {\n \tstruct commit *item;\n@@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,\n \t\t\t      const char *format_cur,\n \t\t\t      const char *format_last);\n \n+/*\n+ * Check the signature of the given commit. The result of the check is stored in\n+ * sig->check_result, 'G' for a good signature, 'U' for a good signature from an\n+ * untrusted signer, 'B' for a bad signature and 'N' for no signature at all.\n+ * This may allocate memory for sig->gpg_output and sig->signer.\n+ */\n+extern void check_commit_signature(const struct commit* commit, struct signature *sig);\n+\n #endif /* COMMIT_H */\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/gpg-interface.h b/gpg-interface.h\nindex b9c3608..3895b2f 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -1,6 +1,12 @@\n #ifndef GPG_INTERFACE_H\n #define GPG_INTERFACE_H\n \n+struct signature {\n+\tchar *gpg_output;\n+\tchar check_result; // 0 (not checked), N (checked but no further result), U (untrusted, good), G (good) or B (bad)\n+\tchar *signer;\n+};\n+\n extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);\n extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);\n extern int git_gpg_config(const char *, const char *, void *);\ndiff --git a/pretty.c b/pretty.c\nindex eae57ad..875a1db 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -756,12 +756,7 @@ struct format_commit_context {\n \tconst struct pretty_print_context *pretty_ctx;\n \tunsigned commit_header_parsed:1;\n \tunsigned commit_message_parsed:1;\n-\tunsigned commit_signature_parsed:1;\n-\tstruct {\n-\t\tchar *gpg_output;\n-\t\tchar good_bad;\n-\t\tchar *signer;\n-\t} signature;\n+\tstruct signature signature;\n \tchar *message;\n \tsize_t width, indent1, indent2;\n \n@@ -944,58 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,\n \tc->indent2 = new_indent2;\n }\n \n-static struct {\n-\tchar result;\n-\tconst char *check;\n-} signature_check[] = {\n-\t{ 'G', \": Good signature from \" },\n-\t{ 'B', \": BAD signature from \" },\n-};\n-\n-static void parse_signature_lines(struct format_commit_context *ctx)\n-{\n-\tconst char *buf = ctx->signature.gpg_output;\n-\tint i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(signature_check); i++) {\n-\t\tconst char *found = strstr(buf, signature_check[i].check);\n-\t\tconst char *next;\n-\t\tif (!found)\n-\t\t\tcontinue;\n-\t\tctx->signature.good_bad = signature_check[i].result;\n-\t\tfound += strlen(signature_check[i].check);\n-\t\tnext = strchrnul(found, '\\n');\n-\t\tctx->signature.signer = xmemdupz(found, next - found);\n-\t\tbreak;\n-\t}\n-}\n-\n-static void parse_commit_signature(struct format_commit_context *ctx)\n-{\n-\tstruct strbuf payload = STRBUF_INIT;\n-\tstruct strbuf signature = STRBUF_INIT;\n-\tstruct strbuf gpg_output = STRBUF_INIT;\n-\tint status;\n-\n-\tctx->commit_signature_parsed = 1;\n-\n-\tif (parse_signed_commit(ctx->commit->object.sha1,\n-\t\t\t\t&payload, &signature) <= 0)\n-\t\tgoto out;\n-\tstatus = verify_signed_buffer(payload.buf, payload.len,\n-\t\t\t\t      signature.buf, signature.len,\n-\t\t\t\t      &gpg_output);\n-\tif (status && !gpg_output.len)\n-\t\tgoto out;\n-\tctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);\n-\tparse_signature_lines(ctx);\n-\n- out:\n-\tstrbuf_release(&gpg_output);\n-\tstrbuf_release(&payload);\n-\tstrbuf_release(&signature);\n-}\n-\n \n static int format_reflog_person(struct strbuf *sb,\n \t\t\t\tchar part,\n@@ -1182,18 +1125,20 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t}\n \n \tif (placeholder[0] == 'G') {\n-\t\tif (!c->commit_signature_parsed)\n-\t\t\tparse_commit_signature(c);\n+\t\tif (!c->signature.check_result)\n+\t\t\tcheck_commit_signature(c->commit, &(c->signature));\n \t\tswitch (placeholder[1]) {\n \t\tcase 'G':\n \t\t\tif (c->signature.gpg_output)\n \t\t\t\tstrbuf_addstr(sb, c->signature.gpg_output);\n \t\t\tbreak;\n \t\tcase '?':\n-\t\t\tswitch (c->signature.good_bad) {\n+\t\t\tswitch (c->signature.check_result) {\n \t\t\tcase 'G':\n \t\t\tcase 'B':\n-\t\t\t\tstrbuf_addch(sb, c->signature.good_bad);\n+\t\t\tcase 'U':\n+\t\t\tcase 'N':\n+\t\t\t\tstrbuf_addch(sb, c->signature.check_result);\n \t\t\t}\n \t\t\tbreak;\n \t\tcase 'S':\ndiff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg\nindex 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644\nGIT binary patch\ndelta 1212\nzcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O\nzhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y\nzF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4\nz>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou\nz8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^\nzLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5\nza%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS\nz0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El\nzxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V\nzFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;\nz8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd\nzxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{\nzsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$\nzc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie\nzrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*\nz4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#\nz-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?\nz^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_\nzmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir\nzXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^\nzgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_\nzqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)\nzg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r\na9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O\n\ndelta 7\nOcmdlk)Wf-<hXnu&fC8TY\n\ndiff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed\nindex 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644\nGIT binary patch\nliteral 600\nzcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw\nza}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv\nzQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-\nzoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U\nzNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9\nzv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P\nzO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41\nzNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q\nzc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(\nzlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;\nzPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN\nmb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll\n\nliteral 600\nzcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%\nzzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8\nz)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)\nz-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ\nzzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<\nzk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-\nz(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ\nzgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl\nzx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u\nziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic\nz+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp\nmVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35\n\ndiff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg\nindex d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644\nGIT binary patch\ndelta 2524\n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\n\ndelta 7\nOcmbOxdzEv;RTcmY+5;N^\n\ndiff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg\nindex abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644\nGIT binary patch\ndelta 133\nzcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX\nzhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6\nDE~Fa=\n\ndelta 52\nzcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p\nADF6Tf\n\ndiff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh\nnew file mode 100755\nindex 0000000..47a4f80\n--- /dev/null\n+++ b/t/t7612-merge-verify-signatures.sh\n@@ -0,0 +1,61 @@\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 -b side-untrusted &&\n+\techo 3 >baz && git add baz &&\n+\ttest_tick && git commit -SB7227189 -m \"untrusted on side\"\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  commit with untrusted signature with verification' '\n+\ttest_must_fail git merge --ff-only --verify-signatures side-untrusted 2> mergeerror &&\n+\tgrep \"from an untrusted key\" 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"},{"id":"212001","messageId":"7v4ng3597s.fsf@alter.siamese.dyndns.org","threadId":"33262","inReplyTo":"514CD26C.2070702@physik.tu-berlin.de","subject":"Re: [PATCH] merge/pull: verify GPG signatures of commits being merged","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-22T23:02:47Z","receivedAt":"2013-03-22T23:02:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sebastian Götte <jaseg@physik.tu-berlin.de> writes:\n\n> git merge/pull:\n> When --verify-signatures is specified on the command-line of git-merge\n> or git-pull, check whether the commits being merged have good gpg\n> signatures and abort the merge in case they do not. This allows e.g.\n> auto-deployment from untrusted repo hosts.\n>\n> pretty printing:\n> Tell about an \"untrusted good signature\" in addition to the previous\n> \"good signature\" and \"bad signature\". In case of a missing signature,\n> expand the pretty format string \"%G?\" to \"N\" in since this eases the\n> wirting of anything parsing git-log output.\n>\n> Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>\n> ---\n> I moved the commit signature verification code from pretty.c to commit.c\n> because it is used from pretty.c and builtin/merge.c. I include that pretty\n> printing change here because I needed to add the good/untrusted check for the\n> merge --verify-signatures option to really make sense.\n>\n> Those new %G? options are implicitly tested by t7612-merge-verify-signatures.sh\n> because %G? is just replaced by the passed-through output of the commit\n> verification function.\n\nWhile I think the new --verify-signature option may be a good\naddition, I wonder if you can split this patch down a bit for easier\nreview and validation.\n\nPerhaps this needs to be done in at least three steps:\n\n    (1) first move the code without changing anything (most\n        importantly, do not add 'U' or 'N' at this step); then\n\n    (2) teach 'merge' and 'pull' to understand the new option, and\n        finally;\n\n    (3) introduce 'U' and 'N'.\n\nThe existing users of '%G?' placeholders are not expecting to see\n'N' but turning it into an empty string, so if the third step turns\nout to be problematic to these users, we can discard the third step\nand still benefit from the first two, for example.\n\n> diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt\n> index 105f18a..7297b1b 100644\n> --- a/Documentation/pretty-formats.txt\n> +++ b/Documentation/pretty-formats.txt\n> @@ -131,7 +131,7 @@ The placeholders are:\n>  - '%B': raw body (unwrapped subject and body)\n>  - '%N': commit notes\n>  - '%GG': raw verification message from GPG for a signed commit\n> -- '%G?': show either \"G\" for Good or \"B\" for Bad for a signed commit\n> +- '%G?': show \"G\" for a Good signature, \"B\" for a Bad signature, \"U\" for a good, untrusted signature and \"N\" for no signature\n\nEven though this is a source that is turned into html and manpages,\npeople do read these in the original text format (that is the whole\npoint of using AsciiDoc as the source format), so please see if you\ncan avoid this overly long line.\n\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index 7c8922c..37ece3d 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 = 0;\n\nAvoid initializing static variables to 0, and instead let BSS take\ncare of them.\n\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,35 @@ 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\nNo // C99/C++ comments.\n\nThe rest of the patch not reviewed; expecting a split reroll.\n\nThanks.\n"},{"id":"212005","messageId":"20130322231430.GK12223@google.com","threadId":"33262","inReplyTo":"514CD26C.2070702@physik.tu-berlin.de","subject":"Re: [PATCH] merge/pull: verify GPG signatures of commits being merged","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2013-03-22T23:14:30Z","receivedAt":"2013-03-22T23:14:30Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nSebastian Götte wrote:\n\n> git merge/pull:\n> When --verify-signatures is specified on the command-line of git-merge\n> or git-pull, check whether the commits being merged have good gpg\n> signatures and abort the merge in case they do not. This allows e.g.\n> auto-deployment from untrusted repo hosts.\n\nThis leaves me pretty nervous.  Is there an argument to pass in to\nspecify a keyring with public keys to trust?  Without that, it is\npresumably using ~/.gnupg/trustdb.gpg, which is about trust of\nidentity rather than trust to provide code to run on my machine. :(\n\nIf there's a good way to avoid that, this looks like a good thing to\ndo, though.\n\nHope that helps,\nJonathan\n"},{"id":"212015","messageId":"7vvc8i4xtm.fsf@alter.siamese.dyndns.org","threadId":"33262","inReplyTo":"20130322231430.GK12223@google.com","subject":"Re: [PATCH] merge/pull: verify GPG signatures of commits being merged","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-03-23T03:08:53Z","receivedAt":"2013-03-23T03:08:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> git merge/pull:\n>> When --verify-signatures is specified on the command-line of git-merge\n>> or git-pull, check whether the commits being merged have good gpg\n>> signatures and abort the merge in case they do not. This allows e.g.\n>> auto-deployment from untrusted repo hosts.\n>\n> This leaves me pretty nervous.  Is there an argument to pass in to\n> specify a keyring with public keys to trust?  Without that, it is\n> presumably using ~/.gnupg/trustdb.gpg, which is about trust of\n> identity rather than trust to provide code to run on my machine. :(\n\nI think people who create a real merge via \"git pull\" and use that\nas \"auto-deployment\" mechanism is insane, but presumably that \"auto\"\ntells us some other things, like it will be done by non-human account,\nits $HOME/.gnupg would contain only the keyring that is for the auto\ndeployer, or the cronscript that runs \"git pull\" can set GNUPGHOME\nand export it before doing so.\n\nSo, I wouldn't be worried about it too much.\n"}]}