Skip to content

Commit 3ec5ebe

Browse files
committed
Merge branch 'hs/gpgsm'
Teach "git tag -s" etc. a few configuration variables (gpg.format that can be set to "openpgp" or "x509", and gpg.<format>.program that is used to specify what program to use to deal with the format) to allow x.509 certs with CMS via "gpgsm" to be used instead of openpgp via "gnupg". * hs/gpgsm: gpg-interface t: extend the existing GPG tests with GPGSM gpg-interface: introduce new signature format "x509" using gpgsm gpg-interface: introduce new config to select per gpg format program gpg-interface: do not hardcode the key string len anymore gpg-interface: introduce an abstraction for multiple gpg formats t/t7510: check the validation of the new config gpg.format gpg-interface: add new config to select how to sign a commit
2 parents 2d7a202 + 53fc999 commit 3ec5ebe

File tree

10 files changed

+285
-23
lines changed

10 files changed

+285
-23
lines changed

Documentation/config.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,16 @@ gpg.program::
18841884
signed, and the program is expected to send the result to its
18851885
standard output.
18861886

1887+
gpg.format::
1888+
Specifies which key format to use when signing with `--gpg-sign`.
1889+
Default is "openpgp" and another possible value is "x509".
1890+
1891+
gpg.<format>.program::
1892+
Use this to customize the program used for the signing format you
1893+
chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
1894+
be used as a legacy synonym for `gpg.openpgp.program`. The default
1895+
value for `gpg.x509.program` is "gpgsm".
1896+
18871897
gui.commitMsgWidth::
18881898
Defines how wide the commit message window is in the
18891899
linkgit:git-gui[1]. "75" is the default.

gpg-interface.c

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,64 @@
77
#include "tempfile.h"
88

99
static char *configured_signing_key;
10-
static const char *gpg_program = "gpg";
10+
struct gpg_format {
11+
const char *name;
12+
const char *program;
13+
const char **verify_args;
14+
const char **sigs;
15+
};
16+
17+
static const char *openpgp_verify_args[] = {
18+
"--keyid-format=long",
19+
NULL
20+
};
21+
static const char *openpgp_sigs[] = {
22+
"-----BEGIN PGP SIGNATURE-----",
23+
"-----BEGIN PGP MESSAGE-----",
24+
NULL
25+
};
26+
27+
static const char *x509_verify_args[] = {
28+
NULL
29+
};
30+
static const char *x509_sigs[] = {
31+
"-----BEGIN SIGNED MESSAGE-----",
32+
NULL
33+
};
1134

12-
#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
13-
#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
35+
static struct gpg_format gpg_format[] = {
36+
{ .name = "openpgp", .program = "gpg",
37+
.verify_args = openpgp_verify_args,
38+
.sigs = openpgp_sigs
39+
},
40+
{ .name = "x509", .program = "gpgsm",
41+
.verify_args = x509_verify_args,
42+
.sigs = x509_sigs
43+
},
44+
};
45+
46+
static struct gpg_format *use_format = &gpg_format[0];
47+
48+
static struct gpg_format *get_format_by_name(const char *str)
49+
{
50+
int i;
51+
52+
for (i = 0; i < ARRAY_SIZE(gpg_format); i++)
53+
if (!strcmp(gpg_format[i].name, str))
54+
return gpg_format + i;
55+
return NULL;
56+
}
57+
58+
static struct gpg_format *get_format_by_sig(const char *sig)
59+
{
60+
int i, j;
61+
62+
for (i = 0; i < ARRAY_SIZE(gpg_format); i++)
63+
for (j = 0; gpg_format[i].sigs[j]; j++)
64+
if (starts_with(sig, gpg_format[i].sigs[j]))
65+
return gpg_format + i;
66+
return NULL;
67+
}
1468

1569
void signature_check_clear(struct signature_check *sigc)
1670
{
@@ -53,10 +107,11 @@ static void parse_gpg_output(struct signature_check *sigc)
53107
sigc->result = sigcheck_gpg_status[i].result;
54108
/* The trust messages are not followed by key/signer information */
55109
if (sigc->result != 'U') {
56-
sigc->key = xmemdupz(found, 16);
110+
next = strchrnul(found, ' ');
111+
sigc->key = xmemdupz(found, next - found);
57112
/* The ERRSIG message is not followed by signer information */
58-
if (sigc-> result != 'E') {
59-
found += 17;
113+
if (*next && sigc-> result != 'E') {
114+
found = next + 1;
60115
next = strchrnul(found, '\n');
61116
sigc->signer = xmemdupz(found, next - found);
62117
}
@@ -101,20 +156,14 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
101156
fputs(output, stderr);
102157
}
103158

104-
static int is_gpg_start(const char *line)
105-
{
106-
return starts_with(line, PGP_SIGNATURE) ||
107-
starts_with(line, PGP_MESSAGE);
108-
}
109-
110159
size_t parse_signature(const char *buf, size_t size)
111160
{
112161
size_t len = 0;
113162
size_t match = size;
114163
while (len < size) {
115164
const char *eol;
116165

117-
if (is_gpg_start(buf + len))
166+
if (get_format_by_sig(buf + len))
118167
match = len;
119168

120169
eol = memchr(buf + len, '\n', size - len);
@@ -131,20 +180,38 @@ void set_signing_key(const char *key)
131180

132181
int git_gpg_config(const char *var, const char *value, void *cb)
133182
{
183+
struct gpg_format *fmt = NULL;
184+
char *fmtname = NULL;
185+
134186
if (!strcmp(var, "user.signingkey")) {
135187
if (!value)
136188
return config_error_nonbool(var);
137189
set_signing_key(value);
138190
return 0;
139191
}
140192

141-
if (!strcmp(var, "gpg.program")) {
193+
if (!strcmp(var, "gpg.format")) {
142194
if (!value)
143195
return config_error_nonbool(var);
144-
gpg_program = xstrdup(value);
196+
fmt = get_format_by_name(value);
197+
if (!fmt)
198+
return error("unsupported value for %s: %s",
199+
var, value);
200+
use_format = fmt;
145201
return 0;
146202
}
147203

204+
if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
205+
fmtname = "openpgp";
206+
207+
if (!strcmp(var, "gpg.x509.program"))
208+
fmtname = "x509";
209+
210+
if (fmtname) {
211+
fmt = get_format_by_name(fmtname);
212+
return git_config_string(&fmt->program, var, value);
213+
}
214+
148215
return 0;
149216
}
150217

@@ -163,7 +230,7 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
163230
struct strbuf gpg_status = STRBUF_INIT;
164231

165232
argv_array_pushl(&gpg.args,
166-
gpg_program,
233+
use_format->program,
167234
"--status-fd=2",
168235
"-bsau", signing_key,
169236
NULL);
@@ -201,6 +268,7 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
201268
struct strbuf *gpg_output, struct strbuf *gpg_status)
202269
{
203270
struct child_process gpg = CHILD_PROCESS_INIT;
271+
struct gpg_format *fmt;
204272
struct tempfile *temp;
205273
int ret;
206274
struct strbuf buf = STRBUF_INIT;
@@ -216,10 +284,14 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
216284
return -1;
217285
}
218286

287+
fmt = get_format_by_sig(signature);
288+
if (!fmt)
289+
BUG("bad signature '%s'", signature);
290+
291+
argv_array_push(&gpg.args, fmt->program);
292+
argv_array_pushv(&gpg.args, fmt->verify_args);
219293
argv_array_pushl(&gpg.args,
220-
gpg_program,
221294
"--status-fd=1",
222-
"--keyid-format=long",
223295
"--verify", temp->filename.buf, "-",
224296
NULL);
225297

t/lib-gpg.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,33 @@ then
3838
"$TEST_DIRECTORY"/lib-gpg/ownertrust &&
3939
gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \
4040
--sign -u committer@example.com &&
41-
test_set_prereq GPG
41+
test_set_prereq GPG &&
42+
# Available key info:
43+
# * see t/lib-gpg/gpgsm-gen-key.in
44+
# To generate new certificate:
45+
# * no passphrase
46+
# gpgsm --homedir /tmp/gpghome/ \
47+
# -o /tmp/gpgsm.crt.user \
48+
# --generate-key \
49+
# --batch t/lib-gpg/gpgsm-gen-key.in
50+
# To import certificate:
51+
# gpgsm --homedir /tmp/gpghome/ \
52+
# --import /tmp/gpgsm.crt.user
53+
# To export into a .p12 we can later import:
54+
# gpgsm --homedir /tmp/gpghome/ \
55+
# -o t/lib-gpg/gpgsm_cert.p12 \
56+
# --export-secret-key-p12 "committer@example.com"
57+
echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \
58+
--passphrase-fd 0 --pinentry-mode loopback \
59+
--import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 &&
60+
gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K \
61+
| grep fingerprint: | cut -d" " -f4 | tr -d '\n' > \
62+
${GNUPGHOME}/trustlist.txt &&
63+
echo " S relax" >> ${GNUPGHOME}/trustlist.txt &&
64+
(gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) &&
65+
echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \
66+
-u committer@example.com -o /dev/null --sign - 2>&1 &&
67+
test_set_prereq GPGSM
4268
;;
4369
esac
4470
fi

t/lib-gpg/gpgsm-gen-key.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Key-Type: RSA
2+
Key-Length: 2048
3+
Key-Usage: sign
4+
Serial: random
5+
Name-DN: CN=C O Mitter, O=Example, SN=C O, GN=Mitter
6+
Name-Email: committer@example.com
7+
Not-Before: 1970-01-01 00:00:00
8+
Not-After: 3000-01-01 00:00:00

t/lib-gpg/gpgsm_cert.p12

2.59 KB
Binary file not shown.

t/t4202-log.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,12 +1556,28 @@ test_expect_success GPG 'setup signed branch' '
15561556
git commit -S -m signed_commit
15571557
'
15581558

1559+
test_expect_success GPGSM 'setup signed branch x509' '
1560+
test_when_finished "git reset --hard && git checkout master" &&
1561+
git checkout -b signed-x509 master &&
1562+
echo foo >foo &&
1563+
git add foo &&
1564+
test_config gpg.format x509 &&
1565+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
1566+
git commit -S -m signed_commit
1567+
'
1568+
15591569
test_expect_success GPG 'log --graph --show-signature' '
15601570
git log --graph --show-signature -n1 signed >actual &&
15611571
grep "^| gpg: Signature made" actual &&
15621572
grep "^| gpg: Good signature" actual
15631573
'
15641574

1575+
test_expect_success GPGSM 'log --graph --show-signature x509' '
1576+
git log --graph --show-signature -n1 signed-x509 >actual &&
1577+
grep "^| gpgsm: Signature made" actual &&
1578+
grep "^| gpgsm: Good signature" actual
1579+
'
1580+
15651581
test_expect_success GPG 'log --graph --show-signature for merged tag' '
15661582
test_when_finished "git reset --hard && git checkout master" &&
15671583
git checkout -b plain master &&
@@ -1581,6 +1597,27 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' '
15811597
grep "^| | gpg: Good signature" actual
15821598
'
15831599

1600+
test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' '
1601+
test_when_finished "git reset --hard && git checkout master" &&
1602+
test_config gpg.format x509 &&
1603+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
1604+
git checkout -b plain-x509 master &&
1605+
echo aaa >bar &&
1606+
git add bar &&
1607+
git commit -m bar_commit &&
1608+
git checkout -b tagged-x509 master &&
1609+
echo bbb >baz &&
1610+
git add baz &&
1611+
git commit -m baz_commit &&
1612+
git tag -s -m signed_tag_msg signed_tag_x509 &&
1613+
git checkout plain-x509 &&
1614+
git merge --no-ff -m msg signed_tag_x509 &&
1615+
git log --graph --show-signature -n1 plain-x509 >actual &&
1616+
grep "^|\\\ merged tag" actual &&
1617+
grep "^| | gpgsm: Signature made" actual &&
1618+
grep "^| | gpgsm: Good signature" actual
1619+
'
1620+
15841621
test_expect_success GPG '--no-show-signature overrides --show-signature' '
15851622
git log -1 --show-signature --no-show-signature signed >actual &&
15861623
! grep "^gpg:" actual

t/t5534-push-signed.sh

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,12 @@ test_expect_success GPG 'fail without key and heed user.signingkey' '
194194
195195
EOF
196196
197-
unset GIT_COMMITTER_EMAIL &&
198-
git config user.email hasnokey@nowhere.com &&
199-
test_must_fail git push --signed dst noop ff +noff &&
200-
git config user.signingkey committer@example.com &&
197+
test_config user.email hasnokey@nowhere.com &&
198+
(
199+
sane_unset GIT_COMMITTER_EMAIL &&
200+
test_must_fail git push --signed dst noop ff +noff
201+
) &&
202+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
201203
git push --signed dst noop ff +noff &&
202204
203205
(
@@ -218,4 +220,57 @@ test_expect_success GPG 'fail without key and heed user.signingkey' '
218220
test_cmp expect dst/push-cert-status
219221
'
220222

223+
test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
224+
test_config gpg.format x509 &&
225+
prepare_dst &&
226+
mkdir -p dst/.git/hooks &&
227+
git -C dst config receive.certnonceseed sekrit &&
228+
write_script dst/.git/hooks/post-receive <<-\EOF &&
229+
# discard the update list
230+
cat >/dev/null
231+
# record the push certificate
232+
if test -n "${GIT_PUSH_CERT-}"
233+
then
234+
git cat-file blob $GIT_PUSH_CERT >../push-cert
235+
fi &&
236+
237+
cat >../push-cert-status <<E_O_F
238+
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
239+
KEY=${GIT_PUSH_CERT_KEY-nokey}
240+
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
241+
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
242+
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
243+
E_O_F
244+
245+
EOF
246+
247+
test_config user.email hasnokey@nowhere.com &&
248+
test_config user.signingkey "" &&
249+
(
250+
sane_unset GIT_COMMITTER_EMAIL &&
251+
test_must_fail git push --signed dst noop ff +noff
252+
) &&
253+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
254+
git push --signed dst noop ff +noff &&
255+
256+
(
257+
cat <<-\EOF &&
258+
SIGNER=/CN=C O Mitter/O=Example/SN=C O/GN=Mitter
259+
KEY=
260+
STATUS=G
261+
NONCE_STATUS=OK
262+
EOF
263+
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
264+
) >expect.in &&
265+
key=$(cat "${GNUPGHOME}/trustlist.txt" | cut -d" " -f1 | tr -d ":") &&
266+
sed -e "s/^KEY=/KEY=${key}/" expect.in >expect &&
267+
268+
noop=$(git rev-parse noop) &&
269+
ff=$(git rev-parse ff) &&
270+
noff=$(git rev-parse noff) &&
271+
grep "$noop $ff refs/heads/ff" dst/push-cert &&
272+
grep "$noop $noff refs/heads/noff" dst/push-cert &&
273+
test_cmp expect dst/push-cert-status
274+
'
275+
221276
test_done

t/t7004-tag.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,19 @@ test_expect_success GPG \
13541354
'test_config gpg.program echo &&
13551355
test_must_fail git tag -s -m tail tag-gpg-failure'
13561356

1357+
# try to sign with bad user.signingkey
1358+
test_expect_success GPGSM \
1359+
'git tag -s fails if gpgsm is misconfigured (bad key)' \
1360+
'test_config user.signingkey BobTheMouse &&
1361+
test_config gpg.format x509 &&
1362+
test_must_fail git tag -s -m tail tag-gpg-failure'
1363+
1364+
# try to produce invalid signature
1365+
test_expect_success GPGSM \
1366+
'git tag -s fails if gpgsm is misconfigured (bad signature format)' \
1367+
'test_config gpg.x509.program echo &&
1368+
test_config gpg.format x509 &&
1369+
test_must_fail git tag -s -m tail tag-gpg-failure'
13571370

13581371
# try to verify without gpg:
13591372

0 commit comments

Comments
 (0)