On Fri, Aug 22, 2014 at 4:30 PM, Junio C Hamano <[email protected]> wrote:
> In order to prevent a valid push certificate for pushing into an
> repository from getting replayed to push to an unrelated one, send a
> nonce string from the receive-pack process and have the signer
> include it in the push certificate. The original nonce is exported
> as GIT_PUSH_CERT_NONCE for the hooks to examine and match against
> the value on the "nonce" header in the certificate to notice a replay.
>
> Because the built-in nonce generation may not be suitable for all
> situations, allow the server to invoke receive-pack with pregenerated
> nonce from the command line argument.
>
> Signed-off-by: Junio C Hamano <[email protected]>
> ---
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index 991e417..8ad4d9b 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1226,12 +1232,28 @@ static int delete_only(struct command *commands)
> return 1;
> }
>
> +static char *prepare_push_cert_nonce(const char *sitename, const char *dir)
> +{
> + struct strbuf buf = STRBUF_INIT;
> + unsigned char sha1[20];
> +
> + if (!sitename) {
> + static char buf[1024];
Potentially confusing 'buf' shadows 'buf' in outer scope.
> + gethostname(buf, sizeof(buf));
> + sitename = buf;
> + }
> + strbuf_addf(&buf, "%s:%s:%lu", sitename, dir, time(NULL));
> + hash_sha1_file(buf.buf, buf.len, "blob", sha1);
strbuf_release(&buf);
> + return xstrdup(sha1_to_hex(sha1));
> +}
> +
> int cmd_receive_pack(int argc, const char **argv, const char *prefix)
> {
> int advertise_refs = 0;
> int stateless_rpc = 0;
> int i;
> const char *dir = NULL;
> + const char *sitename = NULL;
> struct command *commands;
> struct sha1_array shallow = SHA1_ARRAY_INIT;
> struct sha1_array ref = SHA1_ARRAY_INIT;
> @@ -1261,6 +1283,13 @@ int cmd_receive_pack(int argc, const char **argv,
> const char *prefix)
> fix_thin = 0;
> continue;
> }
> + if (skip_prefix(arg, "--sitename=", &sitename)) {
> + continue;
> + }
> + if (skip_prefix(arg, "--push-cert-nonce=",
> &push_cert_nonce)) {
> + push_cert_nonce = xstrdup(push_cert_nonce);
> + continue;
> + }
>
> usage(receive_pack_usage);
> }
> @@ -1277,6 +1306,8 @@ int cmd_receive_pack(int argc, const char **argv, const
> char *prefix)
> die("'%s' does not appear to be a git repository", dir);
>
> git_config(receive_pack_config, NULL);
> + if (!push_cert_nonce)
> + push_cert_nonce = prepare_push_cert_nonce(sitename, dir);
>
> if (0 <= transfer_unpack_limit)
> unpack_limit = transfer_unpack_limit;
> @@ -1321,5 +1352,6 @@ int cmd_receive_pack(int argc, const char **argv, const
> char *prefix)
> packet_flush(1);
> sha1_array_clear(&shallow);
> sha1_array_clear(&ref);
> + free((void *)push_cert_nonce);
> return 0;
> }
> diff --git a/send-pack.c b/send-pack.c
> index 61f321d..349393a 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -228,7 +228,8 @@ static const char *next_line(const char *line, size_t len)
> static int generate_push_cert(struct strbuf *req_buf,
> const struct ref *remote_refs,
> struct send_pack_args *args,
> - const char *cap_string)
> + const char *cap_string,
> + const char *push_cert_nonce)
> {
> const struct ref *ref;
> char stamp[60];
> @@ -240,6 +241,8 @@ static int generate_push_cert(struct strbuf *req_buf,
> datestamp(stamp, sizeof(stamp));
> strbuf_addf(&cert, "certificate version 0.1\n");
> strbuf_addf(&cert, "pusher %s %s\n", signing_key, stamp);
> + if (push_cert_nonce[0])
> + strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
> strbuf_addstr(&cert, "\n");
>
> for (ref = remote_refs; ref; ref = ref->next) {
> @@ -290,6 +293,8 @@ int send_pack(struct send_pack_args *args,
> unsigned cmds_sent = 0;
> int ret;
> struct async demux;
> + const char *push_cert_nonce = NULL;
> +
>
> /* Does the other end support the reporting? */
> if (server_supports("report-status"))
> @@ -306,8 +311,14 @@ int send_pack(struct send_pack_args *args,
> agent_supported = 1;
> if (server_supports("no-thin"))
> args->use_thin_pack = 0;
> - if (args->push_cert && !server_supports("push-cert"))
> - die(_("the receiving end does not support --signed push"));
> + if (args->push_cert) {
> + int len;
> +
> + push_cert_nonce = server_feature_value("push-cert", &len);
> + if (!push_cert_nonce)
> + die(_("the receiving end does not support --signed
> push"));
> + push_cert_nonce = xmemdupz(push_cert_nonce, len);
> + }
>
> if (!remote_refs) {
> fprintf(stderr, "No refs in common and none specified; doing
> nothing.\n"
> @@ -338,7 +349,7 @@ int send_pack(struct send_pack_args *args,
>
> if (!args->dry_run && args->push_cert)
> cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
> - cap_buf.buf);
> + cap_buf.buf, push_cert_nonce);
>
> /*
> * Clear the status for each ref and see if we need to send
> diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh
> index 659bca0..6db59ce 100755
> --- a/t/t5534-push-signed.sh
> +++ b/t/t5534-push-signed.sh
> @@ -58,17 +58,22 @@ test_expect_success GPG 'signed push sends push
> certificate' '
> SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
> KEY=${GIT_PUSH_CERT_KEY-nokey}
> STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
> + NONCE=${GIT_PUSH_CERT_NONCE-nononce}
> E_O_F
>
> EOF
>
> - cat >expect <<-\EOF &&
> - SIGNER=C O Mitter <[email protected]>
> - KEY=13B6F51ECDDE430D
> - STATUS=G
> - EOF
> -
> git push --signed dst noop ff +noff &&
> +
> + (
> + cat <<-\EOF &&
> + SIGNER=C O Mitter <[email protected]>
> + KEY=13B6F51ECDDE430D
> + STATUS=G
> + EOF
> + sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
> + ) >expect &&
> +
> grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
> grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
> test_cmp expect dst/push-cert-status
> --
> 2.1.0-304-g950f846
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html