diff options
-rw-r--r-- | Documentation/config.txt | 10 | ||||
-rw-r--r-- | gpg-interface.c | 108 | ||||
-rwxr-xr-x | t/lib-gpg.sh | 28 | ||||
-rw-r--r-- | t/lib-gpg/gpgsm-gen-key.in | 8 | ||||
-rw-r--r-- | t/lib-gpg/gpgsm_cert.p12 | bin | 0 -> 2652 bytes | |||
-rwxr-xr-x | t/t4202-log.sh | 37 | ||||
-rwxr-xr-x | t/t5534-push-signed.sh | 63 | ||||
-rwxr-xr-x | t/t7004-tag.sh | 13 | ||||
-rwxr-xr-x | t/t7030-verify-tag.sh | 34 | ||||
-rwxr-xr-x | t/t7510-signed-commit.sh | 7 |
10 files changed, 285 insertions, 23 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 3a4ff5722c..fd8d27e761 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1884,6 +1884,16 @@ gpg.program:: signed, and the program is expected to send the result to its standard output. +gpg.format:: + Specifies which key format to use when signing with `--gpg-sign`. + Default is "openpgp" and another possible value is "x509". + +gpg.<format>.program:: + Use this to customize the program used for the signing format you + chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still + be used as a legacy synonym for `gpg.openpgp.program`. The default + value for `gpg.x509.program` is "gpgsm". + gui.commitMsgWidth:: Defines how wide the commit message window is in the linkgit:git-gui[1]. "75" is the default. diff --git a/gpg-interface.c b/gpg-interface.c index 09ddfbc267..bb8ea668b3 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -7,10 +7,64 @@ #include "tempfile.h" static char *configured_signing_key; -static const char *gpg_program = "gpg"; +struct gpg_format { + const char *name; + const char *program; + const char **verify_args; + const char **sigs; +}; + +static const char *openpgp_verify_args[] = { + "--keyid-format=long", + NULL +}; +static const char *openpgp_sigs[] = { + "-----BEGIN PGP SIGNATURE-----", + "-----BEGIN PGP MESSAGE-----", + NULL +}; + +static const char *x509_verify_args[] = { + NULL +}; +static const char *x509_sigs[] = { + "-----BEGIN SIGNED MESSAGE-----", + NULL +}; -#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" -#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----" +static struct gpg_format gpg_format[] = { + { .name = "openpgp", .program = "gpg", + .verify_args = openpgp_verify_args, + .sigs = openpgp_sigs + }, + { .name = "x509", .program = "gpgsm", + .verify_args = x509_verify_args, + .sigs = x509_sigs + }, +}; + +static struct gpg_format *use_format = &gpg_format[0]; + +static struct gpg_format *get_format_by_name(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(gpg_format); i++) + if (!strcmp(gpg_format[i].name, str)) + return gpg_format + i; + return NULL; +} + +static struct gpg_format *get_format_by_sig(const char *sig) +{ + int i, j; + + for (i = 0; i < ARRAY_SIZE(gpg_format); i++) + for (j = 0; gpg_format[i].sigs[j]; j++) + if (starts_with(sig, gpg_format[i].sigs[j])) + return gpg_format + i; + return NULL; +} void signature_check_clear(struct signature_check *sigc) { @@ -53,10 +107,11 @@ static void parse_gpg_output(struct signature_check *sigc) sigc->result = sigcheck_gpg_status[i].result; /* The trust messages are not followed by key/signer information */ if (sigc->result != 'U') { - sigc->key = xmemdupz(found, 16); + next = strchrnul(found, ' '); + sigc->key = xmemdupz(found, next - found); /* The ERRSIG message is not followed by signer information */ - if (sigc-> result != 'E') { - found += 17; + if (*next && sigc-> result != 'E') { + found = next + 1; next = strchrnul(found, '\n'); sigc->signer = xmemdupz(found, next - found); } @@ -101,12 +156,6 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags) fputs(output, stderr); } -static int is_gpg_start(const char *line) -{ - return starts_with(line, PGP_SIGNATURE) || - starts_with(line, PGP_MESSAGE); -} - size_t parse_signature(const char *buf, size_t size) { size_t len = 0; @@ -114,7 +163,7 @@ size_t parse_signature(const char *buf, size_t size) while (len < size) { const char *eol; - if (is_gpg_start(buf + len)) + if (get_format_by_sig(buf + len)) match = len; eol = memchr(buf + len, '\n', size - len); @@ -131,6 +180,9 @@ void set_signing_key(const char *key) int git_gpg_config(const char *var, const char *value, void *cb) { + struct gpg_format *fmt = NULL; + char *fmtname = NULL; + if (!strcmp(var, "user.signingkey")) { if (!value) return config_error_nonbool(var); @@ -138,13 +190,28 @@ int git_gpg_config(const char *var, const char *value, void *cb) return 0; } - if (!strcmp(var, "gpg.program")) { + if (!strcmp(var, "gpg.format")) { if (!value) return config_error_nonbool(var); - gpg_program = xstrdup(value); + fmt = get_format_by_name(value); + if (!fmt) + return error("unsupported value for %s: %s", + var, value); + use_format = fmt; return 0; } + if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program")) + fmtname = "openpgp"; + + if (!strcmp(var, "gpg.x509.program")) + fmtname = "x509"; + + if (fmtname) { + fmt = get_format_by_name(fmtname); + return git_config_string(&fmt->program, var, value); + } + return 0; } @@ -163,7 +230,7 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig struct strbuf gpg_status = STRBUF_INIT; argv_array_pushl(&gpg.args, - gpg_program, + use_format->program, "--status-fd=2", "-bsau", signing_key, NULL); @@ -201,6 +268,7 @@ int verify_signed_buffer(const char *payload, size_t payload_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; @@ -216,10 +284,14 @@ int verify_signed_buffer(const char *payload, size_t payload_size, 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, - gpg_program, "--status-fd=1", - "--keyid-format=long", "--verify", temp->filename.buf, "-", NULL); diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index a5d3b2cbaa..3fe02876c1 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -38,7 +38,33 @@ then "$TEST_DIRECTORY"/lib-gpg/ownertrust && gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \ --sign -u committer@example.com && - test_set_prereq GPG + test_set_prereq GPG && + # Available key info: + # * see t/lib-gpg/gpgsm-gen-key.in + # To generate new certificate: + # * no passphrase + # gpgsm --homedir /tmp/gpghome/ \ + # -o /tmp/gpgsm.crt.user \ + # --generate-key \ + # --batch t/lib-gpg/gpgsm-gen-key.in + # To import certificate: + # gpgsm --homedir /tmp/gpghome/ \ + # --import /tmp/gpgsm.crt.user + # To export into a .p12 we can later import: + # gpgsm --homedir /tmp/gpghome/ \ + # -o t/lib-gpg/gpgsm_cert.p12 \ + # --export-secret-key-p12 "committer@example.com" + echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \ + --passphrase-fd 0 --pinentry-mode loopback \ + --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 && + gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K \ + | grep fingerprint: | cut -d" " -f4 | tr -d '\n' > \ + ${GNUPGHOME}/trustlist.txt && + echo " S relax" >> ${GNUPGHOME}/trustlist.txt && + (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && + echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \ + -u committer@example.com -o /dev/null --sign - 2>&1 && + test_set_prereq GPGSM ;; esac fi diff --git a/t/lib-gpg/gpgsm-gen-key.in b/t/lib-gpg/gpgsm-gen-key.in new file mode 100644 index 0000000000..a7fd87c069 --- /dev/null +++ b/t/lib-gpg/gpgsm-gen-key.in @@ -0,0 +1,8 @@ +Key-Type: RSA +Key-Length: 2048 +Key-Usage: sign +Serial: random +Name-DN: CN=C O Mitter, O=Example, SN=C O, GN=Mitter +Name-Email: committer@example.com +Not-Before: 1970-01-01 00:00:00 +Not-After: 3000-01-01 00:00:00 diff --git a/t/lib-gpg/gpgsm_cert.p12 b/t/lib-gpg/gpgsm_cert.p12 Binary files differnew file mode 100644 index 0000000000..94ffad0d31 --- /dev/null +++ b/t/lib-gpg/gpgsm_cert.p12 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 25b1f8cc73..05d3707e38 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1556,12 +1556,28 @@ test_expect_success GPG 'setup signed branch' ' git commit -S -m signed_commit ' +test_expect_success GPGSM 'setup signed branch x509' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b signed-x509 master && + echo foo >foo && + git add foo && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git commit -S -m signed_commit +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual && grep "^| gpg: Good signature" actual ' +test_expect_success GPGSM 'log --graph --show-signature x509' ' + git log --graph --show-signature -n1 signed-x509 >actual && + grep "^| gpgsm: Signature made" actual && + grep "^| gpgsm: Good signature" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout master" && git checkout -b plain master && @@ -1581,6 +1597,27 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' ' grep "^| | gpg: Good signature" actual ' +test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' ' + test_when_finished "git reset --hard && git checkout master" && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git checkout -b plain-x509 master && + echo aaa >bar && + git add bar && + git commit -m bar_commit && + git checkout -b tagged-x509 master && + echo bbb >baz && + git add baz && + git commit -m baz_commit && + git tag -s -m signed_tag_msg signed_tag_x509 && + git checkout plain-x509 && + git merge --no-ff -m msg signed_tag_x509 && + git log --graph --show-signature -n1 plain-x509 >actual && + grep "^|\\\ merged tag" actual && + grep "^| | gpgsm: Signature made" actual && + grep "^| | gpgsm: Good signature" actual +' + test_expect_success GPG '--no-show-signature overrides --show-signature' ' git log -1 --show-signature --no-show-signature signed >actual && ! grep "^gpg:" actual diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index 1cea758f78..030331f1c5 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -194,10 +194,12 @@ test_expect_success GPG 'fail without key and heed user.signingkey' ' EOF - unset GIT_COMMITTER_EMAIL && - git config user.email hasnokey@nowhere.com && - test_must_fail git push --signed dst noop ff +noff && - git config user.signingkey committer@example.com && + test_config user.email hasnokey@nowhere.com && + ( + sane_unset GIT_COMMITTER_EMAIL && + test_must_fail git push --signed dst noop ff +noff + ) && + test_config user.signingkey $GIT_COMMITTER_EMAIL && git push --signed dst noop ff +noff && ( @@ -218,4 +220,57 @@ test_expect_success GPG 'fail without key and heed user.signingkey' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPGSM 'fail without key and heed user.signingkey x509' ' + test_config gpg.format x509 && + prepare_dst && + mkdir -p dst/.git/hooks && + git -C dst config receive.certnonceseed sekrit && + write_script dst/.git/hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + + cat >../push-cert-status <<E_O_F + SIGNER=${GIT_PUSH_CERT_SIGNER-nobody} + KEY=${GIT_PUSH_CERT_KEY-nokey} + STATUS=${GIT_PUSH_CERT_STATUS-nostatus} + NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus} + NONCE=${GIT_PUSH_CERT_NONCE-nononce} + E_O_F + + EOF + + test_config user.email hasnokey@nowhere.com && + test_config user.signingkey "" && + ( + sane_unset GIT_COMMITTER_EMAIL && + test_must_fail git push --signed dst noop ff +noff + ) && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git push --signed dst noop ff +noff && + + ( + cat <<-\EOF && + SIGNER=/CN=C O Mitter/O=Example/SN=C O/GN=Mitter + KEY= + STATUS=G + NONCE_STATUS=OK + EOF + sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert + ) >expect.in && + key=$(cat "${GNUPGHOME}/trustlist.txt" | cut -d" " -f1 | tr -d ":") && + sed -e "s/^KEY=/KEY=${key}/" expect.in >expect && + + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && + test_cmp expect dst/push-cert-status +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index d7b319e919..2147938aa1 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1354,6 +1354,19 @@ test_expect_success GPG \ 'test_config gpg.program echo && test_must_fail git tag -s -m tail tag-gpg-failure' +# try to sign with bad user.signingkey +test_expect_success GPGSM \ + 'git tag -s fails if gpgsm is misconfigured (bad key)' \ + 'test_config user.signingkey BobTheMouse && + test_config gpg.format x509 && + test_must_fail git tag -s -m tail tag-gpg-failure' + +# try to produce invalid signature +test_expect_success GPGSM \ + 'git tag -s fails if gpgsm is misconfigured (bad signature format)' \ + 'test_config gpg.x509.program echo && + test_config gpg.format x509 && + test_must_fail git tag -s -m tail tag-gpg-failure' # try to verify without gpg: diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 291a1e2b07..99f35a5bbe 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -41,6 +41,13 @@ test_expect_success GPG 'create signed tags' ' git tag -uB7227189 -m eighth eighth-signed-alt ' +test_expect_success GPGSM 'create signed tags x509 ' ' + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo 9 >file && test_tick && git commit -a -m "nineth gpgsm-signed" && + git tag -s -m nineth nineth-signed-x509 +' + test_expect_success GPG 'verify and show signatures' ' ( for tag in initial second merge fourth-signed sixth-signed seventh-signed @@ -72,6 +79,13 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPGSM 'verify and show signatures x509' ' + git verify-tag nineth-signed-x509 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual && + echo nineth-signed-x509 OK +' + test_expect_success GPG 'detect fudged signature' ' git cat-file tag seventh-signed >raw && sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && @@ -112,6 +126,13 @@ test_expect_success GPG 'verify signatures with --raw' ' ) ' +test_expect_success GPGSM 'verify signatures with --raw x509' ' + git verify-tag --raw nineth-signed-x509 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo nineth-signed-x509 OK +' + test_expect_success GPG 'verify multiple tags' ' tags="fourth-signed sixth-signed seventh-signed" && for i in $tags @@ -125,6 +146,19 @@ test_expect_success GPG 'verify multiple tags' ' test_cmp expect.stderr actual.stderr ' +test_expect_success GPGSM 'verify multiple tags x509' ' + tags="seventh-signed nineth-signed-x509" && + for i in $tags + do + git verify-tag -v --raw $i || return 1 + done >expect.stdout 2>expect.stderr.1 && + grep "^.GNUPG:." <expect.stderr.1 >expect.stderr && + git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 && + grep "^.GNUPG:." <actual.stderr.1 >actual.stderr && + test_cmp expect.stdout actual.stdout && + test_cmp expect.stderr actual.stderr +' + test_expect_success GPG 'verifying tag with --format' ' cat >expect <<-\EOF && tagname : fourth-signed diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 6e2015ed9a..4e37ff8f16 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -227,4 +227,11 @@ test_expect_success GPG 'log.showsignature behaves like --show-signature' ' grep "gpg: Good signature" actual ' +test_expect_success GPG 'check config gpg.format values' ' + test_config gpg.format openpgp && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + test_done |