All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/4] Move commit GPG signature verification to commit.c
@ 2013-03-23  1:57 Sebastian Götte
  2013-03-25 15:54 ` Junio C Hamano
  0 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-23  1:57 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 commit.h        |  9 ++++++++
 gpg-interface.h |  6 ++++++
 pretty.c        | 67 +++++----------------------------------------------------
 4 files changed, 74 insertions(+), 62 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..d0d9135 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,60 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} signature_check[] = {
+	{ 'G', ": Good signature from " },
+	{ 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct signature *sig)
+{
+	const char *buf = sig->gpg_output;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+		const char *found = strstr(buf, signature_check[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sig->check_result = signature_check[i].result;
+		found += strlen(signature_check[i].check);
+		next = strchrnul(found, '\n');
+		sig->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void check_commit_signature(const struct commit* commit, struct signature *sig)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+	struct strbuf gpg_output = STRBUF_INIT;
+	int status;
+
+	sig->check_result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&payload, &signature) <= 0)
+		goto out;
+	status = verify_signed_buffer(payload.buf, payload.len,
+				      signature.buf, signature.len,
+				      &gpg_output);
+	if (status && !gpg_output.len)
+		goto out;
+	sig->gpg_output = strbuf_detach(&gpg_output, NULL);
+	parse_signature_lines(sig);
+
+ out:
+	strbuf_release(&gpg_output);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..eada616 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature at all.
+ * This may allocate memory for sig->gpg_output and sig->signer.
+ */
+extern void check_commit_signature(const struct commit* commit, struct signature *sig);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index b9c3608..c69130f 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,12 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature {
+	char *gpg_output;
+	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char *signer;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index eae57ad..0547df1 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,12 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char good_bad;
-		char *signer;
-	} signature;
+	struct signature signature;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -944,58 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', ": Good signature from " },
-	{ 'B', ": BAD signature from " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_output;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&payload, &signature) <= 0)
-		goto out;
-	status = verify_signed_buffer(payload.buf, payload.len,
-				      signature.buf, signature.len,
-				      &gpg_output);
-	if (status && !gpg_output.len)
-		goto out;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
 
 static int format_reflog_person(struct strbuf *sb,
 				char part,
@@ -1182,18 +1125,18 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature.check_result)
+			check_commit_signature(c->commit, &(c->signature));
 		switch (placeholder[1]) {
 		case 'G':
 			if (c->signature.gpg_output)
 				strbuf_addstr(sb, c->signature.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature.check_result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature.check_result);
 			}
 			break;
 		case 'S':
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v2 1/4] Move commit GPG signature verification to commit.c
  2013-03-23  1:57 [PATCH v2 1/4] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-25 15:54 ` Junio C Hamano
  2013-03-25 23:46   ` [PATCH 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
  0 siblings, 2 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-25 15:54 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
> ---
>  commit.c        | 54 ++++++++++++++++++++++++++++++++++++++++++++++
>  commit.h        |  9 ++++++++
>  gpg-interface.h |  6 ++++++
>  pretty.c        | 67 +++++----------------------------------------------------
>  4 files changed, 74 insertions(+), 62 deletions(-)
>
> diff --git a/commit.c b/commit.c
> index e8eb0ae..d0d9135 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -1023,6 +1023,60 @@ free_return:
>  	free(buf);
>  }
>  
> +static struct {
> +	char result;
> +	const char *check;
> +} signature_check[] = {
> +	{ 'G', ": Good signature from " },
> +	{ 'B', ": BAD signature from " },
> +};

This seems to be based on the old codebase.  4a868fd655a7 (pretty:
parse the gpg status lines rather than the output, 2013-02-14) is
already in 'master' for this cycle, and it is likely that we would
want to have the same fix for 1.8.1.x maintenance track as well.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-25 15:54 ` Junio C Hamano
@ 2013-03-25 23:46   ` Sebastian Götte
  2013-03-26  1:46     ` Junio C Hamano
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
  1 sibling, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

Rebased it onto the current 'master'. The second patch fixes that the GPG
status parser ignores the first line of GPG status output (that would be caught
by the new merge signature verification test case).

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   4 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  35 ++++++++++++++++-
 commit.c                           |  61 +++++++++++++++++++++++++++++
 commit.h                           |  10 +++++
 git-pull.sh                        |  10 ++++-
 gpg-interface.h                    |   8 ++++
 pretty.c                           |  77 ++++---------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 +++++++++++++++++++++++++++++
 13 files changed, 195 insertions(+), 74 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH 1/5] Move commit GPG signature verification to commit.c
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-25 23:46     ` Sebastian Götte
  2013-03-25 23:46     ` [PATCH 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                       ` (3 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++++++++++
 commit.h        |  9 +++++++
 gpg-interface.h |  8 ++++++
 pretty.c        | 75 ++++-----------------------------------------------------
 4 files changed, 81 insertions(+), 70 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..1aeb17a 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} signature_check[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_signature_lines(struct signature *sig)
+{
+	const char *buf = sig->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+		const char *found = strstr(buf, signature_check[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sig->check_result = signature_check[i].result;
+		found += strlen(signature_check[i].check);
+		sig->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sig->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void check_commit_signature(const struct commit* commit, struct signature *sig)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+	struct strbuf gpg_output = STRBUF_INIT;
+	struct strbuf gpg_status = STRBUF_INIT;
+	int status;
+
+	sig->check_result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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;
+	sig->gpg_output = strbuf_detach(&gpg_output, NULL);
+	sig->gpg_status = strbuf_detach(&gpg_status, NULL);
+	parse_signature_lines(sig);
+
+ out:
+	strbuf_release(&gpg_status);
+	strbuf_release(&gpg_output);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..cf35472 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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 *sig);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..2a536d9 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,14 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature {
+	char *gpg_output;
+	char *gpg_status;
+	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..3aac5d8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature signature;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,18 +1124,18 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature.check_result)
+			check_commit_signature(c->commit, &(c->signature));
 		switch (placeholder[1]) {
 		case 'G':
 			if (c->signature.gpg_output)
 				strbuf_addstr(sb, c->signature.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature.check_result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature.check_result);
 			}
 			break;
 		case 'S':
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-25 23:46     ` [PATCH 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-25 23:46     ` Sebastian Götte
  2013-03-25 23:46     ` [PATCH 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                       ` (2 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/commit.c b/commit.c
index 1aeb17a..f53a06d 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,8 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'G', "[GNUPG:] GOODSIG " },
+	{ 'B', "[GNUPG:] BADSIG " },
 };
 
 static void parse_signature_lines(struct signature *sig)
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-25 23:46     ` [PATCH 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-25 23:46     ` [PATCH 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-25 23:46     ` Sebastian Götte
  2013-03-25 23:46     ` [PATCH 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-25 23:46     ` [PATCH 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

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>
---
 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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
                       ` (2 preceding siblings ...)
  2013-03-25 23:46     ` [PATCH 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-25 23:46     ` Sebastian Götte
  2013-03-25 23:46     ` [PATCH 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 builtin/merge.c                    |   2 ++
 commit.c                           |   2 ++
 commit.h                           |   9 +++++----
 gpg-interface.h                    |   2 +-
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 9 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 227040b..e3df36f 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1251,6 +1251,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 					if (verbosity >= 0)
 						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature.signer, signature.key);
 					break;
+				case 'U':
+					die(_("Commit %s has a good GPG signature allegedly by %s, albeit from an untrusted key (fingerprint %s)."), hex, signature.signer, signature.key);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature.signer, signature.key);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index f53a06d..3598f75 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,6 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } signature_check[] = {
+	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
+	{ 'U', "[GNUPG:] TRUST_NEVER" },
 	{ 'G', "[GNUPG:] GOODSIG " },
 	{ 'B', "[GNUPG:] BADSIG " },
 };
diff --git a/commit.h b/commit.h
index cf35472..c946135 100644
--- a/commit.h
+++ b/commit.h
@@ -232,10 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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 *sig);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 2a536d9..7fd3021 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -4,7 +4,7 @@
 struct signature {
 	char *gpg_output;
 	char *gpg_status;
-	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char check_result; /* 0 (not checked), N (checked but no further result), U (untrusted, good), G (good) or B (bad) */
 	char *signer;
 	char *key;
 };
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 31b67dd..47a4f80 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	grep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge  commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2> mergeerror &&
+	grep "from an untrusted key" 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
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
                       ` (3 preceding siblings ...)
  2013-03-25 23:46     ` [PATCH 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-25 23:46     ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-25 23:46 UTC (permalink / raw)
  To: git; +Cc: gitster

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index 3aac5d8..fc74795 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature.check_result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature.check_result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-25 23:46   ` [PATCH 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
@ 2013-03-26  1:46     ` Junio C Hamano
  2013-03-26 11:05       ` [PATCH v4 " Sebastian Götte
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
  0 siblings, 2 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-26  1:46 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> Rebased it onto the current 'master'. The second patch fixes that the GPG
> status parser ignores the first line of GPG status output (that would be caught
> by the new merge signature verification test case).

Thanks.

Does it still make sure that it won't be fooled by the expected
string appearing in the middle of a line, not at the beginning?

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v4 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-26  1:46     ` Junio C Hamano
@ 2013-03-26 11:05       ` Sebastian Götte
  2013-03-26 16:26         ` Junio C Hamano
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
  1 sibling, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

On 03/26/2013 02:46 AM, Junio C Hamano wrote:> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
>> Rebased it onto the current 'master'. The second patch fixes that the GPG
>> status parser ignores the first line of GPG status output (that would be caught
>> by the new merge signature verification test case).
> 
> Thanks.
> 
> Does it still make sure that it won't be fooled by the expected
> string appearing in the middle of a line, not at the beginning?
I thought that would not be a problem until I noticed it checks for GOODSIG
before it checks for BADSIG. Here is a fix.

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   4 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  35 ++++++++++++++++-
 commit.c                           |  64 ++++++++++++++++++++++++++++++
 commit.h                           |  10 +++++
 git-pull.sh                        |  10 ++++-
 gpg-interface.h                    |   8 ++++
 pretty.c                           |  77 ++++---------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 +++++++++++++++++++++++++++++
 13 files changed, 198 insertions(+), 74 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v4 1/5] Move commit GPG signature verification to commit.c
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-26 11:05         ` Sebastian Götte
  2013-03-26 11:05         ` [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                           ` (3 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++++++++++
 commit.h        |  9 +++++++
 gpg-interface.h |  8 ++++++
 pretty.c        | 75 ++++-----------------------------------------------------
 4 files changed, 81 insertions(+), 70 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..1aeb17a 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} signature_check[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_signature_lines(struct signature *sig)
+{
+	const char *buf = sig->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+		const char *found = strstr(buf, signature_check[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sig->check_result = signature_check[i].result;
+		found += strlen(signature_check[i].check);
+		sig->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sig->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void check_commit_signature(const struct commit* commit, struct signature *sig)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+	struct strbuf gpg_output = STRBUF_INIT;
+	struct strbuf gpg_status = STRBUF_INIT;
+	int status;
+
+	sig->check_result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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;
+	sig->gpg_output = strbuf_detach(&gpg_output, NULL);
+	sig->gpg_status = strbuf_detach(&gpg_status, NULL);
+	parse_signature_lines(sig);
+
+ out:
+	strbuf_release(&gpg_status);
+	strbuf_release(&gpg_output);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..cf35472 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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 *sig);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..2a536d9 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,14 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature {
+	char *gpg_output;
+	char *gpg_status;
+	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..3aac5d8 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature signature;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,18 +1124,18 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature.check_result)
+			check_commit_signature(c->commit, &(c->signature));
 		switch (placeholder[1]) {
 		case 'G':
 			if (c->signature.gpg_output)
 				strbuf_addstr(sb, c->signature.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature.check_result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature.check_result);
 			}
 			break;
 		case 'S':
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-26 11:05         ` [PATCH v4 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-26 11:05         ` Sebastian Götte
  2013-03-28 22:33           ` Junio C Hamano
  2013-03-26 11:05         ` [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                           ` (2 subsequent siblings)
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/commit.c b/commit.c
index 1aeb17a..533727c 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,8 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'G', "[GNUPG:] GOODSIG " },
+	{ 'B', "[GNUPG:] BADSIG " },
 };
 
 static void parse_signature_lines(struct signature *sig)
@@ -1041,6 +1041,9 @@ static void parse_signature_lines(struct signature *sig)
 		const char *next;
 		if (!found)
 			continue;
+		if (found != buf) 
+			if (found[-1] != '\n')
+				continue;
 		sig->check_result = signature_check[i].result;
 		found += strlen(signature_check[i].check);
 		sig->key = xmemdupz(found, 16);
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-26 11:05         ` [PATCH v4 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-26 11:05         ` [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-26 11:05         ` Sebastian Götte
  2013-03-28 22:33           ` Junio C Hamano
  2013-03-26 11:05         ` [PATCH v4 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-26 11:05         ` [PATCH v4 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

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>
---
 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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v4 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
                           ` (2 preceding siblings ...)
  2013-03-26 11:05         ` [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-26 11:05         ` Sebastian Götte
  2013-03-26 11:05         ` [PATCH v4 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 builtin/merge.c                    |   2 ++
 commit.c                           |   2 ++
 commit.h                           |   9 +++++----
 gpg-interface.h                    |   2 +-
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 9 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 227040b..e3df36f 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1251,6 +1251,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 					if (verbosity >= 0)
 						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature.signer, signature.key);
 					break;
+				case 'U':
+					die(_("Commit %s has a good GPG signature allegedly by %s, albeit from an untrusted key (fingerprint %s)."), hex, signature.signer, signature.key);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature.signer, signature.key);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index 533727c..785eb51 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,6 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } signature_check[] = {
+	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
+	{ 'U', "[GNUPG:] TRUST_NEVER" },
 	{ 'G', "[GNUPG:] GOODSIG " },
 	{ 'B', "[GNUPG:] BADSIG " },
 };
diff --git a/commit.h b/commit.h
index cf35472..c946135 100644
--- a/commit.h
+++ b/commit.h
@@ -232,10 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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 *sig);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 2a536d9..7fd3021 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -4,7 +4,7 @@
 struct signature {
 	char *gpg_output;
 	char *gpg_status;
-	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char check_result; /* 0 (not checked), N (checked but no further result), U (untrusted, good), G (good) or B (bad) */
 	char *signer;
 	char *key;
 };
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 31b67dd..47a4f80 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	grep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge  commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2> mergeerror &&
+	grep "from an untrusted key" 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
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v4 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
                           ` (3 preceding siblings ...)
  2013-03-26 11:05         ` [PATCH v4 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-26 11:05         ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 11:05 UTC (permalink / raw)
  To: git; +Cc: gitster

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index 3aac5d8..fc74795 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature.check_result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature.check_result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v4 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-26 11:05       ` [PATCH v4 " Sebastian Götte
@ 2013-03-26 16:26         ` Junio C Hamano
  2013-03-26 16:43           ` Sebastian Götte
  0 siblings, 1 reply; 62+ messages in thread
From: Junio C Hamano @ 2013-03-26 16:26 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> On 03/26/2013 02:46 AM, Junio C Hamano wrote:> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
>>> Rebased it onto the current 'master'. The second patch fixes that the GPG
>>> status parser ignores the first line of GPG status output (that would be caught
>>> by the new merge signature verification test case).
>> 
>> Thanks.
>> 
>> Does it still make sure that it won't be fooled by the expected
>> string appearing in the middle of a line, not at the beginning?
>
> I thought that would not be a problem until I noticed it checks for GOODSIG
> before it checks for BADSIG. Here is a fix.

What does the order of checking have to do with it?  I am confused...

I was more worried about a case where you may end up misinterpreting

[GNUPG:] BADSIG B0B5E88696AFE6CB [GNUPG:] GOODSIG B0B5E88696AFE6CB <y@xz>

as showing goodsig when the signer's name was set to "[GNUPG:]
GOODSIG B0B5E88696AFE6CB"

The "\n" in the original was to make sure the expected message is at
the beginning of a line.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v4 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-26 16:26         ` Junio C Hamano
@ 2013-03-26 16:43           ` Sebastian Götte
  0 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-26 16:43 UTC (permalink / raw)
  To: git

On 03/26/2013 05:26 PM, Junio C Hamano wrote:
> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
> 
>> On 03/26/2013 02:46 AM, Junio C Hamano wrote:> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
>>>> Rebased it onto the current 'master'. The second patch fixes that the GPG
>>>> status parser ignores the first line of GPG status output (that would be caught
>>>> by the new merge signature verification test case).
>>>
>>> Thanks.
>>>
>>> Does it still make sure that it won't be fooled by the expected
>>> string appearing in the middle of a line, not at the beginning?
>>
>> I thought that would not be a problem until I noticed it checks for GOODSIG
>> before it checks for BADSIG. Here is a fix.
> 
> What does the order of checking have to do with it?  I am confused...
> 
> I was more worried about a case where you may end up misinterpreting
> 
> [GNUPG:] BADSIG B0B5E88696AFE6CB [GNUPG:] GOODSIG B0B5E88696AFE6CB <y@xz>
> 
> as showing goodsig when the signer's name was set to "[GNUPG:]
> GOODSIG B0B5E88696AFE6CB"
> 
> The "\n" in the original was to make sure the expected message is at
> the beginning of a line.
I was assuming only a malicious user would use a name containing "[GNUPG:] SOMETHING_ALLCAPS". In this case, if the code would check for BADSIG/TRUST_NEVER/TRUST_UNKNOWN messages first, the signature would still be rejected. Of course, in that case a non-malicious user with a name containing "[GNUPG:] BADSIG" etc. would still run into problems.

This 4th version fixes that by checking whether the search string is at the beginning of the status buffer (index 0) or at the beginning of a line (prefixed by '\n').

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
  2013-03-26 11:05         ` [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-28 22:33           ` Junio C Hamano
  0 siblings, 0 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-28 22:33 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
> ---
>  commit.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/commit.c b/commit.c
> index 1aeb17a..533727c 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -1027,8 +1027,8 @@ static struct {
>  	char result;
>  	const char *check;
>  } signature_check[] = {
> -	{ 'G', "\n[GNUPG:] GOODSIG " },
> -	{ 'B', "\n[GNUPG:] BADSIG " },
> +	{ 'G', "[GNUPG:] GOODSIG " },
> +	{ 'B', "[GNUPG:] BADSIG " },
>  };
>  
>  static void parse_signature_lines(struct signature *sig)
> @@ -1041,6 +1041,9 @@ static void parse_signature_lines(struct signature *sig)
>  		const char *next;
>  		if (!found)
>  			continue;
> +		if (found != buf) 
> +			if (found[-1] != '\n')
> +				continue;

It would be much easier to read if it were "unless we are not
looking at the very first byte, the previous byte must be LF", i.e.

	if (found != buf && found[-1] != '\n')

Is that continue correct?  Don't you want to retry from the end of
the line that contains the mistaken hit?

The "\n" at the beginning anchors the expected string for quicker
multi-line scan done with strstr().  If you really want to lose that
LF and still write this function correctly and clearly, I think you
would need to iterate over the buffer line by line.

What you want to do may be more like this, I think.

 commit.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/commit.c b/commit.c
index d093c9c..d6e0b00 100644
--- a/commit.c
+++ b/commit.c
@@ -1045,8 +1045,8 @@ static struct {
 	char result;
 	const char *check;
 } signature_check[] = {
-	{ 'G', "[GNUPG:] GOODSIG " },
-	{ 'B', "[GNUPG:] BADSIG " },
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
 };
 
 static void parse_signature_lines(struct signature *sig)
@@ -1055,15 +1055,18 @@ static void parse_signature_lines(struct signature *sig)
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		if (found != buf)
-			if (found[-1] != '\n')
+		const char *found, *next;
+
+		if (!prefixcmp(buf, signature_check[i].check + 1)) {
+			/* At the very beginning of the buffer */
+			found = buf + strlen(signature_check[i].check + 1);
+		} else {
+			found = strstr(buf, signature_check[i].check);
+			if (!found)
 				continue;
+			found +=  strlen(signature_check[i].check);
+		}
 		sig->check_result = signature_check[i].result;
-		found += strlen(signature_check[i].check);
 		sig->key = xmemdupz(found, 16);
 		found += 17;
 		next = strchrnul(found, '\n');

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged
  2013-03-26 11:05         ` [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-28 22:33           ` Junio C Hamano
  2013-03-30  0:13             ` [PATCH v5 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
  0 siblings, 2 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-28 22:33 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> 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>
> ---
> ...
> +	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;

Even though you may happen to know all other fields are output only,
it is a good practice to clear it with

			memset(&signature, 0, sizeof(signature);

By the way, I think this variable and type should be called more
like "signature_check" or something with "check" in its name, not
"signature".  After all it is _not_ a signature itself.

> +			check_commit_signature(commit, &signature);
> +
> +			char hex[41];

builtin/merge.c: In function 'cmd_merge':
builtin/merge.c:1247: error: ISO C90 forbids mixed declarations and code

> +
> +test_expect_success GPG 'merge unsigned commit with verification' '
> +	test_must_fail git merge --ff-only --verify-signatures side-unsigned 2> mergeerror &&

No SP between redirection and the target filename.

> +	grep "does not have a GPG signature" mergeerror

Do we plan to make this message localized?  If so I think you would
need to do this with test_i18ngrep.

> +'
> +
> +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
> +'

Hmph.

So the caller needs to check both the standard output and the
standard error to get the whole picture?  Is there a reason why we
are not sending everything to the standard output consistently?

> +test_expect_success GPG 'merge commit with bad signature without verification' '
> +	git merge $(cat forged.commit)
> +'

Good to see that negative case where the new feature should _not_
trigger is properly tested.

> +
> +test_done

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v5 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-28 22:33           ` Junio C Hamano
@ 2013-03-30  0:13             ` Sebastian Götte
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
  1 sibling, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:13 UTC (permalink / raw)
  To: git; +Cc: gitster

I hope I did not introduce more problems than I fixed in this revision ;)

On 03/28/2013 11:33 PM, Junio C Hamano wrote:
> It would be much easier to read if it were "unless we are not
> looking at the very first byte, the previous byte must be LF", i.e.
> 
> 	if (found != buf && found[-1] != '\n')
> 
> Is that continue correct?  Don't you want to retry from the end of
> the line that contains the mistaken hit?
Actually it is not. Sorry.
> The "\n" at the beginning anchors the expected string for quicker
> multi-line scan done with strstr().  If you really want to lose that
> LF and still write this function correctly and clearly, I think you
> would need to iterate over the buffer line by line.
The function now does that and calls prefixcmp on each line.

On 03/28/2013 11:33 PM, Junio C Hamano wrote:> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
>> +	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;
> 
> [...] 
> By the way, I think this variable and type should be called more
> like "signature_check" or something with "check" in its name, not
> "signature".  After all it is _not_ a signature itself.
I renamed it "signature_check". I put that into the commit moving the code to
commit.c. I also renamed the array/struct containing the GPG status output
strings since that was originally called "signature_check".

>> +	grep "does not have a GPG signature" mergeerror
> 
> Do we plan to make this message localized?  If so I think you would
> need to do this with test_i18ngrep.
Yes, this message should be localized since it is "normal" status output. I
fixed the test case, however I noticed a possible problem with the "git log
 --show-signature" test case (t/t7510-signed-commit.sh): Here, grep is used on
git output, but this git output is actually just passed-through GPG output, and
GPG localizes that output. Are the tests alwasy run with LANG=en_US.utf-8,
LANG=C etc.?

>> +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
>> +'
> 
> Hmph.
> 
> So the caller needs to check both the standard output and the
> standard error to get the whole picture?  Is there a reason why we
> are not sending everything to the standard output consistently?
If --verify-signatures is given, everything but a good signature results in
"die(_("foo")), with _("foo") being printed on stderr. I clarified that point
in merge-options.txt. If additionally --verbose is given on the command line,
git will print _("Commit %s has a good GPG signature by %s (key fingerprint
%s)\n") on stdout for each "good" commit.  I think it is ok that way because
the caller only needs to check the exit code to get a picture. The "good GPG
signature"-message is only meant to convey the signer's name and key
fingerprint in case the caller is interested. If the caller does not want to
abort the merge in case of GPG trouble, git merge may be called without
 --verify-signatures followed by a git log --show-signature on the commits
that have been merged.

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   5 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  35 +++++++++++++-
 commit.c                           |  67 ++++++++++++++++++++++++++
 commit.h                           |  10 ++++
 git-pull.sh                        |  10 +++-
 gpg-interface.h                    |   8 ++++
 pretty.c                           |  93 ++++++-------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 ++++++++++++++++++++++++
 13 files changed, 210 insertions(+), 82 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v5 1/5] Move commit GPG signature verification to commit.c
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-30  0:14               ` Sebastian Götte
  2013-03-30  3:37                 ` Junio C Hamano
  2013-03-30  0:14               ` [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                                 ` (3 subsequent siblings)
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:14 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++
 commit.h        |  9 ++++++
 gpg-interface.h |  8 +++++
 pretty.c        | 91 +++++++++------------------------------------------------
 4 files changed, 89 insertions(+), 78 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..e94d122 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} sigcheck_gpg_status[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_gpg_output(struct signature_check *sigc)
+{
+	const char *buf = sigc->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
+		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sigc->check_result = sigcheck_gpg_status[i].result;
+		found += strlen(sigcheck_gpg_status[i].check);
+		sigc->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sigc->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void 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;
+
+	sigc->check_result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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->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);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..941f7f3 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..44f70aa 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,14 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature_check {
+	char *gpg_output;
+	char *gpg_status;
+	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..da3c53c 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature_check signature_check;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,27 +1124,27 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature_check.check_result)
+			check_commit_signature(c->commit, &(c->signature_check));
 		switch (placeholder[1]) {
 		case 'G':
-			if (c->signature.gpg_output)
-				strbuf_addstr(sb, c->signature.gpg_output);
+			if (c->signature_check.gpg_output)
+				strbuf_addstr(sb, c->signature_check.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature_check.check_result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature_check.check_result);
 			}
 			break;
 		case 'S':
-			if (c->signature.signer)
-				strbuf_addstr(sb, c->signature.signer);
+			if (c->signature_check.signer)
+				strbuf_addstr(sb, c->signature_check.signer);
 			break;
 		case 'K':
-			if (c->signature.key)
-				strbuf_addstr(sb, c->signature.key);
+			if (c->signature_check.key)
+				strbuf_addstr(sb, c->signature_check.key);
 			break;
 		}
 		return 2;
@@ -1347,8 +1282,8 @@ void format_commit_message(const struct commit *commit,
 	rewrap_message_tail(sb, &context, 0, 0, 0);
 
 	logmsg_free(context.message, commit);
-	free(context.signature.gpg_output);
-	free(context.signature.signer);
+	free(context.signature_check.gpg_output);
+	free(context.signature_check.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-30  0:14               ` [PATCH v5 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-30  0:14               ` Sebastian Götte
  2013-03-30  3:37                 ` Junio C Hamano
  2013-03-30  0:14               ` [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                                 ` (2 subsequent siblings)
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:14 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 34 ++++++++++++++++++++--------------
 1 file changed, 20 insertions(+), 14 deletions(-)

diff --git a/commit.c b/commit.c
index e94d122..48f09e9 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,27 +1027,33 @@ static struct {
 	char result;
 	const char *check;
 } sigcheck_gpg_status[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'G', "[GNUPG:] GOODSIG " },
+	{ 'B', "[GNUPG:] BADSIG " },
 };
 
 static void parse_gpg_output(struct signature_check *sigc)
 {
-	const char *buf = sigc->gpg_status;
 	int i;
 
+	/* Iterate over all search strings */
 	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
-		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		sigc->check_result = sigcheck_gpg_status[i].result;
-		found += strlen(sigcheck_gpg_status[i].check);
-		sigc->key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		sigc->signer = xmemdupz(found, next - found);
-		break;
+		const char *found = sigc->gpg_status;
+
+		/* Iterate over all lines */
+		do {
+			if (!prefixcmp(found, sigcheck_gpg_status[i].check)) {
+				const char *next;
+				
+				found += strlen(sigcheck_gpg_status[i].check);
+				sigc->check_result = sigcheck_gpg_status[i].result;
+				sigc->key = xmemdupz(found, 16);
+				found += 17;
+				next = strchrnul(found, '\n');
+				sigc->signer = xmemdupz(found, next - found);
+				return;
+			}
+			found = strchr(found, '\n')+1;
+		} while(found-1);
 	}
 }
 
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-30  0:14               ` [PATCH v5 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-30  0:14               ` [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-30  0:14               ` Sebastian Götte
  2013-03-30  3:38                 ` Junio C Hamano
  2013-03-30  0:14               ` [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-30  0:15               ` [PATCH v5 " Sebastian Götte
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:14 UTC (permalink / raw)
  To: git; +Cc: gitster

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>
---
 Documentation/merge-options.txt    |  5 ++++
 builtin/merge.c                    | 33 +++++++++++++++++++++++-
 git-pull.sh                        | 10 ++++++--
 t/t7612-merge-verify-signatures.sh | 52 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 97 insertions(+), 3 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 0bcbe0a..31f1067 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -83,6 +83,11 @@ 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 GPG signatures and abort the
+	merge in case they do not.
+
 --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..cb3e9ea 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;
+			char hex[41];
+			struct signature_check signature_check;
+			memset(&signature_check, 0, sizeof(signature_check));
+
+			check_commit_signature(commit, &signature_check);
+
+			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+			switch(signature_check.check_result){
+				case 'G':
+					if (verbosity >= 0)
+						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature_check.signer, signature_check.key);
+					break;
+				case 'B':
+					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature_check.signer, signature_check.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_check.gpg_output);
+			free(signature_check.gpg_status);
+			free(signature_check.signer);
+			free(signature_check.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..6ccfbf3
--- /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 &&
+	test_i18ngrep "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 &&
+	test_i18ngrep "has a bad GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge signed commit with verification' '
+	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
+	test_i18ngrep "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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
                                 ` (2 preceding siblings ...)
  2013-03-30  0:14               ` [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-30  0:14               ` Sebastian Götte
  2013-03-31  8:32                 ` Thomas Rast
  2013-03-30  0:15               ` [PATCH v5 " Sebastian Götte
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:14 UTC (permalink / raw)
  To: git; +Cc: gitster

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/merge-options.txt    |   4 ++--
 builtin/merge.c                    |   2 ++
 commit.c                           |   2 ++
 commit.h                           |   9 +++++----
 gpg-interface.h                    |   2 +-
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 10 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 31f1067..a0f022b 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -85,8 +85,8 @@ option can be used to override --squash.
 
 --verify-signatures::
 --no-verify-signatures::
-	Verify that the commits being merged have good GPG signatures and abort the
-	merge in case they do not.
+	Verify that the commits being merged have good and trusted GPG signatures
+	and abort the merge in case they do not.
 
 --summary::
 --no-summary::
diff --git a/builtin/merge.c b/builtin/merge.c
index cb3e9ea..5b2ece1 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1251,6 +1251,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 					if (verbosity >= 0)
 						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature_check.signer, signature_check.key);
 					break;
+				case 'U':
+					die(_("Commit %s has a good GPG signature allegedly by %s, albeit from an untrusted key (fingerprint %s)."), hex, signature_check.signer, signature_check.key);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature_check.signer, signature_check.key);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index 48f09e9..b132c15 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,6 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } sigcheck_gpg_status[] = {
+	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
+	{ 'U', "[GNUPG:] TRUST_NEVER" },
 	{ 'G', "[GNUPG:] GOODSIG " },
 	{ 'B', "[GNUPG:] BADSIG " },
 };
diff --git a/commit.h b/commit.h
index 941f7f3..27d9b36 100644
--- a/commit.h
+++ b/commit.h
@@ -232,10 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 44f70aa..d2e512d 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -4,7 +4,7 @@
 struct signature_check {
 	char *gpg_output;
 	char *gpg_status;
-	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */
+	char check_result; /* 0 (not checked), N (checked but no further result), U (untrusted, good), G (good) or B (bad) */
 	char *signer;
 	char *key;
 };
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 6ccfbf3..684cd50 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	test_i18ngrep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge  commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+	test_i18ngrep "from an untrusted key" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
 	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
 	test_i18ngrep "has a good GPG signature" mergeoutput
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v5 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
                                 ` (3 preceding siblings ...)
  2013-03-30  0:14               ` [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-30  0:15               ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30  0:15 UTC (permalink / raw)
  To: git; +Cc: gitster

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index da3c53c..bbe521b 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature_check.check_result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature_check.check_result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
  2013-03-30  0:14               ` [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-30  3:37                 ` Junio C Hamano
  0 siblings, 0 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-30  3:37 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

>  static void parse_gpg_output(struct signature_check *sigc)
>  {
> -	const char *buf = sigc->gpg_status;
>  	int i;
>  
> +	/* Iterate over all search strings */
>  	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
> +		const char *found = sigc->gpg_status;
> +
> +		/* Iterate over all lines */
> +		do {
> +			if (!prefixcmp(found, sigcheck_gpg_status[i].check)) {
> +				const char *next;
> +				
> +				found += strlen(sigcheck_gpg_status[i].check);
> +				sigc->check_result = sigcheck_gpg_status[i].result;
> +				sigc->key = xmemdupz(found, 16);
> +				found += 17;
> +				next = strchrnul(found, '\n');
> +				sigc->signer = xmemdupz(found, next - found);
> +				return;
> +			}
> +			found = strchr(found, '\n')+1;
> +		} while(found-1);

Yuck.  That termination condition is horrible.

Honestly speaking, I find the one I suggested the other day (which
has been queued on 'pu') much nicer than this loop.

If you really really want to do a line at a time, discarding the
"allow strstr() to scan over multiple lines" optimization, it is
more natural to iterate over buffer one line at a time, and check
for each expected output with an inner loop, perhaps like this:

	const char *cp = buf;
        while (*cp) {
		for (i = 0; i < ARRAY_SIZE(sig_check); i++) {
	        	if (!prefixcmp(cp, sig_check[i].check) &&
                            parse_gpg_status(sigc, cp, &sig_check[i]))
				return;
		}
                cp = strchrnul(cp, '\n');
                if (*cp)
	                cp++;
	}

But I do not see much point in doing so.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 1/5] Move commit GPG signature verification to commit.c
  2013-03-30  0:14               ` [PATCH v5 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-30  3:37                 ` Junio C Hamano
  0 siblings, 0 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-30  3:37 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> @@ -230,4 +231,12 @@ extern void print_commit_list(struct commit_list *list,
>  			      const char *format_cur,
>  			      const char *format_last);
>  
> +/*
> + * Check the signature of the given commit. The result of the check is stored in
> + * sig->check_result, 'G' for a good signature, 'B' for a bad signature and 'N'
> + * for no signature at all.
> + * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer and sig->key.
> + */

How wide is your terminal?  These lines are a tad wider than our
standard.  We tend to keep function decls in *.h files on a single
long line (primarily to help people who grep them without using
CTAGS/ETAGS), but everywhere else we try to keep most of our lines
comfortably fit on 80-column terminals.

> +extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc);
> +
>  #endif /* COMMIT_H */
> diff --git a/gpg-interface.h b/gpg-interface.h
> index cf99021..44f70aa 100644
> --- a/gpg-interface.h
> +++ b/gpg-interface.h
> @@ -1,6 +1,14 @@
>  #ifndef GPG_INTERFACE_H
>  #define GPG_INTERFACE_H
>  
> +struct signature_check {
> +	char *gpg_output;
> +	char *gpg_status;
> +	char check_result; /* 0 (not checked), N (checked but no further result), G (good) or B (bad) */

Listing the possible values is a good idea, but not on a single
line.

Also now the structure screams with its name that it is about
checking, I do not see a reason for its field to repeat "check".
Calling it "result" (or "result_code") would avoid stuttering when
you use them, e.g.

	struct signature_check signature_check;

	switch (signature_check.check_result) {
        ...

would be less nice than

        switch (signature_check.result) {
	...

no?

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged
  2013-03-30  0:14               ` [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-30  3:38                 ` Junio C Hamano
  2013-03-30 14:14                   ` [PATCH v6 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
  0 siblings, 2 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-03-30  3:38 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> +	OPT_BOOLEAN(0, "verify-signatures", &verify_signatures,
> +		N_("Verify that the named commit has a valid GPG signature")),

Please use OPT_BOOL() in new code.  Verifying existing OPT_BOOLEAN()
can safely converted to OPT_BOOL() and doing so would be a separate
matter and should not be part of this series.

> @@ -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 */

This boolean variable is named clearly enough that you do not need
this comment.

> +		for (p = remoteheads; p; p = p->next) {
> +			struct commit *commit = p->item;
> +			char hex[41];
> +			struct signature_check signature_check;
> +			memset(&signature_check, 0, sizeof(signature_check));
> +
> +			check_commit_signature(commit, &signature_check);
> +
> +			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
> +			switch(signature_check.check_result){
> +				case 'G':
> +					if (verbosity >= 0)
> +						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature_check.signer, signature_check.key);
> +					break;
> +				case 'B':
> +					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature_check.signer, signature_check.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);
> +			}

Style.

        switch (expr) {
	case 'G':
		do_something_for_G();
		break;
		...
	}

Also avoid overlong lines, both in the source, but pay extra
attention to what we show the user.  For example:

    "Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."

The first %s will expand to 40 places, the other two are likely to
be around 20-30 places.

    "Commit %s does not have a good GPG signature. In fact, commit %s does not have a GPG signature at all."

Drop everything from the beginning up to "In fact, ", perhaps:

    "Commit '%s' does not have any GPG signature."

is sufficient?  You may also want to consider

	die(_("Commit '%.*s...' does not have any GPG signature."),
	    8, hex);

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v6 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-30  3:38                 ` Junio C Hamano
@ 2013-03-30 14:14                   ` Sebastian Götte
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
  1 sibling, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:14 UTC (permalink / raw)
  To: git; +Cc: gitster

> Yuck.  That termination condition is horrible.
Ok, I reverted it to your suggestion. In this case, a much more elegant
termination condition (foo == 1) is not possible without casts because foo is a
pointer. 

>> +			switch(signature_check.check_result){
>> +				case 'G':
>> +					if (verbosity >= 0)
>> +						printf(_("Commit %s has a good GPG signature by %s (key fingerprint %s)\n"), hex, signature_check.signer, signature_check.key);
>> +					break;
>> +				case 'B':
>> +					die(_("Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."), hex, signature_check.signer, signature_check.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);
>> +			}
> 
> Style.
I moved that verbose printf outside the switch(foo).

> Also avoid overlong lines, both in the source, but pay extra
> attention to what we show the user.  For example:
> 
>     "Commit %s has a bad GPG signature allegedly by %s (key fingerprint %s)."
> 
> The first %s will expand to 40 places, the other two are likely to
> be around 20-30 places.
The first %s is the output of find_unique_abbrev, so though it will be 40
places worst-case, it will usually be *much* shorter (more like 7-8 places).
>     "Commit %s does not have a good GPG signature. In fact, commit %s does not have a GPG signature at all."
> 
> Drop everything from the beginning up to "In fact, ", perhaps:
> 
>     "Commit '%s' does not have any GPG signature."
> 
> is sufficient?  You may also want to consider
> 
> 	die(_("Commit '%.*s...' does not have any GPG signature."),
> 	    8, hex);
I shortened these messages and removed the key fingerprint output. Anybody
interested can use git log --show-signature to get these.

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   5 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  34 +++++++++++++-
 commit.c                           |  68 +++++++++++++++++++++++++++
 commit.h                           |  10 ++++
 git-pull.sh                        |  10 +++-
 gpg-interface.h                    |  12 +++++
 pretty.c                           |  93 ++++++-------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 ++++++++++++++++++++++++
 13 files changed, 214 insertions(+), 82 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v6 1/5] Move commit GPG signature verification to commit.c
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-30 14:15                     ` Sebastian Götte
  2013-03-30 14:15                     ` [PATCH v6 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                                       ` (3 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:15 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++
 commit.h        | 10 +++++++
 gpg-interface.h | 11 +++++++
 pretty.c        | 91 +++++++++------------------------------------------------
 4 files changed, 93 insertions(+), 78 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..eb645af 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} sigcheck_gpg_status[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_gpg_output(struct signature_check *sigc)
+{
+	const char *buf = sigc->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
+		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sigc->result = sigcheck_gpg_status[i].result;
+		found += strlen(sigcheck_gpg_status[i].check);
+		sigc->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sigc->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void 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;
+
+	sigc->result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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->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);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..8bbcf13 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,13 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..5884aa4 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,17 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature_check {
+	char *gpg_output;
+	char *gpg_status;
+	char result; /* 0 (not checked),
+		      * N (checked but no further result),
+		      * G (good)
+		      * B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..cf84d3a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature_check signature_check;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,27 +1124,27 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature_check.result)
+			check_commit_signature(c->commit, &(c->signature_check));
 		switch (placeholder[1]) {
 		case 'G':
-			if (c->signature.gpg_output)
-				strbuf_addstr(sb, c->signature.gpg_output);
+			if (c->signature_check.gpg_output)
+				strbuf_addstr(sb, c->signature_check.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
 		case 'S':
-			if (c->signature.signer)
-				strbuf_addstr(sb, c->signature.signer);
+			if (c->signature_check.signer)
+				strbuf_addstr(sb, c->signature_check.signer);
 			break;
 		case 'K':
-			if (c->signature.key)
-				strbuf_addstr(sb, c->signature.key);
+			if (c->signature_check.key)
+				strbuf_addstr(sb, c->signature_check.key);
 			break;
 		}
 		return 2;
@@ -1347,8 +1282,8 @@ void format_commit_message(const struct commit *commit,
 	rewrap_message_tail(sb, &context, 0, 0, 0);
 
 	logmsg_free(context.message, commit);
-	free(context.signature.gpg_output);
-	free(context.signature.signer);
+	free(context.signature_check.gpg_output);
+	free(context.signature_check.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v6 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-30 14:15                     ` [PATCH v6 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-30 14:15                     ` Sebastian Götte
  2013-03-30 14:15                     ` [PATCH v6 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                                       ` (2 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:15 UTC (permalink / raw)
  To: git; +Cc: gitster

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index eb645af..ca11919 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,8 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } sigcheck_gpg_status[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'G', "[GNUPG:] GOODSIG " },
+	{ 'B', "[GNUPG:] BADSIG " },
 };
 
 static void parse_gpg_output(struct signature_check *sigc)
@@ -1036,13 +1036,20 @@ static void parse_gpg_output(struct signature_check *sigc)
 	const char *buf = sigc->gpg_status;
 	int i;
 
+	/* Iterate over all search strings */
 	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
-		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
-		const char *next;
-		if (!found)
-			continue;
+		const char *found, *next;
+
+		if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
+			/* At the very beginning of the buffer */
+			found = buf + strlen(sigcheck_gpg_status[i].check + 1);
+		} else {
+			found = strstr(buf, sigcheck_gpg_status[i].check);
+			if (!found)
+ 				continue;
+			found +=  strlen(sigcheck_gpg_status[i].check);
+		}
 		sigc->result = sigcheck_gpg_status[i].result;
-		found += strlen(sigcheck_gpg_status[i].check);
 		sigc->key = xmemdupz(found, 16);
 		found += 17;
 		next = strchrnul(found, '\n');
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v6 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-30 14:15                     ` [PATCH v6 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-30 14:15                     ` [PATCH v6 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-30 14:15                     ` Sebastian Götte
  2013-03-30 14:16                     ` [PATCH v6 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-30 14:16                     ` [PATCH v6 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:15 UTC (permalink / raw)
  To: git; +Cc: gitster

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>
---
 Documentation/merge-options.txt    |  5 ++++
 builtin/merge.c                    | 32 ++++++++++++++++++++++-
 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..31f1067 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -83,6 +83,11 @@ 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 GPG signatures and abort the
+	merge in case they do not.
+
 --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..7a33d03 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_BOOL(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,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_merge_usage,
 			builtin_merge_options);
 
+	if (verify_signatures) {
+		for (p = remoteheads; p; p = p->next) {
+			struct commit *commit = p->item;
+			char hex[41];
+			struct signature_check signature_check;
+			memset(&signature_check, 0, sizeof(signature_check));
+
+			check_commit_signature(commit, &signature_check);
+
+			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+			switch(signature_check.result){
+				case 'G':
+					break;
+				case 'B':
+					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
+				default: /* 'N' */
+					die(_("Commit %s does not have a GPG signature."), hex, hex);
+			}
+			if (verbosity >= 0 && signature_check.result == 'G')
+				printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer);
+
+			free(signature_check.gpg_output);
+			free(signature_check.gpg_status);
+			free(signature_check.signer);
+			free(signature_check.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..6ccfbf3
--- /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 &&
+	test_i18ngrep "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 &&
+	test_i18ngrep "has a bad GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge signed commit with verification' '
+	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
+	test_i18ngrep "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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v6 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
                                       ` (2 preceding siblings ...)
  2013-03-30 14:15                     ` [PATCH v6 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-30 14:16                     ` Sebastian Götte
  2013-03-30 14:16                     ` [PATCH v6 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:16 UTC (permalink / raw)
  To: git; +Cc: gitster

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/merge-options.txt    |   4 ++--
 builtin/merge.c                    |   2 ++
 commit.c                           |   2 ++
 commit.h                           |  10 +++++-----
 gpg-interface.h                    |   1 +
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 10 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 31f1067..a0f022b 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -85,8 +85,8 @@ option can be used to override --squash.
 
 --verify-signatures::
 --no-verify-signatures::
-	Verify that the commits being merged have good GPG signatures and abort the
-	merge in case they do not.
+	Verify that the commits being merged have good and trusted GPG signatures
+	and abort the merge in case they do not.
 
 --summary::
 --no-summary::
diff --git a/builtin/merge.c b/builtin/merge.c
index 7a33d03..752e3a9 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1248,6 +1248,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			switch(signature_check.result){
 				case 'G':
 					break;
+				case 'U':
+					die(_("Commit %s has a good, untrusted GPG signature allegedly by %s."), hex, signature_check.signer);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index ca11919..0a49e2b 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,6 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } sigcheck_gpg_status[] = {
+	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
+	{ 'U', "[GNUPG:] TRUST_NEVER" },
 	{ 'G', "[GNUPG:] GOODSIG " },
 	{ 'B', "[GNUPG:] BADSIG " },
 };
diff --git a/commit.h b/commit.h
index 8bbcf13..27d9b36 100644
--- a/commit.h
+++ b/commit.h
@@ -232,11 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer
- * and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 5884aa4..a85cb5b 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -6,6 +6,7 @@ struct signature_check {
 	char *gpg_status;
 	char result; /* 0 (not checked),
 		      * N (checked but no further result),
+		      * U (untrusted good),
 		      * G (good)
 		      * B (bad) */
 	char *signer;
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 6ccfbf3..94039a7 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	test_i18ngrep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge  commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+	test_i18ngrep "has a good, untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
 	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
 	test_i18ngrep "has a good GPG signature" mergeoutput
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v6 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
                                       ` (3 preceding siblings ...)
  2013-03-30 14:16                     ` [PATCH v6 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-30 14:16                     ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-30 14:16 UTC (permalink / raw)
  To: git; +Cc: gitster

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index cf84d3a..840c41f 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-30  0:14               ` [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-31  8:32                 ` Thomas Rast
  2013-03-31 10:55                   ` Sebastian Götte
  0 siblings, 1 reply; 62+ messages in thread
From: Thomas Rast @ 2013-03-31  8:32 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git, gitster

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> When --verify-signatures is specified, abort the merge in case a good
> GPG signature from an untrusted key is encountered.
[...]
> +test_expect_success GPG 'merge  commit with untrusted signature with verification' '
                                  ^
                                  `------------.
Nit: you have a pointless(?) double space here-´

> +	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
> +	test_i18ngrep "from an untrusted key" mergeerror
> +'

This test gives me the following:

==26527== Conditional jump or move depends on uninitialised value(s)
==26527==    at 0x4C2D8BC: strchrnul (mc_replace_strmem.c:1084)
==26527==    by 0x4989CC: parse_signature_lines (commit.c:1074)
==26527==    by 0x498B33: check_commit_signature (commit.c:1100)
==26527==    by 0x453719: cmd_merge (merge.c:1246)
==26527==    by 0x4057B6: run_builtin (git.c:282)
==26527==    by 0x405949: handle_internal_command (git.c:444)
==26527==    by 0x405A63: run_argv (git.c:490)
==26527==    by 0x405BF2: main (git.c:565)

though I currently cannot see what's wrong, probably because I don't
know the format that parse_signature_lines gives.  Can you look into it?

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31  8:32                 ` Thomas Rast
@ 2013-03-31 10:55                   ` Sebastian Götte
  2013-03-31 11:38                     ` Thomas Rast
  0 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 10:55 UTC (permalink / raw)
  To: Thomas Rast, git

On 03/31/2013 10:32 AM, Thomas Rast wrote:
> Sebastian Götte <jaseg@physik.tu-berlin.de> writes:
> 
>> When --verify-signatures is specified, abort the merge in case a good
>> GPG signature from an untrusted key is encountered.
> [...]
>> +test_expect_success GPG 'merge  commit with untrusted signature with verification' '
>                                   ^
>                                   `------------.
> Nit: you have a pointless(?) double space here-´
Will fix that in the next revision ;)

>> +	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
>> +	test_i18ngrep "from an untrusted key" mergeerror
>> +'
> 
> This test gives me the following:
> 
> ==26527== Conditional jump or move depends on uninitialised value(s)
> ==26527==    at 0x4C2D8BC: strchrnul (mc_replace_strmem.c:1084)
> ==26527==    by 0x4989CC: parse_signature_lines (commit.c:1074)
> ==26527==    by 0x498B33: check_commit_signature (commit.c:1100)
> ==26527==    by 0x453719: cmd_merge (merge.c:1246)
> ==26527==    by 0x4057B6: run_builtin (git.c:282)
> ==26527==    by 0x405949: handle_internal_command (git.c:444)
> ==26527==    by 0x405A63: run_argv (git.c:490)
> ==26527==    by 0x405BF2: main (git.c:565)
> 
> though I currently cannot see what's wrong, probably because I don't
> know the format that parse_signature_lines gives.  Can you look into it?
Against what version/combination of the patches are you running the test?
parse_signature_lines is called parse_gpg_output in v5.  Perhaps just try again
with v6 of the patch, the difference between v5 and v6 is parse_gpg_output
(Junio did not like the v5 variant).

Thanks
Sebastian 

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 10:55                   ` Sebastian Götte
@ 2013-03-31 11:38                     ` Thomas Rast
  2013-03-31 11:57                       ` Sebastian Götte
  0 siblings, 1 reply; 62+ messages in thread
From: Thomas Rast @ 2013-03-31 11:38 UTC (permalink / raw)
  To: Sebastian Götte, git

"Sebastian Götte" <jaseg@physik.tu-berlin.de> wrote:

>On 03/31/2013 10:32 AM, Thomas Rast wrote:
>>> +	test_must_fail git merge --ff-only --verify-signatures
>side-untrusted 2>mergeerror &&
>>> +	test_i18ngrep "from an untrusted key" mergeerror
>>> +'
>> 
>> This test gives me the following:
>> 
>> ==26527== Conditional jump or move depends on uninitialised value(s)
>> ==26527==    at 0x4C2D8BC: strchrnul (mc_replace_strmem.c:1084)
>> ==26527==    by 0x4989CC: parse_signature_lines (commit.c:1074)
>> ==26527==    by 0x498B33: check_commit_signature (commit.c:1100)
>> ==26527==    by 0x453719: cmd_merge (merge.c:1246)
>> ==26527==    by 0x4057B6: run_builtin (git.c:282)
>> ==26527==    by 0x405949: handle_internal_command (git.c:444)
>> ==26527==    by 0x405A63: run_argv (git.c:490)
>> ==26527==    by 0x405BF2: main (git.c:565)
>> 
>> though I currently cannot see what's wrong, probably because I don't
>> know the format that parse_signature_lines gives.  Can you look into
>it?
>Against what version/combination of the patches are you running the
>test?
>parse_signature_lines is called parse_gpg_output in v5.  Perhaps just
>try again
>with v6 of the patch, the difference between v5 and v6 is
>parse_gpg_output
>(Junio did not like the v5 variant).

Oh, my bad then. I used the version in pu. I just ran all tests under valgrind and this cropped up.

If you have valgrind installed locally, you can also test yourself ;-) just pass --valgrind to the test script.

-- 
http://code.google.com/p/k9mail

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 11:38                     ` Thomas Rast
@ 2013-03-31 11:57                       ` Sebastian Götte
  2013-03-31 12:16                         ` Thomas Rast
  0 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 11:57 UTC (permalink / raw)
  To: Thomas Rast, git

On 03/31/2013 01:38 PM, Thomas Rast wrote:
> "Sebastian Götte" <jaseg@physik.tu-berlin.de> wrote:
> 
>> On 03/31/2013 10:32 AM, Thomas Rast wrote:
>>>> +	test_must_fail git merge --ff-only --verify-signatures
>> side-untrusted 2>mergeerror &&
>>>> +	test_i18ngrep "from an untrusted key" mergeerror
>>>> +'
>>>
>>> This test gives me the following:
>>>
>>> ==26527== Conditional jump or move depends on uninitialised value(s)
>>> ==26527==    at 0x4C2D8BC: strchrnul (mc_replace_strmem.c:1084)
>>> ==26527==    by 0x4989CC: parse_signature_lines (commit.c:1074)
>>> ==26527==    by 0x498B33: check_commit_signature (commit.c:1100)
>>> ==26527==    by 0x453719: cmd_merge (merge.c:1246)
>>> ==26527==    by 0x4057B6: run_builtin (git.c:282)
>>> ==26527==    by 0x405949: handle_internal_command (git.c:444)
>>> ==26527==    by 0x405A63: run_argv (git.c:490)
>>> ==26527==    by 0x405BF2: main (git.c:565)
> [...]
> 
> If you have valgrind installed locally, you can also test yourself ;-) just pass --valgrind to the test script.
Ok, I can reproduce this with v6 of the patch:

expecting success: 
        test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
        test_i18ngrep "has a good, untrusted GPG signature" mergeerror

==1430== Conditional jump or move depends on uninitialised value(s)
==1430==    at 0x4C26B5C: strchrnul (mc_replace_strmem.c:711)
==1430==    by 0x47B90B: check_commit_signature (commit.c:1057)
==1430==    by 0x444212: cmd_merge (merge.c:1245)
==1430==    by 0x4050E6: handle_internal_command (git.c:281)
==1430==    by 0x40530C: main (git.c:489)

Though I also can't see the problem. strchrnul gets passed a char* that is just fine.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 11:57                       ` Sebastian Götte
@ 2013-03-31 12:16                         ` Thomas Rast
  2013-03-31 12:27                           ` Sebastian Götte
  0 siblings, 1 reply; 62+ messages in thread
From: Thomas Rast @ 2013-03-31 12:16 UTC (permalink / raw)
  To: Sebastian Götte, git

"Sebastian Götte" <jaseg@physik.tu-berlin.de> wrote:

>expecting success: 
>test_must_fail git merge --ff-only --verify-signatures side-untrusted
>2>mergeerror &&
>        test_i18ngrep "has a good, untrusted GPG signature" mergeerror
>
>==1430== Conditional jump or move depends on uninitialised value(s)
>==1430==    at 0x4C26B5C: strchrnul (mc_replace_strmem.c:711)
>==1430==    by 0x47B90B: check_commit_signature (commit.c:1057)
>==1430==    by 0x444212: cmd_merge (merge.c:1245)
>==1430==    by 0x4050E6: handle_internal_command (git.c:281)
>==1430==    by 0x40530C: main (git.c:489)
>
>Though I also can't see the problem. strchrnul gets passed a char* that
>is just fine.

Usually that means that the string *contents* are uninitialized, usually because you scanned past the end of the string...
-- 
http://code.google.com/p/k9mail

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 12:16                         ` Thomas Rast
@ 2013-03-31 12:27                           ` Sebastian Götte
  2013-03-31 13:33                             ` John Keeping
  0 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 12:27 UTC (permalink / raw)
  To: Thomas Rast, git

On 03/31/2013 02:16 PM, Thomas Rast wrote:
> "Sebastian Götte" <jaseg@physik.tu-berlin.de> wrote:
> 
>> expecting success: 
>> test_must_fail git merge --ff-only --verify-signatures side-untrusted
>> 2>mergeerror &&
>>        test_i18ngrep "has a good, untrusted GPG signature" mergeerror
>>
>> ==1430== Conditional jump or move depends on uninitialised value(s)
>> ==1430==    at 0x4C26B5C: strchrnul (mc_replace_strmem.c:711)
>> ==1430==    by 0x47B90B: check_commit_signature (commit.c:1057)
>> ==1430==    by 0x444212: cmd_merge (merge.c:1245)
>> ==1430==    by 0x4050E6: handle_internal_command (git.c:281)
>> ==1430==    by 0x40530C: main (git.c:489)
>>
>> Though I also can't see the problem. strchrnul gets passed a char* that
>> is just fine.
> 
> Usually that means that the string *contents* are uninitialized, usually because you scanned past the end of the string...
I checked for that, everything looks fine to me. The pointer should point to a valid, 0-terminated string.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 12:27                           ` Sebastian Götte
@ 2013-03-31 13:33                             ` John Keeping
  2013-03-31 14:32                               ` [PATCH v7 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
  0 siblings, 2 replies; 62+ messages in thread
From: John Keeping @ 2013-03-31 13:33 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: Thomas Rast, git

On Sun, Mar 31, 2013 at 02:27:20PM +0200, Sebastian Götte wrote:
> On 03/31/2013 02:16 PM, Thomas Rast wrote:
> > "Sebastian Götte" <jaseg@physik.tu-berlin.de> wrote:
> > 
> >> expecting success: 
> >> test_must_fail git merge --ff-only --verify-signatures side-untrusted
> >> 2>mergeerror &&
> >>        test_i18ngrep "has a good, untrusted GPG signature" mergeerror
> >>
> >> ==1430== Conditional jump or move depends on uninitialised value(s)
> >> ==1430==    at 0x4C26B5C: strchrnul (mc_replace_strmem.c:711)
> >> ==1430==    by 0x47B90B: check_commit_signature (commit.c:1057)
> >> ==1430==    by 0x444212: cmd_merge (merge.c:1245)
> >> ==1430==    by 0x4050E6: handle_internal_command (git.c:281)
> >> ==1430==    by 0x40530C: main (git.c:489)
> >>
> >> Though I also can't see the problem. strchrnul gets passed a char* that
> >> is just fine.
> > 
> > Usually that means that the string *contents* are uninitialized,
> > usually because you scanned past the end of the string...
>
> I checked for that, everything looks fine to me. The pointer should point to a valid, 0-terminated string.

It looks like the "found" pointer has wandered off the end of the
string.  In the test case here, the gpg_status is:

-- >8 --
[GNUPG:] SIG_ID rzX3GbdzQyxB4Jdm1uD0CzL4B4Y 2013-03-31 1364735152
[GNUPG:] GOODSIG 61092E85B7227189 Eris Discordia <discord@example.net>
[GNUPG:] VALIDSIG D4BE22311AD3131E5EDA29A461092E85B7227189 2013-03-31
1364735152 0 4 0 1 2 00 D4BE22311AD3131E5EDA29A461092E85B7227189
[GNUPG:] TRUST_UNDEFINED
-- 8< --

But the parse_signature_lines code assumes that after reading a
signature it can fill in the key from the next 16 bytes and then look
for a newline after that.  In this case it clearly needs to only read
the signature if it's a GOODSIG or BADSIG line.

Wrapping a "signature_check[i].result != 'U'" condition around the lines
that extract the key and advance the "found" pointer after doing so
fixes this for me.


John

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v7 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-31 13:33                             ` John Keeping
@ 2013-03-31 14:32                               ` Sebastian Götte
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
  1 sibling, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:32 UTC (permalink / raw)
  To: git; +Cc: trast, john, Junio C Hamano

On 03/31/2013 03:33 PM, John Keeping wrote:
> It looks like the "found" pointer has wandered off the end of the
> string.  In the test case here, the gpg_status is:
> 
> -- >8 --
> [GNUPG:] SIG_ID rzX3GbdzQyxB4Jdm1uD0CzL4B4Y 2013-03-31 1364735152
> [GNUPG:] GOODSIG 61092E85B7227189 Eris Discordia <discord@example.net>
> [GNUPG:] VALIDSIG D4BE22311AD3131E5EDA29A461092E85B7227189 2013-03-31
> 1364735152 0 4 0 1 2 00 D4BE22311AD3131E5EDA29A461092E85B7227189
> [GNUPG:] TRUST_UNDEFINED
> -- 8< --
> 
> But the parse_signature_lines code assumes that after reading a
> signature it can fill in the key from the next 16 bytes and then look
> for a newline after that.  In this case it clearly needs to only read
> the signature if it's a GOODSIG or BADSIG line.
> 
> Wrapping a "signature_check[i].result != 'U'" condition around the lines
> that extract the key and advance the "found" pointer after doing so
> fixes this for me.
This was in fact the case and your fix works. I modified the code a bit so it
does not break at the end of the loop and it checks for untrusted signatures
*last*, this way even in case 'signature_check.result' is 'U' (untrusted),
'key' and 'signer' are available.

I also removed two stray spaces.

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   5 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  34 +++++++++++++-
 commit.c                           |  69 +++++++++++++++++++++++++++
 commit.h                           |  10 ++++
 git-pull.sh                        |  10 +++-
 gpg-interface.h                    |  12 +++++
 pretty.c                           |  93 ++++++-------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 ++++++++++++++++++++++++
 13 files changed, 215 insertions(+), 82 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v7 1/5] Move commit GPG signature verification to commit.c
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-31 14:32                                 ` Sebastian Götte
  2013-03-31 14:32                                 ` [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                                                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, trast, john


Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++
 commit.h        | 10 +++++++
 gpg-interface.h | 11 +++++++
 pretty.c        | 91 +++++++++------------------------------------------------
 4 files changed, 93 insertions(+), 78 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..eb645af 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} sigcheck_gpg_status[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_gpg_output(struct signature_check *sigc)
+{
+	const char *buf = sigc->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
+		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sigc->result = sigcheck_gpg_status[i].result;
+		found += strlen(sigcheck_gpg_status[i].check);
+		sigc->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sigc->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void 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;
+
+	sigc->result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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->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);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..8bbcf13 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,13 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..5884aa4 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,17 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature_check {
+	char *gpg_output;
+	char *gpg_status;
+	char result; /* 0 (not checked),
+		      * N (checked but no further result),
+		      * G (good)
+		      * B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..cf84d3a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature_check signature_check;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,27 +1124,27 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature_check.result)
+			check_commit_signature(c->commit, &(c->signature_check));
 		switch (placeholder[1]) {
 		case 'G':
-			if (c->signature.gpg_output)
-				strbuf_addstr(sb, c->signature.gpg_output);
+			if (c->signature_check.gpg_output)
+				strbuf_addstr(sb, c->signature_check.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
 		case 'S':
-			if (c->signature.signer)
-				strbuf_addstr(sb, c->signature.signer);
+			if (c->signature_check.signer)
+				strbuf_addstr(sb, c->signature_check.signer);
 			break;
 		case 'K':
-			if (c->signature.key)
-				strbuf_addstr(sb, c->signature.key);
+			if (c->signature_check.key)
+				strbuf_addstr(sb, c->signature_check.key);
 			break;
 		}
 		return 2;
@@ -1347,8 +1282,8 @@ void format_commit_message(const struct commit *commit,
 	rewrap_message_tail(sb, &context, 0, 0, 0);
 
 	logmsg_free(context.message, commit);
-	free(context.signature.gpg_output);
-	free(context.signature.signer);
+	free(context.signature_check.gpg_output);
+	free(context.signature_check.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-31 14:32                                 ` [PATCH v7 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-31 14:32                                 ` Sebastian Götte
  2013-03-31 14:41                                   ` John Keeping
  2013-03-31 14:33                                 ` [PATCH v7 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                                                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, trast, john


Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index eb645af..eda7f90 100644
--- a/commit.c
+++ b/commit.c
@@ -1027,8 +1027,8 @@ static struct {
 	char result;
 	const char *check;
 } sigcheck_gpg_status[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'G', "[GNUPG:] GOODSIG " },
+	{ 'B', "[GNUPG:] BADSIG " },
 };
 
 static void parse_gpg_output(struct signature_check *sigc)
@@ -1036,13 +1036,20 @@ static void parse_gpg_output(struct signature_check *sigc)
 	const char *buf = sigc->gpg_status;
 	int i;
 
+	/* Iterate over all search strings */
 	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
-		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
-		const char *next;
-		if (!found)
-			continue;
+		const char *found, *next;
+
+		if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
+			/* At the very beginning of the buffer */
+			found = buf + strlen(sigcheck_gpg_status[i].check + 1);
+		} else {
+			found = strstr(buf, sigcheck_gpg_status[i].check);
+			if (!found)
+ 				continue;
+			found += strlen(sigcheck_gpg_status[i].check);
+		}
 		sigc->result = sigcheck_gpg_status[i].result;
-		found += strlen(sigcheck_gpg_status[i].check);
 		sigc->key = xmemdupz(found, 16);
 		found += 17;
 		next = strchrnul(found, '\n');
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v7 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-31 14:32                                 ` [PATCH v7 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-31 14:32                                 ` [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-31 14:33                                 ` Sebastian Götte
  2013-03-31 14:33                                 ` [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-31 14:34                                 ` [PATCH v7 " Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:33 UTC (permalink / raw)
  To: git; +Cc: gitster, trast, john

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>
---
 Documentation/merge-options.txt    |  5 ++++
 builtin/merge.c                    | 32 ++++++++++++++++++++++-
 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..31f1067 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -83,6 +83,11 @@ 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 GPG signatures and abort the
+	merge in case they do not.
+
 --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..7a33d03 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_BOOL(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,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_merge_usage,
 			builtin_merge_options);
 
+	if (verify_signatures) {
+		for (p = remoteheads; p; p = p->next) {
+			struct commit *commit = p->item;
+			char hex[41];
+			struct signature_check signature_check;
+			memset(&signature_check, 0, sizeof(signature_check));
+
+			check_commit_signature(commit, &signature_check);
+
+			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+			switch(signature_check.result){
+				case 'G':
+					break;
+				case 'B':
+					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
+				default: /* 'N' */
+					die(_("Commit %s does not have a GPG signature."), hex, hex);
+			}
+			if (verbosity >= 0 && signature_check.result == 'G')
+				printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer);
+
+			free(signature_check.gpg_output);
+			free(signature_check.gpg_status);
+			free(signature_check.signer);
+			free(signature_check.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..6ccfbf3
--- /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 &&
+	test_i18ngrep "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 &&
+	test_i18ngrep "has a bad GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge signed commit with verification' '
+	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
+	test_i18ngrep "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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
                                                   ` (2 preceding siblings ...)
  2013-03-31 14:33                                 ` [PATCH v7 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-31 14:33                                 ` Sebastian Götte
  2013-03-31 14:44                                   ` John Keeping
  2013-03-31 14:34                                 ` [PATCH v7 " Sebastian Götte
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:33 UTC (permalink / raw)
  To: git; +Cc: gitster, trast, john

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/merge-options.txt    |   4 ++--
 builtin/merge.c                    |   2 ++
 commit.c                           |  13 ++++++++-----
 commit.h                           |  10 +++++-----
 gpg-interface.h                    |   1 +
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 10 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 31f1067..a0f022b 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -85,8 +85,8 @@ option can be used to override --squash.
 
 --verify-signatures::
 --no-verify-signatures::
-	Verify that the commits being merged have good GPG signatures and abort the
-	merge in case they do not.
+	Verify that the commits being merged have good and trusted GPG signatures
+	and abort the merge in case they do not.
 
 --summary::
 --no-summary::
diff --git a/builtin/merge.c b/builtin/merge.c
index 7a33d03..752e3a9 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1248,6 +1248,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			switch(signature_check.result){
 				case 'G':
 					break;
+				case 'U':
+					die(_("Commit %s has a good, untrusted GPG signature allegedly by %s."), hex, signature_check.signer);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index eda7f90..bb2d9ad 100644
--- a/commit.c
+++ b/commit.c
@@ -1029,6 +1029,8 @@ static struct {
 } sigcheck_gpg_status[] = {
 	{ 'G', "[GNUPG:] GOODSIG " },
 	{ 'B', "[GNUPG:] BADSIG " },
+	{ 'U', "[GNUPG:] TRUST_NEVER" },
+	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
 };
 
 static void parse_gpg_output(struct signature_check *sigc)
@@ -1050,11 +1052,12 @@ static void parse_gpg_output(struct signature_check *sigc)
 			found += strlen(sigcheck_gpg_status[i].check);
 		}
 		sigc->result = sigcheck_gpg_status[i].result;
-		sigc->key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		sigc->signer = xmemdupz(found, next - found);
-		break;
+		if (sigc->result != 'U') {
+			sigc->key = xmemdupz(found, 16);
+			found += 17;
+			next = strchrnul(found, '\n');
+			sigc->signer = xmemdupz(found, next - found);
+		}
 	}
 }
 
diff --git a/commit.h b/commit.h
index 8bbcf13..27d9b36 100644
--- a/commit.h
+++ b/commit.h
@@ -232,11 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer
- * and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 5884aa4..a85cb5b 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -6,6 +6,7 @@ struct signature_check {
 	char *gpg_status;
 	char result; /* 0 (not checked),
 		      * N (checked but no further result),
+		      * U (untrusted good),
 		      * G (good)
 		      * B (bad) */
 	char *signer;
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 6ccfbf3..20b3cfb 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	test_i18ngrep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+	test_i18ngrep "has a good, untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
 	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
 	test_i18ngrep "has a good GPG signature" mergeoutput
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v7 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
                                                   ` (3 preceding siblings ...)
  2013-03-31 14:33                                 ` [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-31 14:34                                 ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 14:34 UTC (permalink / raw)
  To: git; +Cc: gitster, trast, john

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index cf84d3a..840c41f 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
  2013-03-31 14:32                                 ` [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-31 14:41                                   ` John Keeping
  0 siblings, 0 replies; 62+ messages in thread
From: John Keeping @ 2013-03-31 14:41 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git, gitster, trast

On Sun, Mar 31, 2013 at 04:32:52PM +0200, Sebastian Götte wrote:
> 
> Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
> ---
>  commit.c | 21 ++++++++++++++-------
>  1 file changed, 14 insertions(+), 7 deletions(-)
> 
> diff --git a/commit.c b/commit.c
> index eb645af..eda7f90 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -1027,8 +1027,8 @@ static struct {
>  	char result;
>  	const char *check;
>  } sigcheck_gpg_status[] = {
> -	{ 'G', "\n[GNUPG:] GOODSIG " },
> -	{ 'B', "\n[GNUPG:] BADSIG " },
> +	{ 'G', "[GNUPG:] GOODSIG " },
> +	{ 'B', "[GNUPG:] BADSIG " },
>  };
>  
>  static void parse_gpg_output(struct signature_check *sigc)
> @@ -1036,13 +1036,20 @@ static void parse_gpg_output(struct signature_check *sigc)
>  	const char *buf = sigc->gpg_status;
>  	int i;
>  
> +	/* Iterate over all search strings */
>  	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
> -		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
> -		const char *next;
> -		if (!found)
> -			continue;
> +		const char *found, *next;
> +
> +		if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
> +			/* At the very beginning of the buffer */

This seems wrong.  You're losing the "\n" in front of the status strings
above but adding a special first line check skipping the first
character.  Surely it should be one of these changes or the other, not
both?

> +			found = buf + strlen(sigcheck_gpg_status[i].check + 1);
> +		} else {
> +			found = strstr(buf, sigcheck_gpg_status[i].check);
> +			if (!found)
> + 				continue;
> +			found += strlen(sigcheck_gpg_status[i].check);
> +		}
>  		sigc->result = sigcheck_gpg_status[i].result;
> -		found += strlen(sigcheck_gpg_status[i].check);
>  		sigc->key = xmemdupz(found, 16);
>  		found += 17;
>  		next = strchrnul(found, '\n');
> -- 
> 1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 14:33                                 ` [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-31 14:44                                   ` John Keeping
  2013-03-31 15:03                                     ` Thomas Rast
                                                       ` (2 more replies)
  0 siblings, 3 replies; 62+ messages in thread
From: John Keeping @ 2013-03-31 14:44 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git, gitster, trast

On Sun, Mar 31, 2013 at 04:33:57PM +0200, Sebastian Götte wrote:
> When --verify-signatures is specified, abort the merge in case a good
> GPG signature from an untrusted key is encountered.
> 
> Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
> ---
>  Documentation/merge-options.txt    |   4 ++--
>  builtin/merge.c                    |   2 ++
>  commit.c                           |  13 ++++++++-----
>  commit.h                           |  10 +++++-----
>  gpg-interface.h                    |   1 +
>  t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
>  t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
>  t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
>  t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
>  t/t7612-merge-verify-signatures.sh |   9 +++++++++
>  10 files changed, 27 insertions(+), 12 deletions(-)
> 
> diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
> index 31f1067..a0f022b 100644
> --- a/Documentation/merge-options.txt
> +++ b/Documentation/merge-options.txt
> @@ -85,8 +85,8 @@ option can be used to override --squash.
>  
>  --verify-signatures::
>  --no-verify-signatures::
> -	Verify that the commits being merged have good GPG signatures and abort the
> -	merge in case they do not.
> +	Verify that the commits being merged have good and trusted GPG signatures
> +	and abort the merge in case they do not.
>  
>  --summary::
>  --no-summary::
> diff --git a/builtin/merge.c b/builtin/merge.c
> index 7a33d03..752e3a9 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -1248,6 +1248,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  			switch(signature_check.result){
>  				case 'G':
>  					break;
> +				case 'U':
> +					die(_("Commit %s has a good, untrusted GPG signature allegedly by %s."), hex, signature_check.signer);
>  				case 'B':
>  					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
>  				default: /* 'N' */
> diff --git a/commit.c b/commit.c
> index eda7f90..bb2d9ad 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -1029,6 +1029,8 @@ static struct {
>  } sigcheck_gpg_status[] = {
>  	{ 'G', "[GNUPG:] GOODSIG " },
>  	{ 'B', "[GNUPG:] BADSIG " },
> +	{ 'U', "[GNUPG:] TRUST_NEVER" },
> +	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
>  };
>  
>  static void parse_gpg_output(struct signature_check *sigc)
> @@ -1050,11 +1052,12 @@ static void parse_gpg_output(struct signature_check *sigc)
>  			found += strlen(sigcheck_gpg_status[i].check);
>  		}
>  		sigc->result = sigcheck_gpg_status[i].result;
> -		sigc->key = xmemdupz(found, 16);
> -		found += 17;
> -		next = strchrnul(found, '\n');
> -		sigc->signer = xmemdupz(found, next - found);
> -		break;
> +		if (sigc->result != 'U') {

This could use a comment; we know now that only GOODSIG and BADSIG
are followed by a signature, but someone looking at this code in the
future will probably appreciate an explanation.

> +			sigc->key = xmemdupz(found, 16);
> +			found += 17;
> +			next = strchrnul(found, '\n');
> +			sigc->signer = xmemdupz(found, next - found);
> +		}
>  	}
>  }
>  

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 14:44                                   ` John Keeping
@ 2013-03-31 15:03                                     ` Thomas Rast
  2013-03-31 15:21                                       ` Sebastian Götte
  2013-03-31 15:26                                       ` John Keeping
  2013-03-31 15:58                                     ` [PATCH v8 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
  2 siblings, 2 replies; 62+ messages in thread
From: Thomas Rast @ 2013-03-31 15:03 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git, gitster, John Keeping

John Keeping <john@keeping.me.uk> writes:

> On Sun, Mar 31, 2013 at 04:33:57PM +0200, Sebastian Götte wrote:
>> diff --git a/commit.c b/commit.c
>> index eda7f90..bb2d9ad 100644
>> --- a/commit.c
>> +++ b/commit.c
>> @@ -1029,6 +1029,8 @@ static struct {
>>  } sigcheck_gpg_status[] = {
>>  	{ 'G', "[GNUPG:] GOODSIG " },
>>  	{ 'B', "[GNUPG:] BADSIG " },
>> +	{ 'U', "[GNUPG:] TRUST_NEVER" },
>> +	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
>>  };
>>  
>>  static void parse_gpg_output(struct signature_check *sigc)
>> @@ -1050,11 +1052,12 @@ static void parse_gpg_output(struct signature_check *sigc)
>>  			found += strlen(sigcheck_gpg_status[i].check);
>>  		}
>>  		sigc->result = sigcheck_gpg_status[i].result;
>> -		sigc->key = xmemdupz(found, 16);
>> -		found += 17;
>> -		next = strchrnul(found, '\n');
>> -		sigc->signer = xmemdupz(found, next - found);
>> -		break;
>> +		if (sigc->result != 'U') {
>
> This could use a comment; we know now that only GOODSIG and BADSIG
> are followed by a signature, but someone looking at this code in the
> future will probably appreciate an explanation.

Wouldn't it be even less magical if there was an explicit field in the
struct that says whether we need to read a sig from such a line?

And furthermore, to use an enum instead of a char so that you can easily
spell out the details in the code?  This also has the advantage that the
compiler can check that your 'switch'es cover all cases.

That's of course assuming that I interpret the logic right; my current
understanding is that:

* U means untrusted, which is bettern than B but worse than G;

* GPG guarantees that the last line matching any of the patterns is the
  one we care about, so we can blindly override one with the other

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 15:03                                     ` Thomas Rast
@ 2013-03-31 15:21                                       ` Sebastian Götte
  2013-03-31 15:27                                         ` Thomas Rast
  2013-03-31 15:26                                       ` John Keeping
  1 sibling, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 15:21 UTC (permalink / raw)
  To: Thomas Rast, git

On 03/31/2013 05:03 PM, Thomas Rast wrote:
> John Keeping <john@keeping.me.uk> writes:
> 
>> On Sun, Mar 31, 2013 at 04:33:57PM +0200, Sebastian Götte wrote:
>>> diff --git a/commit.c b/commit.c
>>> index eda7f90..bb2d9ad 100644
>>> --- a/commit.c
>>> +++ b/commit.c
>>> @@ -1029,6 +1029,8 @@ static struct {
>>>  } sigcheck_gpg_status[] = {
>>>  	{ 'G', "[GNUPG:] GOODSIG " },
>>>  	{ 'B', "[GNUPG:] BADSIG " },
>>> +	{ 'U', "[GNUPG:] TRUST_NEVER" },
>>> +	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
>>>  };
>>>  
>>>  static void parse_gpg_output(struct signature_check *sigc)
>>> @@ -1050,11 +1052,12 @@ static void parse_gpg_output(struct signature_check *sigc)
>>>  			found += strlen(sigcheck_gpg_status[i].check);
>>>  		}
>>>  		sigc->result = sigcheck_gpg_status[i].result;
>>> -		sigc->key = xmemdupz(found, 16);
>>> -		found += 17;
>>> -		next = strchrnul(found, '\n');
>>> -		sigc->signer = xmemdupz(found, next - found);
>>> -		break;
>>> +		if (sigc->result != 'U') {
>>
>> This could use a comment; we know now that only GOODSIG and BADSIG
>> are followed by a signature, but someone looking at this code in the
>> future will probably appreciate an explanation.
> 
> Wouldn't it be even less magical if there was an explicit field in the
> struct that says whether we need to read a sig from such a line?
I think that special-case if a few lines below is OK for now.
> And furthermore, to use an enum instead of a char so that you can easily
> spell out the details in the code?  This also has the advantage that the
> compiler can check that your 'switch'es cover all cases.
This char is actually from Junios original code. I think we can afford
three chars. This could be changed if we ever need more than that. Another
possible future feature would be to distinguish between "no signature" and
"public key not found" and to allow pass-through of that GPG "retrieve
missing public keys from keyserver" option.

> That's of course assuming that I interpret the logic right; my current
> understanding is that:
> 
> * U means untrusted, which is bettern than B but worse than G;
Correct. Also, BADSIG is never followed by trust information.
> * GPG guarantees that the last line matching any of the patterns is the
>   one we care about, so we can blindly override one with the other
Actually, we are searching *all* GPG status lines for every search string
in the array. This way, first GOODSIG may be matched to fill in the key
and signer information, but a subsequent TRUST_* match finally sets the
result code.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 15:03                                     ` Thomas Rast
  2013-03-31 15:21                                       ` Sebastian Götte
@ 2013-03-31 15:26                                       ` John Keeping
  1 sibling, 0 replies; 62+ messages in thread
From: John Keeping @ 2013-03-31 15:26 UTC (permalink / raw)
  To: Thomas Rast; +Cc: Sebastian Götte, git, gitster

On Sun, Mar 31, 2013 at 05:03:44PM +0200, Thomas Rast wrote:
> John Keeping <john@keeping.me.uk> writes:
> 
> > On Sun, Mar 31, 2013 at 04:33:57PM +0200, Sebastian Götte wrote:
> >> diff --git a/commit.c b/commit.c
> >> index eda7f90..bb2d9ad 100644
> >> --- a/commit.c
> >> +++ b/commit.c
> >> @@ -1029,6 +1029,8 @@ static struct {
> >>  } sigcheck_gpg_status[] = {
> >>  	{ 'G', "[GNUPG:] GOODSIG " },
> >>  	{ 'B', "[GNUPG:] BADSIG " },
> >> +	{ 'U', "[GNUPG:] TRUST_NEVER" },
> >> +	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
> >>  };
> >>  
> >>  static void parse_gpg_output(struct signature_check *sigc)
> >> @@ -1050,11 +1052,12 @@ static void parse_gpg_output(struct signature_check *sigc)
> >>  			found += strlen(sigcheck_gpg_status[i].check);
> >>  		}
> >>  		sigc->result = sigcheck_gpg_status[i].result;
> >> -		sigc->key = xmemdupz(found, 16);
> >> -		found += 17;
> >> -		next = strchrnul(found, '\n');
> >> -		sigc->signer = xmemdupz(found, next - found);
> >> -		break;
> >> +		if (sigc->result != 'U') {
> >
> > This could use a comment; we know now that only GOODSIG and BADSIG
> > are followed by a signature, but someone looking at this code in the
> > future will probably appreciate an explanation.
> 
> Wouldn't it be even less magical if there was an explicit field in the
> struct that says whether we need to read a sig from such a line?

Yeah, that would be even better.

> And furthermore, to use an enum instead of a char so that you can easily
> spell out the details in the code?  This also has the advantage that the
> compiler can check that your 'switch'es cover all cases.
>
> That's of course assuming that I interpret the logic right; my current
> understanding is that:
> 
> * U means untrusted, which is bettern than B but worse than G;

Yes, although I wonder if we should split TRUST_NEVER and
TRUST_UNDEFINED (and maybe handle TRUST_MARGINAL as well) and print
different messages in each case.

> * GPG guarantees that the last line matching any of the patterns is the
>   one we care about, so we can blindly override one with the other

The DETAILS file in GPG says:

    For each signature only one of the codes GOODSIG, BADSIG, EXPSIG,
    EXPKEYSIG, REVKEYSIG or ERRSIG will be emitted.

so we should be OK here.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures
  2013-03-31 15:21                                       ` Sebastian Götte
@ 2013-03-31 15:27                                         ` Thomas Rast
  0 siblings, 0 replies; 62+ messages in thread
From: Thomas Rast @ 2013-03-31 15:27 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> On 03/31/2013 05:03 PM, Thomas Rast wrote:
>>>>  } sigcheck_gpg_status[] = {
>>>>  	{ 'G', "[GNUPG:] GOODSIG " },
>>>>  	{ 'B', "[GNUPG:] BADSIG " },
>>>> +	{ 'U', "[GNUPG:] TRUST_NEVER" },
>>>> +	{ 'U', "[GNUPG:] TRUST_UNDEFINED" },
[...]
>> And furthermore, to use an enum instead of a char so that you can easily
>> spell out the details in the code?  This also has the advantage that the
>> compiler can check that your 'switch'es cover all cases.
> This char is actually from Junios original code. I think we can afford
> three chars. This could be changed if we ever need more than that.

*shrug*

I'm tempted to count the above as an argument in favor of the enum,
since there are in fact *four* chars... 'N' also counts. ;-)

But either way... I don't care too deeply and I don't know this corner
of the code.  I just came here because of the valgrind discovery.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v8 0/5] Verify GPG signatures when merging and extend %G? pretty string
  2013-03-31 14:44                                   ` John Keeping
  2013-03-31 15:03                                     ` Thomas Rast
@ 2013-03-31 15:58                                     ` Sebastian Götte
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
  2 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 15:58 UTC (permalink / raw)
  To: git; +Cc: gitster, john

On 03/31/2013 04:41 PM, John Keeping wrote:> On Sun, Mar 31, 2013 at 04:32:52PM +0200, Sebastian Götte wrote:
>> +	/* Iterate over all search strings */
>>  	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
>> -		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
>> -		const char *next;
>> -		if (!found)
>> -			continue;
>> +		const char *found, *next;
>> +
>> +		if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
>> +			/* At the very beginning of the buffer */
> 
> This seems wrong.  You're losing the "\n" in front of the status strings
> above but adding a special first line check skipping the first
> character.  Surely it should be one of these changes or the other, not
> both?

You're right, that does not make a whole lot of sense.

On 03/31/2013 04:44 PM, John Keeping wrote:
>> +		if (sigc->result != 'U') {
>
> This could use a comment; we know now that only GOODSIG and BADSIG
> are followed by a signature, but someone looking at this code in the
> future will probably appreciate an explanation.

Fixed.

Sebastian Götte (5):
  Move commit GPG signature verification to commit.c
  commit.c/GPG signature verification: Also look at the first GPG status
    line
  merge/pull: verify GPG signatures of commits being merged
  merge/pull Check for untrusted good GPG signatures
  pretty printing: extend %G? to include 'N' and 'U'

 Documentation/merge-options.txt    |   5 ++
 Documentation/pretty-formats.txt   |   3 +-
 builtin/merge.c                    |  34 +++++++++++++-
 commit.c                           |  70 ++++++++++++++++++++++++++++
 commit.h                           |  10 ++++
 git-pull.sh                        |  10 +++-
 gpg-interface.h                    |  12 +++++
 pretty.c                           |  93 ++++++-------------------------------
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |  61 ++++++++++++++++++++++++
 13 files changed, 216 insertions(+), 82 deletions(-)
 create mode 100755 t/t7612-merge-verify-signatures.sh

-- 
1.8.1.5

^ permalink raw reply	[flat|nested] 62+ messages in thread

* [PATCH v8 1/5] Move commit GPG signature verification to commit.c
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
@ 2013-03-31 16:00                                       ` Sebastian Götte
  2013-03-31 16:01                                       ` [PATCH v8 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
                                                         ` (3 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 16:00 UTC (permalink / raw)
  To: git; +Cc: gitster, john


Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c        | 59 +++++++++++++++++++++++++++++++++++++
 commit.h        | 10 +++++++
 gpg-interface.h | 11 +++++++
 pretty.c        | 91 +++++++++------------------------------------------------
 4 files changed, 93 insertions(+), 78 deletions(-)

diff --git a/commit.c b/commit.c
index e8eb0ae..eb645af 100644
--- a/commit.c
+++ b/commit.c
@@ -1023,6 +1023,65 @@ free_return:
 	free(buf);
 }
 
+static struct {
+	char result;
+	const char *check;
+} sigcheck_gpg_status[] = {
+	{ 'G', "\n[GNUPG:] GOODSIG " },
+	{ 'B', "\n[GNUPG:] BADSIG " },
+};
+
+static void parse_gpg_output(struct signature_check *sigc)
+{
+	const char *buf = sigc->gpg_status;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
+		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		sigc->result = sigcheck_gpg_status[i].result;
+		found += strlen(sigcheck_gpg_status[i].check);
+		sigc->key = xmemdupz(found, 16);
+		found += 17;
+		next = strchrnul(found, '\n');
+		sigc->signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+void 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;
+
+	sigc->result = 'N';
+
+	if (parse_signed_commit(commit->object.sha1,
+				&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->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);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
+
 void append_merge_tag_headers(struct commit_list *parents,
 			      struct commit_extra_header ***tail)
 {
diff --git a/commit.h b/commit.h
index 4138bb4..8bbcf13 100644
--- a/commit.h
+++ b/commit.h
@@ -5,6 +5,7 @@
 #include "tree.h"
 #include "strbuf.h"
 #include "decorate.h"
+#include "gpg-interface.h"
 
 struct commit_list {
 	struct commit *item;
@@ -230,4 +231,13 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_cur,
 			      const char *format_last);
 
+/*
+ * Check the signature of the given commit. The result of the check is stored in
+ * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
+ * for no signature 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);
+
 #endif /* COMMIT_H */
diff --git a/gpg-interface.h b/gpg-interface.h
index cf99021..5884aa4 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -1,6 +1,17 @@
 #ifndef GPG_INTERFACE_H
 #define GPG_INTERFACE_H
 
+struct signature_check {
+	char *gpg_output;
+	char *gpg_status;
+	char result; /* 0 (not checked),
+		      * N (checked but no further result),
+		      * G (good)
+		      * B (bad) */
+	char *signer;
+	char *key;
+};
+
 extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
 extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
 extern int git_gpg_config(const char *, const char *, void *);
diff --git a/pretty.c b/pretty.c
index b57adef..cf84d3a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -756,14 +756,7 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
-	unsigned commit_signature_parsed:1;
-	struct {
-		char *gpg_output;
-		char *gpg_status;
-		char good_bad;
-		char *signer;
-		char *key;
-	} signature;
+	struct signature_check signature_check;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -946,64 +939,6 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
-static struct {
-	char result;
-	const char *check;
-} signature_check[] = {
-	{ 'G', "\n[GNUPG:] GOODSIG " },
-	{ 'B', "\n[GNUPG:] BADSIG " },
-};
-
-static void parse_signature_lines(struct format_commit_context *ctx)
-{
-	const char *buf = ctx->signature.gpg_status;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
-		const char *found = strstr(buf, signature_check[i].check);
-		const char *next;
-		if (!found)
-			continue;
-		ctx->signature.good_bad = signature_check[i].result;
-		found += strlen(signature_check[i].check);
-		ctx->signature.key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		ctx->signature.signer = xmemdupz(found, next - found);
-		break;
-	}
-}
-
-static void parse_commit_signature(struct format_commit_context *ctx)
-{
-	struct strbuf payload = STRBUF_INIT;
-	struct strbuf signature = STRBUF_INIT;
-	struct strbuf gpg_output = STRBUF_INIT;
-	struct strbuf gpg_status = STRBUF_INIT;
-	int status;
-
-	ctx->commit_signature_parsed = 1;
-
-	if (parse_signed_commit(ctx->commit->object.sha1,
-				&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;
-	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
-	ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL);
-	parse_signature_lines(ctx);
-
- out:
-	strbuf_release(&gpg_status);
-	strbuf_release(&gpg_output);
-	strbuf_release(&payload);
-	strbuf_release(&signature);
-}
-
-
 static int format_reflog_person(struct strbuf *sb,
 				char part,
 				struct reflog_walk_info *log,
@@ -1189,27 +1124,27 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 	}
 
 	if (placeholder[0] == 'G') {
-		if (!c->commit_signature_parsed)
-			parse_commit_signature(c);
+		if (!c->signature_check.result)
+			check_commit_signature(c->commit, &(c->signature_check));
 		switch (placeholder[1]) {
 		case 'G':
-			if (c->signature.gpg_output)
-				strbuf_addstr(sb, c->signature.gpg_output);
+			if (c->signature_check.gpg_output)
+				strbuf_addstr(sb, c->signature_check.gpg_output);
 			break;
 		case '?':
-			switch (c->signature.good_bad) {
+			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
-				strbuf_addch(sb, c->signature.good_bad);
+				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
 		case 'S':
-			if (c->signature.signer)
-				strbuf_addstr(sb, c->signature.signer);
+			if (c->signature_check.signer)
+				strbuf_addstr(sb, c->signature_check.signer);
 			break;
 		case 'K':
-			if (c->signature.key)
-				strbuf_addstr(sb, c->signature.key);
+			if (c->signature_check.key)
+				strbuf_addstr(sb, c->signature_check.key);
 			break;
 		}
 		return 2;
@@ -1347,8 +1282,8 @@ void format_commit_message(const struct commit *commit,
 	rewrap_message_tail(sb, &context, 0, 0, 0);
 
 	logmsg_free(context.message, commit);
-	free(context.signature.gpg_output);
-	free(context.signature.signer);
+	free(context.signature_check.gpg_output);
+	free(context.signature_check.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v8 2/5] commit.c/GPG signature verification: Also look at the first GPG status line
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-31 16:00                                       ` [PATCH v8 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
@ 2013-03-31 16:01                                       ` Sebastian Götte
  2013-03-31 16:02                                       ` [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
                                                         ` (2 subsequent siblings)
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 16:01 UTC (permalink / raw)
  To: git; +Cc: gitster, john


Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 commit.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/commit.c b/commit.c
index eb645af..3a8453d 100644
--- a/commit.c
+++ b/commit.c
@@ -1036,13 +1036,20 @@ static void parse_gpg_output(struct signature_check *sigc)
 	const char *buf = sigc->gpg_status;
 	int i;
 
+	/* Iterate over all search strings */
 	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
-		const char *found = strstr(buf, sigcheck_gpg_status[i].check);
-		const char *next;
-		if (!found)
-			continue;
+		const char *found, *next;
+
+		if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
+			/* At the very beginning of the buffer */
+			found = buf + strlen(sigcheck_gpg_status[i].check + 1);
+		} else {
+			found = strstr(buf, sigcheck_gpg_status[i].check);
+			if (!found)
+ 				continue;
+			found += strlen(sigcheck_gpg_status[i].check);
+		}
 		sigc->result = sigcheck_gpg_status[i].result;
-		found += strlen(sigcheck_gpg_status[i].check);
 		sigc->key = xmemdupz(found, 16);
 		found += 17;
 		next = strchrnul(found, '\n');
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
  2013-03-31 16:00                                       ` [PATCH v8 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
  2013-03-31 16:01                                       ` [PATCH v8 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
@ 2013-03-31 16:02                                       ` Sebastian Götte
  2013-04-01  2:47                                         ` Junio C Hamano
  2013-03-31 16:02                                       ` [PATCH v8 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
  2013-03-31 16:03                                       ` [PATCH v8 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 16:02 UTC (permalink / raw)
  To: git; +Cc: gitster, john

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>
---
 Documentation/merge-options.txt    |  5 ++++
 builtin/merge.c                    | 32 ++++++++++++++++++++++-
 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..31f1067 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -83,6 +83,11 @@ 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 GPG signatures and abort the
+	merge in case they do not.
+
 --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..7a33d03 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_BOOL(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,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		usage_with_options(builtin_merge_usage,
 			builtin_merge_options);
 
+	if (verify_signatures) {
+		for (p = remoteheads; p; p = p->next) {
+			struct commit *commit = p->item;
+			char hex[41];
+			struct signature_check signature_check;
+			memset(&signature_check, 0, sizeof(signature_check));
+
+			check_commit_signature(commit, &signature_check);
+
+			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+			switch(signature_check.result){
+				case 'G':
+					break;
+				case 'B':
+					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
+				default: /* 'N' */
+					die(_("Commit %s does not have a GPG signature."), hex, hex);
+			}
+			if (verbosity >= 0 && signature_check.result == 'G')
+				printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer);
+
+			free(signature_check.gpg_output);
+			free(signature_check.gpg_status);
+			free(signature_check.signer);
+			free(signature_check.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..6ccfbf3
--- /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 &&
+	test_i18ngrep "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 &&
+	test_i18ngrep "has a bad GPG signature" mergeerror
+'
+
+test_expect_success GPG 'merge signed commit with verification' '
+	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
+	test_i18ngrep "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

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v8 4/5] merge/pull Check for untrusted good GPG signatures
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
                                                         ` (2 preceding siblings ...)
  2013-03-31 16:02                                       ` [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-03-31 16:02                                       ` Sebastian Götte
  2013-03-31 16:03                                       ` [PATCH v8 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 16:02 UTC (permalink / raw)
  To: git; +Cc: gitster, john

When --verify-signatures is specified, abort the merge in case a good
GPG signature from an untrusted key is encountered.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/merge-options.txt    |   4 ++--
 builtin/merge.c                    |   2 ++
 commit.c                           |  14 +++++++++-----
 commit.h                           |  10 +++++-----
 gpg-interface.h                    |   1 +
 t/lib-gpg/pubring.gpg              | Bin 1164 -> 2359 bytes
 t/lib-gpg/random_seed              | Bin 600 -> 600 bytes
 t/lib-gpg/secring.gpg              | Bin 1237 -> 3734 bytes
 t/lib-gpg/trustdb.gpg              | Bin 1280 -> 1360 bytes
 t/t7612-merge-verify-signatures.sh |   9 +++++++++
 10 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index 31f1067..a0f022b 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -85,8 +85,8 @@ option can be used to override --squash.
 
 --verify-signatures::
 --no-verify-signatures::
-	Verify that the commits being merged have good GPG signatures and abort the
-	merge in case they do not.
+	Verify that the commits being merged have good and trusted GPG signatures
+	and abort the merge in case they do not.
 
 --summary::
 --no-summary::
diff --git a/builtin/merge.c b/builtin/merge.c
index 7a33d03..752e3a9 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1248,6 +1248,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			switch(signature_check.result){
 				case 'G':
 					break;
+				case 'U':
+					die(_("Commit %s has a good, untrusted GPG signature allegedly by %s."), hex, signature_check.signer);
 				case 'B':
 					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
 				default: /* 'N' */
diff --git a/commit.c b/commit.c
index 3a8453d..d590724 100644
--- a/commit.c
+++ b/commit.c
@@ -1029,6 +1029,8 @@ static struct {
 } sigcheck_gpg_status[] = {
 	{ 'G', "\n[GNUPG:] GOODSIG " },
 	{ 'B', "\n[GNUPG:] BADSIG " },
+	{ 'U', "\n[GNUPG:] TRUST_NEVER" },
+	{ 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
 };
 
 static void parse_gpg_output(struct signature_check *sigc)
@@ -1050,11 +1052,13 @@ static void parse_gpg_output(struct signature_check *sigc)
 			found += strlen(sigcheck_gpg_status[i].check);
 		}
 		sigc->result = sigcheck_gpg_status[i].result;
-		sigc->key = xmemdupz(found, 16);
-		found += 17;
-		next = strchrnul(found, '\n');
-		sigc->signer = xmemdupz(found, next - found);
-		break;
+		/* The trust messages are not followed by key/signer information */
+		if (sigc->result != 'U') {
+			sigc->key = xmemdupz(found, 16);
+			found += 17;
+			next = strchrnul(found, '\n');
+			sigc->signer = xmemdupz(found, next - found);
+		}
 	}
 }
 
diff --git a/commit.h b/commit.h
index 8bbcf13..27d9b36 100644
--- a/commit.h
+++ b/commit.h
@@ -232,11 +232,11 @@ extern void print_commit_list(struct commit_list *list,
 			      const char *format_last);
 
 /*
- * Check the signature of the given commit. The result of the check is stored in
- * sig->result, 'G' for a good signature, 'B' for a bad signature and 'N'
- * for no signature at all.
- * This may allocate memory for sig->gpg_output, sig->gpg_status, sig->signer
- * and sig->key.
+ * Check the signature of the given commit. The result of the check is stored
+ * in sig->check_result, 'G' for a good signature, 'U' for a good signature
+ * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
+ * 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);
 
diff --git a/gpg-interface.h b/gpg-interface.h
index 5884aa4..a85cb5b 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -6,6 +6,7 @@ struct signature_check {
 	char *gpg_status;
 	char result; /* 0 (not checked),
 		      * N (checked but no further result),
+		      * U (untrusted good),
 		      * G (good)
 		      * B (bad) */
 	char *signer;
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..1a3c2d487c2fda9169751a3068fa51e853a1e519 100644
GIT binary patch
delta 1212
zcmV;t1Vj6b3AYlkj0As~0SyFEOqNFh2mr~8{AU1PG9!Ku9w|}@vpZJPg*#s86v-*O
zhafj(DL&&lF<A0)`tvC$E@WHJ2r~0{0ZCh1kHo$b9ih^aD*~)oVvK&yC1(yi)6x_y
zF8V3JpbIY^ZYQ&<Uk#j*ja0`;jw^J+5~!h3qc)ej%g1;Wb0U8cXSuLKdXkx2PUtB4
z>zqQ>O;r+6Qa<VFd?Z<kA#8Ch(&2p+QaoKR;K0{BU;l_DqGU2YCt6Fr4psgB*tPou
z8C@tnj=CqkffSLs=a-5G+h;Y8bkw<s-iIgT{$f^j!)l`hmkz}$nZPxBoPmwU;zIH^
zLN~K=bO>hGBes9{Fcqzh%Wj2h^{ZjI01*KI0kkAVa%poQL}_zlZ*pX5VIVwYX>((5
za%4bdcwudDY-KKPWpqA?0XPH`0RjLb1p-k_mPY~`0|pBT2nPcK1{DYb2?`4Y76JnS
z0v-VZ7k~f?2@qikE`_%uafw)?2m1%-{uDP0Rs1|(F{OVhOSKoH$k!o)x3CO5Z^@El
zxGq=+xQGR&r@3$S<0@SUu@@UuxUH%pD3Q-D`?37Vg*xmo<%+KD)(U~k>XU^b+=70V
zFr?b3DvlFq^B*d%lz=lr!ch6nQbvcwWCD}n2DT3`r?RDkq=7r}qFvEh<dfApA7yR;
z8`OB;$TEKm@jkwG=8vlcKFd>rcM8+9eP66QueaUSz6NhphD3O@E(iX7T@2kxV(dXd
zxG_$z;qqdh<Np6*{J!OD7<b;8M6otxRi0UUN|OngZ4NdYbeU0hneiVEwY2h5xZ4z{
zsXL4>UkGxJ?Pc}MK(e<Q>lV@oumS)Bxd9CXQA~f9M*#=`sXI><#Mh|Ll%uxJqK}<$
zc)VOU|M7H$nU~Bd&(!9WlLk)V)&z>UY~vSc_rVFe1JAY7ThlG1IzE9z6>ly;9W<Ie
zrW&+EU|2KpxZ$0rq-Jn9T{vsi6Qh0j42I3_PLQ5$)qa$}3etj89nJkr^3#KD!xh9*
z4BvmZ0TM||zk);(8@JN=fad9d4hK=Hf1{&&J5DP=<J9wW0IGZ_*p$xvH=WZu8h1r#
z-&1Cjfqp9S1m|b%7<S$7Du_^GfwyFyObC4EMw~6<SwH1<W45gXD*OLAz9x~@tJ8<?
z^Jtn;2$K^0SUY?f@Cu@)V5#TJ_yB~Qw!nY4S8m<?1+xGV0RRDs0Urby0RjLC1p-k_
zmPY~`3;+rV5Mc=}g|{MciR^I*0LA1n{v0`JrvmHmaVfVdlHi8rqLbHhetm3X@&@Ir
zXeh6;re%LlP-QWig+n`!Ae0mFQrnvwOO!ZC8%_&sj-2tVNM{p{WTNusbkj>Mdd`2^
zgn2IBiSf%lm^DyQau7lg)pfK8(7<}-iih0<Mm(k9C_6rNl4sE(`#Aa}K2LGjw*;6_
zqt%Wq<wPe#N15ysVyQhrRmcBJ^&Q!GEW&Mp#@%c+q~S#5UAR4_eeRejBs87Xv&Yt)
zg^u)$-LG%(tX4KA^%L!j<@*Z_A!anKyx}t&F6RX0yYXc)&KtB~l*=nGq9`cf<aD`r
a9*zs=xM)EjeiKg!$s4>o369jT0ssS^s3f2O

delta 7
Ocmdlk)Wf-<hXnu&fC8TY

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..95d249f15fce980f0e8c1a8a18b085b3885708aa 100644
GIT binary patch
literal 600
zcmV-e0;m1ccZd+x>>TST*Lrq1x^ggx^+ymwieO!6X=U~ZH@{avIgxdn#ai{)Ou@Qw
za}Z!boffEq^fn)n?c=IEnDpt59Lnc)aR*;8Z;k>gh_NW;ka;7Mt@v#sG(!Y9SSXWv
zQxd3WlyBr#4ltW6uKOoa6(r3df1VX$cG4`Om6hD-ckaX+Hb_yI?{f`hJQY&k!1cM-
zoGeY~(Z7aYn$W06djh?W|CMs>W=k@jgf=P2D1UA1T%vz0oE|<O<lIacG0xioPtS&U
zNd#}P%YpJr-H65~J^RdqA!YV9BEvh7Gw^CdXg+Hp?kj=KGW|+|&g$4?`trWWGuy$9
zv-|;8Y4(NRHWPyJ{epd{4%FHQKk5j}?0FFDAJ;0kIItZ4y<JS?DIG4~0!#x~;X`!P
zO%+va?@`?yQnhjrP@&#yjY$YO_0yk|1ddhc8V&ru7d%ytet)mF<ZIUbPB3bvhHQ41
zNmnYeFxUMu=m$K5&s=5_F&JSR#oU3Y#X{(q7HTp-VYJ)%JjihbZ@R#GeqmU{>0C4Q
zc}hUG+ighB{7XSaNw_h;=YtqacQ<B(Cg$e)^NTDD-oMD+T`O#-^|-ib>j!<pxHg+(
zlC$%zE836|E*F*((=>O{Nn@K$taZO}!>$t>GMgsw?!=n_#(%X9Ha|$b=H@VstWYe;
zPUQ<L$$#9HTcOLoyEd6*A4TOEe3}c}GiW*^P1Lt{nHYUEAB`Qx7*wizaEyM$?AjVN
mb-6m)4=6PVqdR>h+D!{<c#q1!T9b(}OW7hrrT@nJcBO(OGA4ll

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..82dca8f80bf170fde5705862c3eeb9d994725042 100644
GIT binary patch
delta 2524
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

delta 7
OcmbOxdzEv;RTcmY+5;N^

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
index abace962b8bf84be688a6f27e4ebd0ee7052f210..4879ae9a84650a93a4d15bd6560c5d1b89eb4c2f 100644
GIT binary patch
delta 133
zcmZqRy1*sEm|l?1%*@Ej$i%=9=re5@0|Nu&L_y(=>YJDu6*k{umSl_t3LyXw!<BtX
zhEkV><>GE>E=lCnYu&C?*vSl0pomb%%dj-dsEA)Mq*Svt$mH(j_twWhW_@KtD1fp6
DE~Fa=

delta 52
zcmcb>)xagdm|l?1%*@Ej$iTqhmVVlAqM`Uk^-atZ9rz~(oS3|U#hLd&3{VON0Ad;p
ADF6Tf

diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh
index 6ccfbf3..20b3cfb 100755
--- a/t/t7612-merge-verify-signatures.sh
+++ b/t/t7612-merge-verify-signatures.sh
@@ -27,6 +27,10 @@ test_expect_success GPG 'create signed commits' '
 	git hash-object -w -t commit forged >forged.commit &&
 	git checkout initial &&
 
+	git checkout -b side-untrusted &&
+	echo 3 >baz && git add baz &&
+	test_tick && git commit -SB7227189 -m "untrusted on side"
+
 	git checkout master
 '
 
@@ -40,6 +44,11 @@ test_expect_success GPG 'merge commit with bad signature with verification' '
 	test_i18ngrep "has a bad GPG signature" mergeerror
 '
 
+test_expect_success GPG 'merge commit with untrusted signature with verification' '
+	test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror &&
+	test_i18ngrep "has a good, untrusted GPG signature" mergeerror
+'
+
 test_expect_success GPG 'merge signed commit with verification' '
 	git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput &&
 	test_i18ngrep "has a good GPG signature" mergeoutput
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* [PATCH v8 5/5] pretty printing: extend %G? to include 'N' and 'U'
       [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
                                                         ` (3 preceding siblings ...)
  2013-03-31 16:02                                       ` [PATCH v8 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
@ 2013-03-31 16:03                                       ` Sebastian Götte
  4 siblings, 0 replies; 62+ messages in thread
From: Sebastian Götte @ 2013-03-31 16:03 UTC (permalink / raw)
  To: git; +Cc: gitster, john

Expand %G? in pretty format strings to 'N' in case of no GPG signature
and 'U' in case of a good but untrusted GPG signature in addition to
the previous 'G'ood and 'B'ad. This eases writing anyting parsing
git-log output.

Signed-off-by: Sebastian Götte <jaseg@physik-pool.tu-berlin.de>
---
 Documentation/pretty-formats.txt | 3 ++-
 pretty.c                         | 2 ++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2939655..afac703 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -131,7 +131,8 @@ The placeholders are:
 - '%B': raw body (unwrapped subject and body)
 - '%N': commit notes
 - '%GG': raw verification message from GPG for a signed commit
-- '%G?': show either "G" for Good or "B" for Bad for a signed commit
+- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
+  untrusted signature and "N" for no signature
 - '%GS': show the name of the signer for a signed commit
 - '%GK': show the key used to sign a signed commit
 - '%gD': reflog selector, e.g., `refs/stash@{1}`
diff --git a/pretty.c b/pretty.c
index cf84d3a..840c41f 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1135,6 +1135,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 			switch (c->signature_check.result) {
 			case 'G':
 			case 'B':
+			case 'U':
+			case 'N':
 				strbuf_addch(sb, c->signature_check.result);
 			}
 			break;
-- 
1.8.1.5

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged
  2013-03-31 16:02                                       ` [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
@ 2013-04-01  2:47                                         ` Junio C Hamano
  2013-04-01 12:53                                           ` Sebastian Götte
  0 siblings, 1 reply; 62+ messages in thread
From: Junio C Hamano @ 2013-04-01  2:47 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git, john

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> +	if (verify_signatures) {
> +		for (p = remoteheads; p; p = p->next) {
> +			struct commit *commit = p->item;
> +			char hex[41];
> +			struct signature_check signature_check;
> +			memset(&signature_check, 0, sizeof(signature_check));
> +
> +			check_commit_signature(commit, &signature_check);
> +
> +			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
> +			switch(signature_check.result){
> +				case 'G':
> +					break;
> +				case 'B':
> +					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
> +				default: /* 'N' */
> +					die(_("Commit %s does not have a GPG signature."), hex, hex);

Count %s and number and arguments.

> +			}

Style.

        switch (expr) {
	case 'G':
		do_something_for_G();
		break;
		...
	}

Also avoid overlong lines.

I'll squash in something like the following and push out the result
on 'pu' tonight.  Please check to see if I made silly mistakes while
doing so.

Thanks.

 builtin/merge.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 7a33d03..e57c42c 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1245,16 +1245,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			check_commit_signature(commit, &signature_check);
 
 			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
-			switch(signature_check.result){
-				case 'G':
-					break;
-				case 'B':
-					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
-				default: /* 'N' */
-					die(_("Commit %s does not have a GPG signature."), hex, hex);
+			switch (signature_check.result) {
+			case 'G':
+				break;
+			case 'B':
+				die(_("Commit %s has a bad GPG signature "
+				      "allegedly by %s."), hex, signature_check.signer);
+			default: /* 'N' */
+				die(_("Commit %s does not have a GPG signature."), hex);
 			}
 			if (verbosity >= 0 && signature_check.result == 'G')
-				printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer);
+				printf(_("Commit %s has a good GPG signature by %s\n"),
+				       hex, signature_check.signer);
 
 			free(signature_check.gpg_output);
 			free(signature_check.gpg_status);

^ permalink raw reply related	[flat|nested] 62+ messages in thread

* Re: [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged
  2013-04-01  2:47                                         ` Junio C Hamano
@ 2013-04-01 12:53                                           ` Sebastian Götte
  2013-04-01 14:55                                             ` Junio C Hamano
  0 siblings, 1 reply; 62+ messages in thread
From: Sebastian Götte @ 2013-04-01 12:53 UTC (permalink / raw)
  To: Junio C Hamano, git

On 04/01/2013 04:47 AM, Junio C Hamano wrote:
> I'll squash in something like the following and push out the result
> on 'pu' tonight.  Please check to see if I made silly mistakes while
> doing so.
> 
> Thanks.
> 
>  builtin/merge.c | 18 ++++++++++--------
>  1 file changed, 10 insertions(+), 8 deletions(-)
> 
> diff --git a/builtin/merge.c b/builtin/merge.c
> index 7a33d03..e57c42c 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -1245,16 +1245,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  			check_commit_signature(commit, &signature_check);
>  
>  			strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
> -			switch(signature_check.result){
> -				case 'G':
> -					break;
> -				case 'B':
> -					die(_("Commit %s has a bad GPG signature allegedly by %s."), hex, signature_check.signer);
> -				default: /* 'N' */
> -					die(_("Commit %s does not have a GPG signature."), hex, hex);
> +			switch (signature_check.result) {
> +			case 'G':
> +				break;
> +			case 'B':
> +				die(_("Commit %s has a bad GPG signature "
> +				      "allegedly by %s."), hex, signature_check.signer);
> +			default: /* 'N' */
> +				die(_("Commit %s does not have a GPG signature."), hex);
>  			}
>  			if (verbosity >= 0 && signature_check.result == 'G')
> -				printf(_("Commit %s has a good GPG signature by %s\n"), hex, signature_check.signer);
> +				printf(_("Commit %s has a good GPG signature by %s\n"),
> +				       hex, signature_check.signer);
>  
>  			free(signature_check.gpg_output);
>  			free(signature_check.gpg_status);
Looks fine to me.

^ permalink raw reply	[flat|nested] 62+ messages in thread

* Re: [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged
  2013-04-01 12:53                                           ` Sebastian Götte
@ 2013-04-01 14:55                                             ` Junio C Hamano
  0 siblings, 0 replies; 62+ messages in thread
From: Junio C Hamano @ 2013-04-01 14:55 UTC (permalink / raw)
  To: Sebastian Götte; +Cc: git

Sebastian Götte <jaseg@physik.tu-berlin.de> writes:

> On 04/01/2013 04:47 AM, Junio C Hamano wrote:
>> I'll squash in something like the following and push out the result
>> on 'pu' tonight.  Please check to see if I made silly mistakes while
>> doing so.
>> 
>> Thanks.
>> 
>...
> Looks fine to me.

Thanks for a quick response.

^ permalink raw reply	[flat|nested] 62+ messages in thread

end of thread, other threads:[~2013-04-01 14:56 UTC | newest]

Thread overview: 62+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-03-23  1:57 [PATCH v2 1/4] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-25 15:54 ` Junio C Hamano
2013-03-25 23:46   ` [PATCH 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
2013-03-26  1:46     ` Junio C Hamano
2013-03-26 11:05       ` [PATCH v4 " Sebastian Götte
2013-03-26 16:26         ` Junio C Hamano
2013-03-26 16:43           ` Sebastian Götte
     [not found]       ` <cover.1364295502.git.jaseg@physik-pool.tu-berlin.de>
2013-03-26 11:05         ` [PATCH v4 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-26 11:05         ` [PATCH v4 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-28 22:33           ` Junio C Hamano
2013-03-26 11:05         ` [PATCH v4 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-03-28 22:33           ` Junio C Hamano
2013-03-30  0:13             ` [PATCH v5 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
     [not found]             ` <cover.1364601337.git.jaseg@physik-pool.tu-berlin.de>
2013-03-30  0:14               ` [PATCH v5 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-30  3:37                 ` Junio C Hamano
2013-03-30  0:14               ` [PATCH v5 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-30  3:37                 ` Junio C Hamano
2013-03-30  0:14               ` [PATCH v5 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-03-30  3:38                 ` Junio C Hamano
2013-03-30 14:14                   ` [PATCH v6 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
     [not found]                   ` <cover.1364652339.git.jaseg@physik-pool.tu-berlin.de>
2013-03-30 14:15                     ` [PATCH v6 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-30 14:15                     ` [PATCH v6 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-30 14:15                     ` [PATCH v6 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-03-30 14:16                     ` [PATCH v6 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-30 14:16                     ` [PATCH v6 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
2013-03-30  0:14               ` [PATCH v5 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-31  8:32                 ` Thomas Rast
2013-03-31 10:55                   ` Sebastian Götte
2013-03-31 11:38                     ` Thomas Rast
2013-03-31 11:57                       ` Sebastian Götte
2013-03-31 12:16                         ` Thomas Rast
2013-03-31 12:27                           ` Sebastian Götte
2013-03-31 13:33                             ` John Keeping
2013-03-31 14:32                               ` [PATCH v7 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
     [not found]                               ` <cover.1364738348.git.jaseg@physik-pool.tu-berlin.de>
2013-03-31 14:32                                 ` [PATCH v7 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-31 14:32                                 ` [PATCH v7 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-31 14:41                                   ` John Keeping
2013-03-31 14:33                                 ` [PATCH v7 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-03-31 14:33                                 ` [PATCH v7 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-31 14:44                                   ` John Keeping
2013-03-31 15:03                                     ` Thomas Rast
2013-03-31 15:21                                       ` Sebastian Götte
2013-03-31 15:27                                         ` Thomas Rast
2013-03-31 15:26                                       ` John Keeping
2013-03-31 15:58                                     ` [PATCH v8 0/5] Verify GPG signatures when merging and extend %G? pretty string Sebastian Götte
     [not found]                                     ` <cover.1364742659.git.jaseg@physik-pool.tu-berlin.de>
2013-03-31 16:00                                       ` [PATCH v8 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-31 16:01                                       ` [PATCH v8 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-31 16:02                                       ` [PATCH v8 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-04-01  2:47                                         ` Junio C Hamano
2013-04-01 12:53                                           ` Sebastian Götte
2013-04-01 14:55                                             ` Junio C Hamano
2013-03-31 16:02                                       ` [PATCH v8 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-31 16:03                                       ` [PATCH v8 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
2013-03-31 14:34                                 ` [PATCH v7 " Sebastian Götte
2013-03-30  0:15               ` [PATCH v5 " Sebastian Götte
2013-03-26 11:05         ` [PATCH v4 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-26 11:05         ` [PATCH v4 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte
     [not found]   ` <cover.1364254748.git.jaseg@physik-pool.tu-berlin.de>
2013-03-25 23:46     ` [PATCH 1/5] Move commit GPG signature verification to commit.c Sebastian Götte
2013-03-25 23:46     ` [PATCH 2/5] commit.c/GPG signature verification: Also look at the first GPG status line Sebastian Götte
2013-03-25 23:46     ` [PATCH 3/5] merge/pull: verify GPG signatures of commits being merged Sebastian Götte
2013-03-25 23:46     ` [PATCH 4/5] merge/pull Check for untrusted good GPG signatures Sebastian Götte
2013-03-25 23:46     ` [PATCH 5/5] pretty printing: extend %G? to include 'N' and 'U' Sebastian Götte

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.