diff options
Diffstat (limited to 'third_party/git/gpg-interface.c')
-rw-r--r-- | third_party/git/gpg-interface.c | 135 |
1 files changed, 111 insertions, 24 deletions
diff --git a/third_party/git/gpg-interface.c b/third_party/git/gpg-interface.c index d60115ca404e..165274d74a84 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); @@ -195,6 +264,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 +274,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 +322,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 +343,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"; |