From e543b3f6fee9f910573d53ddfc0750cd2bf88972 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Aug 2014 14:23:55 -0700 Subject: pack-protocol doc: typofix for PKT-LINE Everywhere else we use PKT-LINE to denote the pkt-line formatted data, but "shallow/deepen" messages are described with PKT_LINE(). Fix them. Signed-off-by: Junio C Hamano --- Documentation/technical/pack-protocol.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation/technical') diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 18dea8d15f..a845d51b1b 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -212,9 +212,9 @@ out of what the server said it could do with the first 'want' line. want-list = first-want *additional-want - shallow-line = PKT_LINE("shallow" SP obj-id) + shallow-line = PKT-LINE("shallow" SP obj-id) - depth-request = PKT_LINE("deepen" SP depth) + depth-request = PKT-LINE("deepen" SP depth) first-want = PKT-LINE("want" SP obj-id SP capability-list LF) additional-want = PKT-LINE("want" SP obj-id LF) -- cgit v1.3 From 4adf569dea052dac88121d822e11c249986b3398 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Aug 2014 14:38:45 -0700 Subject: signed push: remove duplicated protocol info With the interim protocol, we used to send the update commands even though we already send a signed copy of the same information when push certificate is in use. Update the send-pack/receive-pack pair not to do so. The notable thing on the receive-pack side is that it makes sure that there is no command sent over the traditional protocol packet outside the push certificate. Otherwise a pusher can claim to be pushing one set of ref updates in the signed certificate while issuing commands to update unrelated refs, and such an update will evade later audits. Finally, start documenting the protocol. Signed-off-by: Junio C Hamano --- Documentation/technical/pack-protocol.txt | 33 ++++++++++++++++++++++- Documentation/technical/protocol-capabilities.txt | 12 +++++++-- builtin/receive-pack.c | 26 ++++++++++++++++++ send-pack.c | 2 +- 4 files changed, 69 insertions(+), 4 deletions(-) (limited to 'Documentation/technical') diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index a845d51b1b..4a5c2e8636 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -465,7 +465,7 @@ contain all the objects that the server will need to complete the new references. ---- - update-request = *shallow command-list [pack-file] + update-request = *shallow ( command-list | push-cert ) [pack-file] shallow = PKT-LINE("shallow" SP obj-id) @@ -481,12 +481,25 @@ references. old-id = obj-id new-id = obj-id + push-cert = PKT-LINE("push-cert" NUL capability-list LF) + PKT-LINE("certificate version 0.1" LF) + PKT-LINE("pusher" SP ident LF) + PKT-LINE(LF) + *PKT-LINE(command LF) + *PKT-LINE(gpg-signature-lines LF) + PKT-LINE("push-cert-end" LF) + pack-file = "PACK" 28*(OCTET) ---- If the receiving end does not support delete-refs, the sending end MUST NOT ask for delete command. +If the receiving end does not support push-cert, the sending end +MUST NOT send a push-cert command. When a push-cert command is +sent, command-list MUST NOT be sent; the commands recorded in the +push certificate is used instead. + The pack-file MUST NOT be sent if the only command used is 'delete'. A pack-file MUST be sent if either create or update command is used, @@ -501,6 +514,24 @@ was being processed (the obj-id is still the same as the old-id), and it will run any update hooks to make sure that the update is acceptable. If all of that is fine, the server will then update the references. +Push Certificate +---------------- + +A push certificate begins with a set of header lines. After the +header and an empty line, the protocol commands follow, one per +line. + +Currently, the following header fields are defined: + +`pusher` ident:: + Identify the GPG key in "Human Readable Name " + format. + +The GPG signature lines are a detached signature for the contents +recorded in the push certificate before the signature block begins. +The detached signature is used to certify that the commands were +given by the pusher, who must be the signer. + Report Status ------------- diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index e174343847..a478cc4135 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -18,8 +18,8 @@ was sent. Server MUST NOT ignore capabilities that client requested and server advertised. As a consequence of these rules, server MUST NOT advertise capabilities it does not understand. -The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and -recognized by the receive-pack (push to server) process. +The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities +are sent and recognized by the receive-pack (push to server) process. The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized by both upload-pack and receive-pack protocols. The 'agent' capability @@ -250,3 +250,11 @@ allow-tip-sha1-in-want If the upload-pack server advertises this capability, fetch-pack may send "want" lines with SHA-1s that exist at the server but are not advertised by upload-pack. + +push-cert +--------- + +The receive-pack server that advertises this capability is willing +to accept a signed push certificate. A send-pack client MUST NOT +send a push-cert packet unless the receive-pack server advertises +this capability. diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c0a3189943..431af39335 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -926,6 +926,28 @@ static struct command **queue_command(struct command **tail, return &cmd->next; } +static void queue_commands_from_cert(struct command **tail, + struct strbuf *push_cert) +{ + const char *boc, *eoc; + + if (*tail) + die("protocol error: got both push certificate and unsigned commands"); + + boc = strstr(push_cert->buf, "\n\n"); + if (!boc) + die("malformed push certificate %.*s", 100, push_cert->buf); + else + boc += 2; + eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); + + while (boc < eoc) { + const char *eol = memchr(boc, '\n', eoc - boc); + tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol); + boc = eol ? eol + 1 : eoc; + } +} + static struct command *read_head_info(struct sha1_array *shallow) { struct command *commands = NULL; @@ -981,6 +1003,10 @@ static struct command *read_head_info(struct sha1_array *shallow) p = queue_command(p, line, linelen); } + + if (push_cert.len) + queue_commands_from_cert(p, &push_cert); + return commands; } diff --git a/send-pack.c b/send-pack.c index d392f5b3a0..857beb393d 100644 --- a/send-pack.c +++ b/send-pack.c @@ -363,7 +363,7 @@ int send_pack(struct send_pack_args *args, for (ref = remote_refs; ref; ref = ref->next) { char *old_hex, *new_hex; - if (args->dry_run) + if (args->dry_run || args->push_cert) continue; if (!ref_update_to_be_sent(ref, args)) -- cgit v1.3 From 9be89160e7382a88e56a02bcf38f4694dd6542d6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Aug 2014 18:15:24 -0700 Subject: signed push: add "pushee" header to push certificate Record the URL of the intended recipient for a push (after anonymizing it if it has authentication material) on a new "pushee URL" header. Because the networking configuration (SSH-tunnels, proxies, etc.) on the pushing user's side varies, the receiving repository may not know the single canonical URL all the pushing users would refer it as (besides, many sites allow pushing over ssh://host/path and https://host/path protocols to the same repository but with different local part of the path). So this value may not be reliably used for replay-attack prevention purposes, but this will still serve as a human readable hint to identify the repository the certificate refers to. Signed-off-by: Junio C Hamano --- Documentation/technical/pack-protocol.txt | 6 ++++++ send-pack.c | 5 +++++ send-pack.h | 1 + transport.c | 1 + 4 files changed, 13 insertions(+) (limited to 'Documentation/technical') diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 4a5c2e8636..7b543dc311 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -484,6 +484,7 @@ references. push-cert = PKT-LINE("push-cert" NUL capability-list LF) PKT-LINE("certificate version 0.1" LF) PKT-LINE("pusher" SP ident LF) + PKT-LINE("pushee" SP url LF) PKT-LINE(LF) *PKT-LINE(command LF) *PKT-LINE(gpg-signature-lines LF) @@ -527,6 +528,11 @@ Currently, the following header fields are defined: Identify the GPG key in "Human Readable Name " format. +`pushee` url:: + The repository URL (anonymized, if the URL contains + authentication material) the user who ran `git push` + intended to push into. + The GPG signature lines are a detached signature for the contents recorded in the push certificate before the signature block begins. The detached signature is used to certify that the commands were diff --git a/send-pack.c b/send-pack.c index 857beb393d..9c2c64966d 100644 --- a/send-pack.c +++ b/send-pack.c @@ -240,6 +240,11 @@ 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 (args->url && *args->url) { + char *anon_url = transport_anonymize_url(args->url); + strbuf_addf(&cert, "pushee %s\n", anon_url); + free(anon_url); + } strbuf_addstr(&cert, "\n"); for (ref = remote_refs; ref; ref = ref->next) { diff --git a/send-pack.h b/send-pack.h index 3555d8e8ad..5635457746 100644 --- a/send-pack.h +++ b/send-pack.h @@ -2,6 +2,7 @@ #define SEND_PACK_H struct send_pack_args { + const char *url; unsigned verbose:1, quiet:1, porcelain:1, diff --git a/transport.c b/transport.c index 07fdf86494..1df13753a6 100644 --- a/transport.c +++ b/transport.c @@ -827,6 +827,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); args.push_cert = !!(flags & TRANSPORT_PUSH_CERT); + args.url = transport->url; ret = send_pack(&args, data->fd, data->conn, remote_refs, &data->extra_have); -- cgit v1.3 From b89363e4a5277038629491f8765c0598f366326c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 21 Aug 2014 16:45:30 -0700 Subject: signed push: fortify against replay attacks In order to prevent a valid push certificate for pushing into an repository from getting replayed in a different push operation, send a nonce string from the receive-pack process and have the signer include it in the push certificate. The receiving end uses an HMAC hash of the path to the repository it serves and the current time stamp, hashed with a secret seed (the secret seed does not have to be per-repository but can be defined in /etc/gitconfig) to generate the nonce, in order to ensure that a random third party cannot forge a nonce that looks like it originated from it. 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, but returned "nonce" header in the push certificate is examined by receive-pack and the result is exported as GIT_PUSH_CERT_NONCE_STATUS, whose value would be "OK" if the nonce recorded in the certificate matches what we expect, so that the hooks can more easily check. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 12 +- Documentation/git-receive-pack.txt | 19 ++++ Documentation/technical/pack-protocol.txt | 6 + Documentation/technical/protocol-capabilities.txt | 7 +- builtin/receive-pack.c | 132 ++++++++++++++++++++-- send-pack.c | 18 ++- t/t5534-push-signed.sh | 22 ++-- 7 files changed, 187 insertions(+), 29 deletions(-) (limited to 'Documentation/technical') diff --git a/Documentation/config.txt b/Documentation/config.txt index 0d01e32888..dd6fd65e9f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2038,17 +2038,17 @@ rebase.autostash:: successful rebase might result in non-trivial conflicts. Defaults to false. -receive.acceptpushcert:: - By default, `git receive-pack` will advertise that it - accepts `git push --signed`. Setting this variable to - false disables it (this is a tentative variable that - will go away at the end of this series). - receive.autogc:: By default, git-receive-pack will run "git-gc --auto" after receiving data from git-push and updating refs. You can stop it by setting this variable to false. +receive.certnonceseed:: + By setting this variable to a string, `git receive-pack` + will accept a `git push --signed` and verifies it by using + a "nonce" protected by HMAC using this string as a secret + key. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index e6df234926..2d4b45242c 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -72,6 +72,24 @@ the following environment variables: using the same mnemonic as used in `%G?` format of `git log` family of commands (see linkgit:git-log[1]). +`GIT_PUSH_CERT_NONCE`:: + The nonce string the process asked the signer to include + in the push certificate. If this does not match the value + recorded on the "nonce" header in the push certificate, it + may indicate that the certificate is a valid one that is + being replayed from a separate "git push" session. + +`GIT_PUSH_CERT_NONCE_STATUS`:: +`UNSOLICITED`;; + "git push --signed" sent a nonce when we did not ask it to + send one. +`MISSING`;; + "git push --signed" did not send any nonce header. +`BAD`;; + "git push --signed" sent a bogus nonce. +`OK`;; + "git push --signed" sent the nonce we asked it to send. + This hook is called before any refname is updated and before any fast-forward checks are performed. @@ -147,6 +165,7 @@ service: if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G then ( + echo expected nonce is ${GIT_PUSH_NONCE} git cat-file blob ${GIT_PUSH_CERT} ) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain fi diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 7b543dc311..dda120631e 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -485,6 +485,7 @@ references. PKT-LINE("certificate version 0.1" LF) PKT-LINE("pusher" SP ident LF) PKT-LINE("pushee" SP url LF) + PKT-LINE("nonce" SP nonce LF) PKT-LINE(LF) *PKT-LINE(command LF) *PKT-LINE(gpg-signature-lines LF) @@ -533,6 +534,11 @@ Currently, the following header fields are defined: authentication material) the user who ran `git push` intended to push into. +`nonce` nonce:: + The 'nonce' string the receiving repository asked the + pushing user to include in the certificate, to prevent + replay attacks. + The GPG signature lines are a detached signature for the contents recorded in the push certificate before the signature block begins. The detached signature is used to certify that the commands were diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index a478cc4135..0c92deebcc 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -251,10 +251,11 @@ If the upload-pack server advertises this capability, fetch-pack may send "want" lines with SHA-1s that exist at the server but are not advertised by upload-pack. -push-cert ---------- +push-cert= +----------------- The receive-pack server that advertises this capability is willing -to accept a signed push certificate. A send-pack client MUST NOT +to accept a signed push certificate, and asks the to be +included in the push certificate. A send-pack client MUST NOT send a push-cert packet unless the receive-pack server advertises this capability. diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 431af39335..91d1a6f59d 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -48,10 +48,17 @@ static void *head_name_to_free; static int sent_capabilities; static int shallow_update; 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 const char *push_cert_nonce; +static const char *cert_nonce_seed; + +static const char *NONCE_UNSOLICITED = "UNSOLICITED"; +static const char *NONCE_BAD = "BAD"; +static const char *NONCE_MISSING = "MISSING"; +static const char *NONCE_OK = "OK"; +static const char *nonce_status; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -135,10 +142,8 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } - if (strcmp(var, "receive.acceptpushcert") == 0) { - accept_push_cert = git_config_bool(var, value); - return 0; - } + if (strcmp(var, "receive.certnonceseed") == 0) + return git_config_string(&cert_nonce_seed, var, value); return git_default_config(var, value, cb); } @@ -157,8 +162,8 @@ static void show_ref(const char *path, const unsigned char *sha1) "report-status delete-refs side-band-64k quiet"); if (prefer_ofs_delta) strbuf_addstr(&cap, " ofs-delta"); - if (accept_push_cert) - strbuf_addstr(&cap, " push-cert"); + if (push_cert_nonce) + strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), path, 0, cap.buf); @@ -271,6 +276,110 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } +#define HMAC_BLOCK_SIZE 64 + +static void hmac_sha1(unsigned char out[20], + const char *key_in, size_t key_len, + const char *text, size_t text_len) +{ + unsigned char key[HMAC_BLOCK_SIZE]; + unsigned char k_ipad[HMAC_BLOCK_SIZE]; + unsigned char k_opad[HMAC_BLOCK_SIZE]; + int i; + git_SHA_CTX ctx; + + /* RFC 2104 2. (1) */ + memset(key, '\0', HMAC_BLOCK_SIZE); + if (HMAC_BLOCK_SIZE < key_len) { + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, key_in, key_len); + git_SHA1_Final(key, &ctx); + } else { + memcpy(key, key_in, key_len); + } + + /* RFC 2104 2. (2) & (5) */ + for (i = 0; i < sizeof(key); i++) { + k_ipad[i] = key[i] ^ 0x36; + k_opad[i] = key[i] ^ 0x5c; + } + + /* RFC 2104 2. (3) & (4) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad)); + git_SHA1_Update(&ctx, text, text_len); + git_SHA1_Final(out, &ctx); + + /* RFC 2104 2. (6) & (7) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_opad, sizeof(k_opad)); + git_SHA1_Update(&ctx, out, sizeof(out)); + git_SHA1_Final(out, &ctx); +} + +static char *prepare_push_cert_nonce(const char *path, unsigned long stamp) +{ + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + strbuf_addf(&buf, "%s:%lu", path, stamp); + hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));; + strbuf_release(&buf); + + /* RFC 2104 5. HMAC-SHA1-80 */ + strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1)); + return strbuf_detach(&buf, NULL); +} + +/* + * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing + * after dropping "_commit" from its name and possibly moving it out + * of commit.c + */ +static char *find_header(const char *msg, size_t len, const char *key) +{ + int key_len = strlen(key); + const char *line = msg; + + while (line && line < msg + len) { + const char *eol = strchrnul(line, '\n'); + + if ((msg + len <= eol) || line == eol) + return NULL; + if (line + key_len < eol && + !memcmp(line, key, key_len) && line[key_len] == ' ') { + int offset = key_len + 1; + return xmemdupz(line + offset, (eol - line) - offset); + } + line = *eol ? eol + 1 : NULL; + } + return NULL; +} + +static const char *check_nonce(const char *buf, size_t len) +{ + char *nonce = find_header(buf, len, "nonce"); + const char *retval = NONCE_BAD; + + if (!nonce) { + retval = NONCE_MISSING; + goto leave; + } else if (!push_cert_nonce) { + retval = NONCE_UNSOLICITED; + goto leave; + } else if (!strcmp(push_cert_nonce, nonce)) { + retval = NONCE_OK; + goto leave; + } + + /* returned nonce MUST match what we gave out earlier */ + retval = NONCE_BAD; + +leave: + free(nonce); + return retval; +} + static void prepare_push_cert_sha1(struct child_process *proc) { static int already_done; @@ -305,6 +414,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) strbuf_release(&gpg_output); strbuf_release(&gpg_status); + nonce_status = check_nonce(push_cert.buf, bogs); } if (!is_null_sha1(push_cert_sha1)) { argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1)); @@ -313,7 +423,10 @@ static void prepare_push_cert_sha1(struct child_process *proc) argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s", sigcheck.key ? sigcheck.key : ""); argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result); - + if (push_cert_nonce) { + argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce); + argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status); + } proc->env = env.argv; } } @@ -1296,6 +1409,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 (cert_nonce_seed) + push_cert_nonce = prepare_push_cert_nonce(dir, time(NULL)); if (0 <= transfer_unpack_limit) unpack_limit = transfer_unpack_limit; @@ -1340,5 +1455,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 9c2c64966d..7ad1a5968b 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]; @@ -245,6 +246,8 @@ static int generate_push_cert(struct strbuf *req_buf, 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) { @@ -295,6 +298,7 @@ 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")) @@ -311,8 +315,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" @@ -343,7 +353,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 2f4b74ed83..2786346f9a 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -50,7 +50,6 @@ test_expect_success 'unsigned push does not send push certificate' ' test_expect_success 'talking with a receiver without push certificate support' ' prepare_dst && mkdir -p dst/.git/hooks && - git -C dst config receive.acceptpushcert no && write_script dst/.git/hooks/post-receive <<-\EOF && # discard the update list cat >/dev/null @@ -68,7 +67,6 @@ test_expect_success 'talking with a receiver without push certificate support' ' test_expect_success 'push --signed fails with a receiver without push certificate support' ' prepare_dst && mkdir -p dst/.git/hooks && - git -C dst config receive.acceptpushcert no && test_must_fail git push --signed dst noop ff +noff 2>err && test_i18ngrep "the receiving end does not support" err ' @@ -89,6 +87,7 @@ test_expect_success GPG 'no certificate for a signed push with no update' ' test_expect_success GPG 'signed push sends push certificate' ' prepare_dst && mkdir -p dst/.git/hooks && + git -C dst config receive.certnonceseed sekrit && write_script dst/.git/hooks/post-receive <<-\EOF && # discard the update list cat >/dev/null @@ -102,17 +101,24 @@ 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_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus} + NONCE=${GIT_PUSH_CERT_NONCE-nononce} E_O_F EOF - cat >expect <<-\EOF && - SIGNER=C O Mitter - KEY=13B6F51ECDDE430D - STATUS=G - EOF - git push --signed dst noop ff +noff && + + ( + cat <<-\EOF && + SIGNER=C O Mitter + KEY=13B6F51ECDDE430D + STATUS=G + NONCE_STATUS=OK + 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 -- cgit v1.3