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 <gitster@pobox.com>
Junio C Hamano committed
Aug 14, 2014 at 15:59 UTC
d05b9618ce42e85936176537f939a4eb85d4d65e
3 files changed
+66
-7
Documentation/git-receive-pack.txt
+19
-5
@@ -56,7 +56,21 @@ sha1-old and sha1-new should be valid objects in the repository.
56
When accepting a signed push (see linkgit:git-push[1]), the signed
57
push certificate is stored in a blob and an environment variable
58
`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]).
74
75
This hook is called before any refname is updated and before any
76
fast-forward checks are performed.
@@ -106,13 +120,13 @@ the update. Refs that were created will have sha1-old equal to
120
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
121
the repository.
122
109
-The `GIT_PUSH_CERT` environment variable can be inspected, just as
123
+The `GIT_PUSH_CERT*` environment variables can be inspected, just as
124
in `pre-receive` hook, after accepting a signed push.
125
126
Using this hook, it is easy to generate mails describing the updates
127
to the repository. This example script sends one mail message per
128
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
130
service:
131
132
#!/bin/sh
@@ -130,11 +144,11 @@ service:
144
mail -s "Changes to ref $ref" commit-list@mydomain
145
done
146
# 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
148
then
149
(
150
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
152
fi
153
exit 0
154
builtin/receive-pack.c
+31
@@ -15,6 +15,8 @@
15
#include "connected.h"
16
#include "argv-array.h"
17
#include "version.h"
18
+#include "tag.h"
19
+#include "gpg-interface.h"
20
21
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
22
@@ -49,6 +51,7 @@ static const char *alt_shallow_file;
51
static int accept_push_cert = 1;
52
static struct strbuf push_cert = STRBUF_INIT;
53
static unsigned char push_cert_sha1[20];
54
+static struct signature_check sigcheck;
55
56
static enum deny_action parse_deny_action(const char *var, const char *value)
57
{
@@ -277,12 +280,40 @@ static void prepare_push_cert_sha1(struct child_process *proc)
280
return;
281
282
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
+
287
already_done = 1;
288
if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
289
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);
308
}
309
if (!is_null_sha1(push_cert_sha1)) {
310
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
+
317
proc->env = env.argv;
318
}
319
}
t/t5534-push-signed.sh
+16
-2
@@ -83,12 +83,26 @@ test_expect_success GPG 'signed push sends push certificate' '
83
if test -n "${GIT_PUSH_CERT-}"
84
then
85
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 <committer@example.com>
98
+ KEY=13B6F51ECDDE430D
99
+ STATUS=G
100
EOF
101
102
git push --signed dst noop ff +noff &&
103
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
106
'
107
108
test_done