about summary refs log tree commit diff
path: root/third_party/git/gpg-interface.c
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2020-11-21T18·20+0100
committerVincent Ambo <mail@tazj.in>2020-11-21T18·45+0100
commitf4609b896fac842433bd495c166d5987852a6a73 (patch)
tree95511c465c54c4f5d27e5d39ce187e2a1dd82bd3 /third_party/git/gpg-interface.c
parent082c006c04343a78d87b6c6ab3608c25d6213c3f (diff)
merge(3p/git): Merge git subtree at v2.29.2 r/1890
This also bumps the stable nixpkgs to 20.09 as of 2020-11-21, because
there is some breakage in the git build related to the netrc
credentials helper which someone has taken care of in nixpkgs.

The stable channel is not used for anything other than git, so this
should be fine.

Change-Id: I3575a19dab09e1e9556cf8231d717de9890484fb
Diffstat (limited to 'third_party/git/gpg-interface.c')
-rw-r--r--third_party/git/gpg-interface.c242
1 files changed, 165 insertions, 77 deletions
diff --git a/third_party/git/gpg-interface.c b/third_party/git/gpg-interface.c
index d60115ca404e..b49927083661 100644
--- a/third_party/git/gpg-interface.c
+++ b/third_party/git/gpg-interface.c
@@ -7,6 +7,8 @@
 #include "tempfile.h"
 
 static char *configured_signing_key;
+static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
+
 struct gpg_format {
 	const char *name;
 	const char *program;
@@ -85,6 +87,8 @@ void signature_check_clear(struct signature_check *sigc)
 #define GPG_STATUS_UID		(1<<2)
 /* The status includes key fingerprints */
 #define GPG_STATUS_FINGERPRINT	(1<<3)
+/* The status includes trust level */
+#define GPG_STATUS_TRUST_LEVEL	(1<<4)
 
 /* Short-hand for standard exclusive *SIG status with keyid & UID */
 #define GPG_STATUS_STDSIG	(GPG_STATUS_EXCLUSIVE|GPG_STATUS_KEYID|GPG_STATUS_UID)
@@ -96,15 +100,49 @@ static struct {
 } sigcheck_gpg_status[] = {
 	{ 'G', "GOODSIG ", GPG_STATUS_STDSIG },
 	{ 'B', "BADSIG ", GPG_STATUS_STDSIG },
-	{ 'U', "TRUST_NEVER", 0 },
-	{ 'U', "TRUST_UNDEFINED", 0 },
 	{ 'E', "ERRSIG ", GPG_STATUS_EXCLUSIVE|GPG_STATUS_KEYID },
 	{ 'X', "EXPSIG ", GPG_STATUS_STDSIG },
 	{ 'Y', "EXPKEYSIG ", GPG_STATUS_STDSIG },
 	{ 'R', "REVKEYSIG ", GPG_STATUS_STDSIG },
 	{ 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT },
+	{ 0, "TRUST_", GPG_STATUS_TRUST_LEVEL },
+};
+
+static struct {
+	const char *key;
+	enum signature_trust_level value;
+} sigcheck_gpg_trust_level[] = {
+	{ "UNDEFINED", TRUST_UNDEFINED },
+	{ "NEVER", TRUST_NEVER },
+	{ "MARGINAL", TRUST_MARGINAL },
+	{ "FULLY", TRUST_FULLY },
+	{ "ULTIMATE", TRUST_ULTIMATE },
 };
 
+static void replace_cstring(char **field, const char *line, const char *next)
+{
+	free(*field);
+
+	if (line && next)
+		*field = xmemdupz(line, next - line);
+	else
+		*field = NULL;
+}
+
+static int parse_gpg_trust_level(const char *level,
+				 enum signature_trust_level *res)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_trust_level); i++) {
+		if (!strcmp(sigcheck_gpg_trust_level[i].key, level)) {
+			*res = sigcheck_gpg_trust_level[i].value;
+			return 0;
+		}
+	}
+	return 1;
+}
+
 static void parse_gpg_output(struct signature_check *sigc)
 {
 	const char *buf = sigc->gpg_status;
@@ -126,9 +164,18 @@ static void parse_gpg_output(struct signature_check *sigc)
 		/* Iterate over all search strings */
 		for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
 			if (skip_prefix(line, sigcheck_gpg_status[i].check, &line)) {
+				/*
+				 * GOODSIG, BADSIG etc. can occur only once for
+				 * each signature.  Therefore, if we had more
+				 * than one then we're dealing with multiple
+				 * signatures.  We don't support them
+				 * currently, and they're rather hard to
+				 * create, so something is likely fishy and we
+				 * should reject them altogether.
+				 */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_EXCLUSIVE) {
 					if (seen_exclusive_status++)
-						goto found_duplicate_status;
+						goto error;
 				}
 
 				if (sigcheck_gpg_status[i].result)
@@ -136,33 +183,62 @@ static void parse_gpg_output(struct signature_check *sigc)
 				/* Do we have key information? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_KEYID) {
 					next = strchrnul(line, ' ');
-					free(sigc->key);
-					sigc->key = xmemdupz(line, next - line);
+					replace_cstring(&sigc->key, line, next);
 					/* Do we have signer information? */
 					if (*next && (sigcheck_gpg_status[i].flags & GPG_STATUS_UID)) {
 						line = next + 1;
 						next = strchrnul(line, '\n');
-						free(sigc->signer);
-						sigc->signer = xmemdupz(line, next - line);
+						replace_cstring(&sigc->signer, line, next);
 					}
 				}
+
+				/* Do we have trust level? */
+				if (sigcheck_gpg_status[i].flags & GPG_STATUS_TRUST_LEVEL) {
+					/*
+					 * GPG v1 and v2 differs in how the
+					 * TRUST_ lines are written.  Some
+					 * trust lines contain no additional
+					 * space-separated information for v1.
+					 */
+					size_t trust_size = strcspn(line, " \n");
+					char *trust = xmemdupz(line, trust_size);
+
+					if (parse_gpg_trust_level(trust, &sigc->trust_level)) {
+						free(trust);
+						goto error;
+					}
+					free(trust);
+				}
+
 				/* Do we have fingerprint? */
 				if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) {
-					next = strchrnul(line, ' ');
-					free(sigc->fingerprint);
-					sigc->fingerprint = xmemdupz(line, next - line);
+					const char *limit;
+					char **field;
 
-					/* Skip interim fields */
+					next = strchrnul(line, ' ');
+					replace_cstring(&sigc->fingerprint, line, next);
+
+					/*
+					 * Skip interim fields.  The search is
+					 * limited to the same line since only
+					 * OpenPGP signatures has a field with
+					 * the primary fingerprint.
+					 */
+					limit = strchrnul(line, '\n');
 					for (j = 9; j > 0; j--) {
-						if (!*next)
+						if (!*next || limit <= next)
 							break;
 						line = next + 1;
 						next = strchrnul(line, ' ');
 					}
 
-					next = strchrnul(line, '\n');
-					free(sigc->primary_key_fingerprint);
-					sigc->primary_key_fingerprint = xmemdupz(line, next - line);
+					field = &sigc->primary_key_fingerprint;
+					if (!j) {
+						next = strchrnul(line, '\n');
+						replace_cstring(field, line, next);
+					} else {
+						replace_cstring(field, NULL, NULL);
+					}
 				}
 
 				break;
@@ -171,14 +247,7 @@ static void parse_gpg_output(struct signature_check *sigc)
 	}
 	return;
 
-found_duplicate_status:
-	/*
-	 * GOODSIG, BADSIG etc. can occur only once for each signature.
-	 * Therefore, if we had more than one then we're dealing with multiple
-	 * signatures.  We don't support them currently, and they're rather
-	 * hard to create, so something is likely fishy and we should reject
-	 * them altogether.
-	 */
+error:
 	sigc->result = 'E';
 	/* Clear partial data to avoid confusion */
 	FREE_AND_NULL(sigc->primary_key_fingerprint);
@@ -187,6 +256,55 @@ found_duplicate_status:
 	FREE_AND_NULL(sigc->key);
 }
 
+static 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)
+{
+	struct child_process gpg = CHILD_PROCESS_INIT;
+	struct gpg_format *fmt;
+	struct tempfile *temp;
+	int ret;
+	struct strbuf buf = STRBUF_INIT;
+
+	temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
+	if (!temp)
+		return error_errno(_("could not create temporary file"));
+	if (write_in_full(temp->fd, signature, signature_size) < 0 ||
+	    close_tempfile_gently(temp) < 0) {
+		error_errno(_("failed writing detached signature to '%s'"),
+			    temp->filename.buf);
+		delete_tempfile(&temp);
+		return -1;
+	}
+
+	fmt = get_format_by_sig(signature);
+	if (!fmt)
+		BUG("bad signature '%s'", signature);
+
+	strvec_push(&gpg.args, fmt->program);
+	strvec_pushv(&gpg.args, fmt->verify_args);
+	strvec_pushl(&gpg.args,
+		     "--status-fd=1",
+		     "--verify", temp->filename.buf, "-",
+		     NULL);
+
+	if (!gpg_status)
+		gpg_status = &buf;
+
+	sigchain_push(SIGPIPE, SIG_IGN);
+	ret = pipe_command(&gpg, payload, payload_size,
+			   gpg_status, 0, gpg_output, 0);
+	sigchain_pop(SIGPIPE);
+
+	delete_tempfile(&temp);
+
+	ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
+	strbuf_release(&buf); /* no matter it was used or not */
+
+	return ret;
+}
+
 int check_signature(const char *payload, size_t plen, const char *signature,
 	size_t slen, struct signature_check *sigc)
 {
@@ -195,6 +313,7 @@ int check_signature(const char *payload, size_t plen, const char *signature,
 	int status;
 
 	sigc->result = 'N';
+	sigc->trust_level = -1;
 
 	status = verify_signed_buffer(payload, plen, signature, slen,
 				      &gpg_output, &gpg_status);
@@ -204,7 +323,8 @@ int check_signature(const char *payload, size_t plen, const char *signature,
 	sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
 	sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
 	parse_gpg_output(sigc);
-	status |= sigc->result != 'G' && sigc->result != 'U';
+	status |= sigc->result != 'G';
+	status |= sigc->trust_level < configured_min_trust_level;
 
  out:
 	strbuf_release(&gpg_status);
@@ -251,6 +371,8 @@ int git_gpg_config(const char *var, const char *value, void *cb)
 {
 	struct gpg_format *fmt = NULL;
 	char *fmtname = NULL;
+	char *trust;
+	int ret;
 
 	if (!strcmp(var, "user.signingkey")) {
 		if (!value)
@@ -270,6 +392,20 @@ int git_gpg_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "gpg.mintrustlevel")) {
+		if (!value)
+			return config_error_nonbool(var);
+
+		trust = xstrdup_toupper(value);
+		ret = parse_gpg_trust_level(trust, &configured_min_trust_level);
+		free(trust);
+
+		if (ret)
+			return error("unsupported value for %s: %s", var,
+				     value);
+		return 0;
+	}
+
 	if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
 		fmtname = "openpgp";
 
@@ -298,11 +434,11 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 	size_t i, j, bottom;
 	struct strbuf gpg_status = STRBUF_INIT;
 
-	argv_array_pushl(&gpg.args,
-			 use_format->program,
-			 "--status-fd=2",
-			 "-bsau", signing_key,
-			 NULL);
+	strvec_pushl(&gpg.args,
+		     use_format->program,
+		     "--status-fd=2",
+		     "-bsau", signing_key,
+		     NULL);
 
 	bottom = signature->len;
 
@@ -331,51 +467,3 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
 
 	return 0;
 }
-
-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)
-{
-	struct child_process gpg = CHILD_PROCESS_INIT;
-	struct gpg_format *fmt;
-	struct tempfile *temp;
-	int ret;
-	struct strbuf buf = STRBUF_INIT;
-
-	temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
-	if (!temp)
-		return error_errno(_("could not create temporary file"));
-	if (write_in_full(temp->fd, signature, signature_size) < 0 ||
-	    close_tempfile_gently(temp) < 0) {
-		error_errno(_("failed writing detached signature to '%s'"),
-			    temp->filename.buf);
-		delete_tempfile(&temp);
-		return -1;
-	}
-
-	fmt = get_format_by_sig(signature);
-	if (!fmt)
-		BUG("bad signature '%s'", signature);
-
-	argv_array_push(&gpg.args, fmt->program);
-	argv_array_pushv(&gpg.args, fmt->verify_args);
-	argv_array_pushl(&gpg.args,
-			 "--status-fd=1",
-			 "--verify", temp->filename.buf, "-",
-			 NULL);
-
-	if (!gpg_status)
-		gpg_status = &buf;
-
-	sigchain_push(SIGPIPE, SIG_IGN);
-	ret = pipe_command(&gpg, payload, payload_size,
-			   gpg_status, 0, gpg_output, 0);
-	sigchain_pop(SIGPIPE);
-
-	delete_tempfile(&temp);
-
-	ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
-	strbuf_release(&buf); /* no matter it was used or not */
-
-	return ret;
-}