Skip to content

Commit d05b961

Browse files
committed
receive-pack: GPG-validate push certificates
Reusing the GPG signature check helpers we already have, verify the signature in receive-pack and give the results to the hooks via GIT_PUSH_CERT_{SIGNER,KEY,STATUS} environment variables. Policy decisions, such as accepting or rejecting a good signature by a key that is not fully trusted, is left to the hook and kept outside of the core. Signed-off-by: Junio C Hamano <[email protected]>
1 parent a85b377 commit d05b961

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

Documentation/git-receive-pack.txt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,21 @@ sha1-old and sha1-new should be valid objects in the repository.
5656
When accepting a signed push (see linkgit:git-push[1]), the signed
5757
push certificate is stored in a blob and an environment variable
5858
`GIT_PUSH_CERT` can be consulted for its object name. See the
59-
description of `post-receive` hook for an example.
59+
description of `post-receive` hook for an example. In addition, the
60+
certificate is verified using GPG and the result is exported with
61+
the following environment variables:
62+
63+
`GIT_PUSH_CERT_SIGNER`::
64+
The name and the e-mail address of the owner of the key that
65+
signed the push certificate.
66+
67+
`GIT_PUSH_CERT_KEY`::
68+
The GPG key ID of the key that signed the push certificate.
69+
70+
`GIT_PUSH_CERT_STATUS`::
71+
The status of GPG verification of the push certificate,
72+
using the same mnemonic as used in `%G?` format of `git log`
73+
family of commands (see linkgit:git-log[1]).
6074

6175
This hook is called before any refname is updated and before any
6276
fast-forward checks are performed.
@@ -106,13 +120,13 @@ the update. Refs that were created will have sha1-old equal to
106120
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
107121
the repository.
108122

109-
The `GIT_PUSH_CERT` environment variable can be inspected, just as
123+
The `GIT_PUSH_CERT*` environment variables can be inspected, just as
110124
in `pre-receive` hook, after accepting a signed push.
111125

112126
Using this hook, it is easy to generate mails describing the updates
113127
to the repository. This example script sends one mail message per
114128
ref listing the commits pushed to the repository, and logs the push
115-
certificates of signed pushes to a logger
129+
certificates of signed pushes with good signatures to a logger
116130
service:
117131

118132
#!/bin/sh
@@ -130,11 +144,11 @@ service:
130144
mail -s "Changes to ref $ref" commit-list@mydomain
131145
done
132146
# log signed push certificate, if any
133-
if test -n "${GIT_PUSH_CERT-}"
147+
if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G
134148
then
135149
(
136150
git cat-file blob ${GIT_PUSH_CERT}
137-
) | mail -s "push certificate" push-log@mydomain
151+
) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain
138152
fi
139153
exit 0
140154

builtin/receive-pack.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "connected.h"
1616
#include "argv-array.h"
1717
#include "version.h"
18+
#include "tag.h"
19+
#include "gpg-interface.h"
1820

1921
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
2022

@@ -49,6 +51,7 @@ static const char *alt_shallow_file;
4951
static int accept_push_cert = 1;
5052
static struct strbuf push_cert = STRBUF_INIT;
5153
static unsigned char push_cert_sha1[20];
54+
static struct signature_check sigcheck;
5255

5356
static enum deny_action parse_deny_action(const char *var, const char *value)
5457
{
@@ -277,12 +280,40 @@ static void prepare_push_cert_sha1(struct child_process *proc)
277280
return;
278281

279282
if (!already_done) {
283+
struct strbuf gpg_output = STRBUF_INIT;
284+
struct strbuf gpg_status = STRBUF_INIT;
285+
int bogs /* beginning_of_gpg_sig */;
286+
280287
already_done = 1;
281288
if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
282289
hashclr(push_cert_sha1);
290+
291+
memset(&sigcheck, '\0', sizeof(sigcheck));
292+
sigcheck.result = 'N';
293+
294+
bogs = parse_signature(push_cert.buf, push_cert.len);
295+
if (verify_signed_buffer(push_cert.buf, bogs,
296+
push_cert.buf + bogs, push_cert.len - bogs,
297+
&gpg_output, &gpg_status) < 0) {
298+
; /* error running gpg */
299+
} else {
300+
sigcheck.payload = push_cert.buf;
301+
sigcheck.gpg_output = gpg_output.buf;
302+
sigcheck.gpg_status = gpg_status.buf;
303+
parse_gpg_output(&sigcheck);
304+
}
305+
306+
strbuf_release(&gpg_output);
307+
strbuf_release(&gpg_status);
283308
}
284309
if (!is_null_sha1(push_cert_sha1)) {
285310
argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
311+
argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
312+
sigcheck.signer ? sigcheck.signer : "");
313+
argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
314+
sigcheck.key ? sigcheck.key : "");
315+
argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
316+
286317
proc->env = env.argv;
287318
}
288319
}

t/t5534-push-signed.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,26 @@ test_expect_success GPG 'signed push sends push certificate' '
8383
if test -n "${GIT_PUSH_CERT-}"
8484
then
8585
git cat-file blob $GIT_PUSH_CERT >../push-cert
86-
fi
86+
fi &&
87+
88+
cat >../push-cert-status <<E_O_F
89+
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
90+
KEY=${GIT_PUSH_CERT_KEY-nokey}
91+
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
92+
E_O_F
93+
94+
EOF
95+
96+
cat >expect <<-\EOF &&
97+
SIGNER=C O Mitter <[email protected]>
98+
KEY=13B6F51ECDDE430D
99+
STATUS=G
87100
EOF
88101
89102
git push --signed dst noop ff +noff &&
90103
grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
91-
grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert
104+
grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
105+
test_cmp expect dst/push-cert-status
92106
'
93107

94108
test_done

0 commit comments

Comments
 (0)