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>
This commit is contained in:
parent
a85b377d04
commit
d05b9618ce
@ -56,7 +56,21 @@ sha1-old and sha1-new should be valid objects in the repository.
|
||||
When accepting a signed push (see linkgit:git-push[1]), the signed
|
||||
push certificate is stored in a blob and an environment variable
|
||||
`GIT_PUSH_CERT` can be consulted for its object name. See the
|
||||
description of `post-receive` hook for an example.
|
||||
description of `post-receive` hook for an example. In addition, the
|
||||
certificate is verified using GPG and the result is exported with
|
||||
the following environment variables:
|
||||
|
||||
`GIT_PUSH_CERT_SIGNER`::
|
||||
The name and the e-mail address of the owner of the key that
|
||||
signed the push certificate.
|
||||
|
||||
`GIT_PUSH_CERT_KEY`::
|
||||
The GPG key ID of the key that signed the push certificate.
|
||||
|
||||
`GIT_PUSH_CERT_STATUS`::
|
||||
The status of GPG verification of the push certificate,
|
||||
using the same mnemonic as used in `%G?` format of `git log`
|
||||
family of commands (see linkgit:git-log[1]).
|
||||
|
||||
This hook is called before any refname is updated and before any
|
||||
fast-forward checks are performed.
|
||||
@ -106,13 +120,13 @@ the update. Refs that were created will have sha1-old equal to
|
||||
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
|
||||
the repository.
|
||||
|
||||
The `GIT_PUSH_CERT` environment variable can be inspected, just as
|
||||
The `GIT_PUSH_CERT*` environment variables can be inspected, just as
|
||||
in `pre-receive` hook, after accepting a signed push.
|
||||
|
||||
Using this hook, it is easy to generate mails describing the updates
|
||||
to the repository. This example script sends one mail message per
|
||||
ref listing the commits pushed to the repository, and logs the push
|
||||
certificates of signed pushes to a logger
|
||||
certificates of signed pushes with good signatures to a logger
|
||||
service:
|
||||
|
||||
#!/bin/sh
|
||||
@ -130,11 +144,11 @@ service:
|
||||
mail -s "Changes to ref $ref" commit-list@mydomain
|
||||
done
|
||||
# log signed push certificate, if any
|
||||
if test -n "${GIT_PUSH_CERT-}"
|
||||
if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G
|
||||
then
|
||||
(
|
||||
git cat-file blob ${GIT_PUSH_CERT}
|
||||
) | mail -s "push certificate" push-log@mydomain
|
||||
) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain
|
||||
fi
|
||||
exit 0
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "connected.h"
|
||||
#include "argv-array.h"
|
||||
#include "version.h"
|
||||
#include "tag.h"
|
||||
#include "gpg-interface.h"
|
||||
|
||||
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
|
||||
|
||||
@ -49,6 +51,7 @@ static const char *alt_shallow_file;
|
||||
static int accept_push_cert = 1;
|
||||
static struct strbuf push_cert = STRBUF_INIT;
|
||||
static unsigned char push_cert_sha1[20];
|
||||
static struct signature_check sigcheck;
|
||||
|
||||
static enum deny_action parse_deny_action(const char *var, const char *value)
|
||||
{
|
||||
@ -277,12 +280,40 @@ static void prepare_push_cert_sha1(struct child_process *proc)
|
||||
return;
|
||||
|
||||
if (!already_done) {
|
||||
struct strbuf gpg_output = STRBUF_INIT;
|
||||
struct strbuf gpg_status = STRBUF_INIT;
|
||||
int bogs /* beginning_of_gpg_sig */;
|
||||
|
||||
already_done = 1;
|
||||
if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
|
||||
hashclr(push_cert_sha1);
|
||||
|
||||
memset(&sigcheck, '\0', sizeof(sigcheck));
|
||||
sigcheck.result = 'N';
|
||||
|
||||
bogs = parse_signature(push_cert.buf, push_cert.len);
|
||||
if (verify_signed_buffer(push_cert.buf, bogs,
|
||||
push_cert.buf + bogs, push_cert.len - bogs,
|
||||
&gpg_output, &gpg_status) < 0) {
|
||||
; /* error running gpg */
|
||||
} else {
|
||||
sigcheck.payload = push_cert.buf;
|
||||
sigcheck.gpg_output = gpg_output.buf;
|
||||
sigcheck.gpg_status = gpg_status.buf;
|
||||
parse_gpg_output(&sigcheck);
|
||||
}
|
||||
|
||||
strbuf_release(&gpg_output);
|
||||
strbuf_release(&gpg_status);
|
||||
}
|
||||
if (!is_null_sha1(push_cert_sha1)) {
|
||||
argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
|
||||
argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
|
||||
sigcheck.signer ? sigcheck.signer : "");
|
||||
argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
|
||||
sigcheck.key ? sigcheck.key : "");
|
||||
argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
|
||||
|
||||
proc->env = env.argv;
|
||||
}
|
||||
}
|
||||
|
@ -83,12 +83,26 @@ test_expect_success GPG 'signed push sends push certificate' '
|
||||
if test -n "${GIT_PUSH_CERT-}"
|
||||
then
|
||||
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
||||
fi
|
||||
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}
|
||||
E_O_F
|
||||
|
||||
EOF
|
||||
|
||||
cat >expect <<-\EOF &&
|
||||
SIGNER=C O Mitter <committer@example.com>
|
||||
KEY=13B6F51ECDDE430D
|
||||
STATUS=G
|
||||
EOF
|
||||
|
||||
git push --signed dst noop ff +noff &&
|
||||
grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
|
||||
grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert
|
||||
grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
|
||||
test_cmp expect dst/push-cert-status
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Loading…
Reference in New Issue
Block a user