Skip to content

Commit 8e9ecf6

Browse files
committed
Merge branch 'hs/gpgsm' into pu
* 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 2263c7a + 069ff97 commit 8e9ecf6

File tree

10 files changed

+268
-19
lines changed

10 files changed

+268
-19
lines changed

Documentation/config.txt

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

1873+
gpg.format::
1874+
Specifies which key format to use when signing with `--gpg-sign`.
1875+
Default is "openpgp" and another possible value is "x509".
1876+
1877+
gpg.<format>.program::
1878+
Use this to customize the program used for the signing format you
1879+
chose. (see gpg.program) gpg.openpgp.program is a synonym for the
1880+
legacy gpg.program, while the default gpg.x509.program is "gpgsm".
1881+
1882+
18731883
gui.commitMsgWidth::
18741884
Defines how wide the commit message window is in the
18751885
linkgit:git-gui[1]. "75" is the default.

gpg-interface.c

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,52 @@
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 **extra_args_verify;
14+
const char **sigs;
15+
};
16+
17+
static const char *openpgp_verify_args[] = { "--keyid-format=long", NULL };
18+
static const char *openpgp_sigs[] = {
19+
"-----BEGIN PGP SIGNATURE-----",
20+
"-----BEGIN PGP MESSAGE-----", NULL };
21+
static const char *x509_verify_args[] = { NULL };
22+
static const char *x509_sigs[] = { "-----BEGIN SIGNED MESSAGE-----", NULL };
23+
24+
static struct gpg_format gpg_formats[] = {
25+
{ .name = "openpgp", .program = "gpg",
26+
.extra_args_verify = openpgp_verify_args,
27+
.sigs = openpgp_sigs
28+
},
29+
{ .name = "x509", .program = "gpgsm",
30+
.extra_args_verify = x509_verify_args,
31+
.sigs = x509_sigs
32+
},
33+
};
34+
static struct gpg_format *current_format = &gpg_formats[0];
35+
36+
static struct gpg_format *get_format_by_name(const char *str)
37+
{
38+
int i;
39+
40+
for (i = 0; i < ARRAY_SIZE(gpg_formats); i++)
41+
if (!strcasecmp(gpg_formats[i].name, str))
42+
return gpg_formats + i;
43+
return NULL;
44+
}
45+
46+
static struct gpg_format *get_format_by_sig(const char *sig)
47+
{
48+
int i, j;
1149

12-
#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
13-
#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
50+
for (i = 0; i < ARRAY_SIZE(gpg_formats); i++)
51+
for (j = 0; gpg_formats[i].sigs[j]; j++)
52+
if (starts_with(sig, gpg_formats[i].sigs[j]))
53+
return gpg_formats + i;
54+
return NULL;
55+
}
1456

1557
void signature_check_clear(struct signature_check *sigc)
1658
{
@@ -53,10 +95,11 @@ static void parse_gpg_output(struct signature_check *sigc)
5395
sigc->result = sigcheck_gpg_status[i].result;
5496
/* The trust messages are not followed by key/signer information */
5597
if (sigc->result != 'U') {
56-
sigc->key = xmemdupz(found, 16);
98+
next = strchrnul(found, ' ');
99+
sigc->key = xmemdupz(found, next - found);
57100
/* The ERRSIG message is not followed by signer information */
58-
if (sigc-> result != 'E') {
59-
found += 17;
101+
if (*next && sigc-> result != 'E') {
102+
found = next + 1;
60103
next = strchrnul(found, '\n');
61104
sigc->signer = xmemdupz(found, next - found);
62105
}
@@ -101,20 +144,14 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
101144
fputs(output, stderr);
102145
}
103146

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-
110147
size_t parse_signature(const char *buf, size_t size)
111148
{
112149
size_t len = 0;
113150
size_t match = size;
114151
while (len < size) {
115152
const char *eol;
116153

117-
if (is_gpg_start(buf + len))
154+
if (get_format_by_sig(buf + len))
118155
match = len;
119156

120157
eol = memchr(buf + len, '\n', size - len);
@@ -131,20 +168,37 @@ void set_signing_key(const char *key)
131168

132169
int git_gpg_config(const char *var, const char *value, void *cb)
133170
{
171+
struct gpg_format *fmt = NULL;
172+
char *fmtname = NULL;
173+
134174
if (!strcmp(var, "user.signingkey")) {
135175
if (!value)
136176
return config_error_nonbool(var);
137177
set_signing_key(value);
138178
return 0;
139179
}
140180

141-
if (!strcmp(var, "gpg.program")) {
181+
if (!strcmp(var, "gpg.format")) {
142182
if (!value)
143183
return config_error_nonbool(var);
144-
gpg_program = xstrdup(value);
184+
fmt = get_format_by_name(value);
185+
if (!fmt)
186+
return error("malformed value for %s: %s", var, value);
187+
current_format = fmt;
145188
return 0;
146189
}
147190

191+
if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
192+
fmtname = "openpgp";
193+
194+
if (!strcmp(var, "gpg.x509.program"))
195+
fmtname = "x509";
196+
197+
if (fmtname) {
198+
fmt = get_format_by_name(fmtname);
199+
return git_config_string(&fmt->program, var, value);
200+
}
201+
148202
return 0;
149203
}
150204

@@ -163,7 +217,7 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
163217
struct strbuf gpg_status = STRBUF_INIT;
164218

165219
argv_array_pushl(&gpg.args,
166-
gpg_program,
220+
current_format->program,
167221
"--status-fd=2",
168222
"-bsau", signing_key,
169223
NULL);
@@ -201,6 +255,7 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
201255
struct strbuf *gpg_output, struct strbuf *gpg_status)
202256
{
203257
struct child_process gpg = CHILD_PROCESS_INIT;
258+
struct gpg_format *fmt;
204259
struct tempfile *temp;
205260
int ret;
206261
struct strbuf buf = STRBUF_INIT;
@@ -216,10 +271,14 @@ int verify_signed_buffer(const char *payload, size_t payload_size,
216271
return -1;
217272
}
218273

274+
fmt = get_format_by_sig(signature);
275+
if (!fmt)
276+
BUG("bad signature '%s'", signature);
277+
278+
argv_array_push(&gpg.args, fmt->program);
279+
argv_array_pushv(&gpg.args, fmt->extra_args_verify);
219280
argv_array_pushl(&gpg.args,
220-
gpg_program,
221281
"--status-fd=1",
222-
"--keyid-format=long",
223282
"--verify", temp->filename.buf, "-",
224283
NULL);
225284

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 [email protected] &&
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 "[email protected]"
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 [email protected] -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: [email protected]
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: 39 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,29 @@ 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+
git config --unset gpg.format &&
1620+
git config --unset user.signingkey
1621+
'
1622+
15841623
test_expect_success GPG '--no-show-signature overrides --show-signature' '
15851624
git log -1 --show-signature --no-show-signature signed >actual &&
15861625
! grep "^gpg:" actual

t/t5534-push-signed.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,56 @@ test_expect_success GPG 'fail without key and heed user.signingkey' '
218218
test_cmp expect dst/push-cert-status
219219
'
220220

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