Merge branch 'mg/gpg-parse-tighten'
Detect and reject a signature block that has more than one GPG signature. * mg/gpg-parse-tighten: gpg-interface.c: detect and reject multiple signatures on commits
This commit is contained in:
commit
02561896de
@ -75,48 +75,80 @@ void signature_check_clear(struct signature_check *sigc)
|
||||
FREE_AND_NULL(sigc->key);
|
||||
}
|
||||
|
||||
/* An exclusive status -- only one of them can appear in output */
|
||||
#define GPG_STATUS_EXCLUSIVE (1<<0)
|
||||
|
||||
static struct {
|
||||
char result;
|
||||
const char *check;
|
||||
unsigned int flags;
|
||||
} sigcheck_gpg_status[] = {
|
||||
{ 'G', "\n[GNUPG:] GOODSIG " },
|
||||
{ 'B', "\n[GNUPG:] BADSIG " },
|
||||
{ 'U', "\n[GNUPG:] TRUST_NEVER" },
|
||||
{ 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
|
||||
{ 'E', "\n[GNUPG:] ERRSIG "},
|
||||
{ 'X', "\n[GNUPG:] EXPSIG "},
|
||||
{ 'Y', "\n[GNUPG:] EXPKEYSIG "},
|
||||
{ 'R', "\n[GNUPG:] REVKEYSIG "},
|
||||
{ 'G', "GOODSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
{ 'B', "BADSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
{ 'U', "TRUST_NEVER", 0 },
|
||||
{ 'U', "TRUST_UNDEFINED", 0 },
|
||||
{ 'E', "ERRSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
{ 'X', "EXPSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
{ 'Y', "EXPKEYSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
{ 'R', "REVKEYSIG ", GPG_STATUS_EXCLUSIVE },
|
||||
};
|
||||
|
||||
static void parse_gpg_output(struct signature_check *sigc)
|
||||
{
|
||||
const char *buf = sigc->gpg_status;
|
||||
const char *line, *next;
|
||||
int i;
|
||||
int seen_exclusive_status = 0;
|
||||
|
||||
/* Iterate over all search strings */
|
||||
for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
|
||||
const char *found, *next;
|
||||
/* Iterate over all lines */
|
||||
for (line = buf; *line; line = strchrnul(line+1, '\n')) {
|
||||
while (*line == '\n')
|
||||
line++;
|
||||
/* Skip lines that don't start with GNUPG status */
|
||||
if (!skip_prefix(line, "[GNUPG:] ", &line))
|
||||
continue;
|
||||
|
||||
if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) {
|
||||
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;
|
||||
/* The trust messages are not followed by key/signer information */
|
||||
if (sigc->result != 'U') {
|
||||
next = strchrnul(found, ' ');
|
||||
sigc->key = xmemdupz(found, next - found);
|
||||
/* The ERRSIG message is not followed by signer information */
|
||||
if (*next && sigc-> result != 'E') {
|
||||
found = next + 1;
|
||||
next = strchrnul(found, '\n');
|
||||
sigc->signer = xmemdupz(found, next - found);
|
||||
/* 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)) {
|
||||
if (sigcheck_gpg_status[i].flags & GPG_STATUS_EXCLUSIVE) {
|
||||
if (seen_exclusive_status++)
|
||||
goto found_duplicate_status;
|
||||
}
|
||||
|
||||
sigc->result = sigcheck_gpg_status[i].result;
|
||||
/* The trust messages are not followed by key/signer information */
|
||||
if (sigc->result != 'U') {
|
||||
next = strchrnul(line, ' ');
|
||||
free(sigc->key);
|
||||
sigc->key = xmemdupz(line, next - line);
|
||||
/* The ERRSIG message is not followed by signer information */
|
||||
if (*next && sigc->result != 'E') {
|
||||
line = next + 1;
|
||||
next = strchrnul(line, '\n');
|
||||
free(sigc->signer);
|
||||
sigc->signer = xmemdupz(line, next - line);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
sigc->result = 'E';
|
||||
/* Clear partial data to avoid confusion */
|
||||
FREE_AND_NULL(sigc->signer);
|
||||
FREE_AND_NULL(sigc->key);
|
||||
}
|
||||
|
||||
int check_signature(const char *payload, size_t plen, const char *signature,
|
||||
|
@ -234,4 +234,30 @@ test_expect_success GPG 'check config gpg.format values' '
|
||||
test_must_fail git commit -S --amend -m "fail"
|
||||
'
|
||||
|
||||
test_expect_success GPG 'detect fudged commit with double signature' '
|
||||
sed -e "/gpgsig/,/END PGP/d" forged1 >double-base &&
|
||||
sed -n -e "/gpgsig/,/END PGP/p" forged1 | \
|
||||
sed -e "s/^gpgsig//;s/^ //" | gpg --dearmor >double-sig1.sig &&
|
||||
gpg -o double-sig2.sig -u 29472784 --detach-sign double-base &&
|
||||
cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc &&
|
||||
sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/gpgsig /;2,\$s/^/ /" \
|
||||
double-combined.asc > double-gpgsig &&
|
||||
sed -e "/committer/r double-gpgsig" double-base >double-commit &&
|
||||
git hash-object -w -t commit double-commit >double-commit.commit &&
|
||||
test_must_fail git verify-commit $(cat double-commit.commit) &&
|
||||
git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual &&
|
||||
grep "BAD signature from" double-actual &&
|
||||
grep "Good signature from" double-actual
|
||||
'
|
||||
|
||||
test_expect_success GPG 'show double signature with custom format' '
|
||||
cat >expect <<-\EOF &&
|
||||
E
|
||||
|
||||
|
||||
EOF
|
||||
git log -1 --format="%G?%n%GK%n%GS" $(cat double-commit.commit) >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user