From: Junio C Hamano Date: Wed, 8 Oct 2014 20:05:15 +0000 (-0700) Subject: Merge branch 'jc/push-cert' X-Git-Tag: v2.2.0-rc0~64 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fb06b5280ea05d75515fa780cf08d4ec9d6fe101;p=git Merge branch 'jc/push-cert' Allow "git push" request to be signed, so that it can be verified and audited, using the GPG signature of the person who pushed, that the tips of branches at a public repository really point the commits the pusher wanted to, without having to "trust" the server. * jc/push-cert: (24 commits) receive-pack::hmac_sha1(): copy the entire SHA-1 hash out signed push: allow stale nonce in stateless mode signed push: teach smart-HTTP to pass "git push --signed" around signed push: fortify against replay attacks signed push: add "pushee" header to push certificate signed push: remove duplicated protocol info send-pack: send feature request on push-cert packet receive-pack: GPG-validate push certificates push: the beginning of "git push --signed" pack-protocol doc: typofix for PKT-LINE gpg-interface: move parse_signature() to where it should be gpg-interface: move parse_gpg_output() to where it should be send-pack: clarify that cmds_sent is a boolean send-pack: refactor inspecting and resetting status and sending commands send-pack: rename "new_refs" to "need_pack_data" receive-pack: factor out capability string generation send-pack: factor out capability string generation send-pack: always send capabilities send-pack: refactor decision to send update per ref send-pack: move REF_STATUS_REJECT_NODELETE logic a bit higher ... --- fb06b5280ea05d75515fa780cf08d4ec9d6fe101 diff --cc Documentation/technical/pack-protocol.txt index 569c48a352,dda120631e..462e20645f --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@@ -465,9 -465,9 +465,9 @@@ contain all the objects that the serve references. ---- - update-request = *shallow command-list [pack-file] + update-request = *shallow ( command-list | push-cert ) [pack-file] - shallow = PKT-LINE("shallow" SP obj-id) + shallow = PKT-LINE("shallow" SP obj-id LF) command-list = PKT-LINE(command NUL capability-list LF) *PKT-LINE(command LF) diff --cc builtin/receive-pack.c index daf0600ca3,42f25a5103..a01ac2096a --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@@ -15,7 -15,8 +15,9 @@@ #include "connected.h" #include "argv-array.h" #include "version.h" + #include "tag.h" + #include "gpg-interface.h" +#include "sigchain.h" static const char receive_pack_usage[] = "git receive-pack "; diff --cc commit.c index 9c4439fed6,01cdad2626..19cf8f9c67 --- a/commit.c +++ b/commit.c @@@ -1214,43 -1220,7 +1214,7 @@@ free_return free(buf); } - static struct { - char result; - const char *check; - } sigcheck_gpg_status[] = { - { 'G', "\n[GNUPG:] GOODSIG " }, - { 'B', "\n[GNUPG:] BADSIG " }, - { 'U', "\n[GNUPG:] TRUST_NEVER" }, - { 'U', "\n[GNUPG:] TRUST_UNDEFINED" }, - }; - - static void parse_gpg_output(struct signature_check *sigc) - { - const char *buf = sigc->gpg_status; - int i; - - /* Iterate over all search strings */ - for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { - const char *found, *next; - - if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) { - found = strstr(buf, sigcheck_gpg_status[i].check); - if (!found) - continue; - found += strlen(sigcheck_gpg_status[i].check); - } - sigc->result = sigcheck_gpg_status[i].result; - /* The trust messages are not followed by key/signer information */ - if (sigc->result != 'U') { - sigc->key = xmemdupz(found, 16); - found += 17; - next = strchrnul(found, '\n'); - sigc->signer = xmemdupz(found, next - found); - } - } - } - -void check_commit_signature(const struct commit* commit, struct signature_check *sigc) +void check_commit_signature(const struct commit *commit, struct signature_check *sigc) { struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; diff --cc send-pack.c index 8b4cbf049c,7ad1a5968b..949cb61aa0 --- a/send-pack.c +++ b/send-pack.c @@@ -189,6 -191,94 +190,94 @@@ static void advertise_shallow_grafts_bu for_each_commit_graft(advertise_shallow_grafts_cb, sb); } + static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args) + { + if (!ref->peer_ref && !args->send_mirror) + return 0; + + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_REJECT_ALREADY_EXISTS: + case REF_STATUS_REJECT_FETCH_FIRST: + case REF_STATUS_REJECT_NEEDS_FORCE: + case REF_STATUS_REJECT_STALE: + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_UPTODATE: + return 0; + default: + return 1; + } + } + + /* + * the beginning of the next line, or the end of buffer. + * + * NEEDSWORK: perhaps move this to git-compat-util.h or somewhere and + * convert many similar uses found by "git grep -A4 memchr". + */ + static const char *next_line(const char *line, size_t len) + { + const char *nl = memchr(line, '\n', len); + if (!nl) + return line + len; /* incomplete line */ + return nl + 1; + } + + 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 *push_cert_nonce) + { + const struct ref *ref; - char stamp[60]; + char *signing_key = xstrdup(get_signing_key()); + const char *cp, *np; + struct strbuf cert = STRBUF_INIT; + int update_seen = 0; + - datestamp(stamp, sizeof(stamp)); + strbuf_addf(&cert, "certificate version 0.1\n"); - strbuf_addf(&cert, "pusher %s %s\n", signing_key, stamp); ++ strbuf_addf(&cert, "pusher %s ", signing_key); ++ datestamp(&cert); ++ strbuf_addch(&cert, '\n'); + if (args->url && *args->url) { + char *anon_url = transport_anonymize_url(args->url); + strbuf_addf(&cert, "pushee %s\n", anon_url); + free(anon_url); + } + 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) { + if (!ref_update_to_be_sent(ref, args)) + continue; + update_seen = 1; + strbuf_addf(&cert, "%s %s %s\n", + sha1_to_hex(ref->old_sha1), + sha1_to_hex(ref->new_sha1), + ref->name); + } + if (!update_seen) + goto free_return; + + if (sign_buffer(&cert, &cert, signing_key)) + die(_("failed to sign the push certificate")); + + packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string); + for (cp = cert.buf; cp < cert.buf + cert.len; cp = np) { + np = next_line(cp, cert.buf + cert.len - cp); + packet_buf_write(req_buf, + "%.*s", (int)(np - cp), cp); + } + packet_buf_write(req_buf, "push-cert-end\n"); + + free_return: + free(signing_key); + strbuf_release(&cert); + return update_seen; + } + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, diff --cc t/t5541-http-push-smart.sh index db1998873c,ffb3af4498..d2c681ebfd --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@@ -323,20 -324,45 +324,60 @@@ test_expect_success 'push into half-aut test_cmp expect actual ' +run_with_limited_cmdline () { + (ulimit -s 128 && "$@") +} + +test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true' + +test_expect_success CMDLINE_LIMIT 'push 2000 tags over http' ' + sha1=$(git rev-parse HEAD) && + test_seq 2000 | + sort | + sed "s|.*|$sha1 refs/tags/really-long-tag-name-&|" \ + >.git/packed-refs && + run_with_limited_cmdline git push --mirror +' + + test_expect_success GPG 'push with post-receive to inspect certificate' ' + ( + cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + mkdir -p hooks && + write_script hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + cat >../push-cert-status < + KEY=13B6F51ECDDE430D + STATUS=G + NONCE_STATUS=OK + EOF + sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" push-cert + ) >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status" + ' + stop_httpd test_done