{"thread":{"id":"37585","subject":"[PATCH v6 01/23] receive-pack: do not overallocate command structure","startedAt":"2014-09-17T22:45:35Z","lastAt":"2014-09-17T22:45:58Z","messageCount":24,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":6,"patchTotal":23},"messages":[{"id":"249603","messageId":"1410993958-32394-1-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":null,"subject":"[PATCH v6 00/23] Signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:35Z","receivedAt":"2014-09-17T22:45:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"No changes to the earlier 20 patches in the series since the last\nround ($gmane/257087).  The last three patches in the series have\nbeen reworked and reordered to:\n\n - plug a small leak in replay prevention code;\n - smart HTTP integration and test are in a single patch;\n - handling of a stale nonce in smart HTTP mode was reworked.\n\nI think this round is ready for 'next'.  Those who work on various\nreimplementations of Git may want to start thinking about adding\nsupport for the \"push-cert\" feature in their receive-pack, and those\nwho use server-side pre-receive/post-receive hooks (Gitolite, I am\nlooking at you ;-) may want to start planning to take advantage of\nit.\n\nJunio C Hamano (23):\n  receive-pack: do not overallocate command structure\n  receive-pack: parse feature request a bit earlier\n  receive-pack: do not reuse old_sha1[] for other things\n  receive-pack: factor out queueing of command\n  send-pack: move REF_STATUS_REJECT_NODELETE logic a bit higher\n  send-pack: refactor decision to send update per ref\n  send-pack: always send capabilities\n  send-pack: factor out capability string generation\n  receive-pack: factor out capability string generation\n  send-pack: rename \"new_refs\" to \"need_pack_data\"\n  send-pack: refactor inspecting and resetting status and sending commands\n  send-pack: clarify that cmds_sent is a boolean\n  gpg-interface: move parse_gpg_output() to where it should be\n  gpg-interface: move parse_signature() to where it should be\n  pack-protocol doc: typofix for PKT-LINE\n  push: the beginning of \"git push --signed\"\n  receive-pack: GPG-validate push certificates\n  send-pack: send feature request on push-cert packet\n  signed push: remove duplicated protocol info\n  signed push: add \"pushee\" header to push certificate\n  signed push: fortify against replay attacks\n  signed push: teach smart-HTTP to pass \"git push --signed\" around\n  signed push: allow stale nonce in stateless mode\n\n Documentation/config.txt                          |  19 ++\n Documentation/git-push.txt                        |   9 +-\n Documentation/git-receive-pack.txt                |  65 +++-\n Documentation/technical/pack-protocol.txt         |  49 ++-\n Documentation/technical/protocol-capabilities.txt |  13 +-\n builtin/push.c                                    |   1 +\n builtin/receive-pack.c                            | 393 +++++++++++++++++++---\n builtin/send-pack.c                               |   4 +\n commit.c                                          |  36 --\n gpg-interface.c                                   |  57 ++++\n gpg-interface.h                                   |  17 +-\n remote-curl.c                                     |  13 +-\n send-pack.c                                       | 201 ++++++++---\n send-pack.h                                       |   2 +\n t/lib-httpd/apache.conf                           |   1 +\n t/t5534-push-signed.sh                            | 127 +++++++\n t/t5541-http-push-smart.sh                        |  41 +++\n t/test-lib.sh                                     |   3 +-\n tag.c                                             |  20 --\n tag.h                                             |   1 -\n transport-helper.c                                |   9 +-\n transport.c                                       |   5 +\n transport.h                                       |   5 +\n 23 files changed, 932 insertions(+), 159 deletions(-)\n create mode 100755 t/t5534-push-signed.sh\n\n-- \n2.1.0-403-g099cf47\n"},{"id":"249602","messageId":"1410993958-32394-2-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 01/23] receive-pack: do not overallocate command structure","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:36Z","receivedAt":"2014-09-17T22:45:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"An \"update\" command in the protocol exchange consists of 40-hex old\nobject name, SP, 40-hex new object name, SP, and a refname, but the\nfirst instance is further followed by a NUL with feature requests.\n\nThe command structure, which has a flex-array member that stores the\nrefname at the end, was allocated based on the whole length of the\nupdate command, without excluding the trailing feature requests.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n builtin/receive-pack.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex f93ac45..1663beb 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -872,10 +872,11 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n \t\t\t\tquiet = 1;\n \t\t}\n-\t\tcmd = xcalloc(1, sizeof(struct command) + len - 80);\n+\t\tcmd = xcalloc(1, sizeof(struct command) + reflen + 1);\n \t\thashcpy(cmd->old_sha1, old_sha1);\n \t\thashcpy(cmd->new_sha1, new_sha1);\n-\t\tmemcpy(cmd->ref_name, line + 82, len - 81);\n+\t\tmemcpy(cmd->ref_name, refname, reflen);\n+\t\tcmd->ref_name[reflen] = '\\0';\n \t\t*p = cmd;\n \t\tp = &cmd->next;\n \t}\n-- \n2.1.0-403-g099cf47\n"},{"id":"249623","messageId":"1410993958-32394-3-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 02/23] receive-pack: parse feature request a bit earlier","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:37Z","receivedAt":"2014-09-17T22:45:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ideally, we should have also allowed the first \"shallow\" to carry\nthe feature request trailer, but that is water under the bridge\nnow.  This makes the next step to factor out the queuing of commands\neasier to review.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n builtin/receive-pack.c | 26 ++++++++++++++------------\n 1 file changed, 14 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1663beb..a91eec8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -840,7 +840,7 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\tunsigned char old_sha1[20], new_sha1[20];\n \t\tstruct command *cmd;\n \t\tchar *refname;\n-\t\tint len, reflen;\n+\t\tint len, reflen, linelen;\n \n \t\tline = packet_read_line(0, &len);\n \t\tif (!line)\n@@ -853,7 +853,18 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\tcontinue;\n \t\t}\n \n-\t\tif (len < 83 ||\n+\t\tlinelen = strlen(line);\n+\t\tif (linelen < len) {\n+\t\t\tconst char *feature_list = line + linelen + 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n+\t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n+\t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n+\t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n+\t\t\t\tquiet = 1;\n+\t\t}\n+\n+\t\tif (linelen < 83 ||\n \t\t    line[40] != ' ' ||\n \t\t    line[81] != ' ' ||\n \t\t    get_sha1_hex(line, old_sha1) ||\n@@ -862,16 +873,7 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\t    line);\n \n \t\trefname = line + 82;\n-\t\treflen = strlen(refname);\n-\t\tif (reflen + 82 < len) {\n-\t\t\tconst char *feature_list = refname + reflen + 1;\n-\t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n-\t\t\t\treport_status = 1;\n-\t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n-\t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n-\t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n-\t\t\t\tquiet = 1;\n-\t\t}\n+\t\treflen = linelen - 82;\n \t\tcmd = xcalloc(1, sizeof(struct command) + reflen + 1);\n \t\thashcpy(cmd->old_sha1, old_sha1);\n \t\thashcpy(cmd->new_sha1, new_sha1);\n-- \n2.1.0-403-g099cf47\n"},{"id":"249625","messageId":"1410993958-32394-4-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 03/23] receive-pack: do not reuse old_sha1[] for other things","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:38Z","receivedAt":"2014-09-17T22:45:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This piece of code reads object names of shallow boundaries, not\nold_sha1[], i.e. the current value the ref points at, which is to be\nreplaced by what is in new_sha1[].\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n builtin/receive-pack.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a91eec8..c9b92bf 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -847,9 +847,11 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\tbreak;\n \n \t\tif (len == 48 && starts_with(line, \"shallow \")) {\n-\t\t\tif (get_sha1_hex(line + 8, old_sha1))\n-\t\t\t\tdie(\"protocol error: expected shallow sha, got '%s'\", line + 8);\n-\t\t\tsha1_array_append(shallow, old_sha1);\n+\t\t\tunsigned char sha1[20];\n+\t\t\tif (get_sha1_hex(line + 8, sha1))\n+\t\t\t\tdie(\"protocol error: expected shallow sha, got '%s'\",\n+\t\t\t\t    line + 8);\n+\t\t\tsha1_array_append(shallow, sha1);\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.1.0-403-g099cf47\n"},{"id":"249605","messageId":"1410993958-32394-5-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 04/23] receive-pack: factor out queueing of command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:39Z","receivedAt":"2014-09-17T22:45:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Make a helper function to accept a line of a protocol message and\nqueue an update command out of the code from read_head_info().\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n builtin/receive-pack.c | 50 +++++++++++++++++++++++++++++---------------------\n 1 file changed, 29 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c9b92bf..587f7da 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -831,16 +831,40 @@ static void execute_commands(struct command *commands,\n \t\t      \"the reported refs above\");\n }\n \n+static struct command **queue_command(struct command **tail,\n+\t\t\t\t      const char *line,\n+\t\t\t\t      int linelen)\n+{\n+\tunsigned char old_sha1[20], new_sha1[20];\n+\tstruct command *cmd;\n+\tconst char *refname;\n+\tint reflen;\n+\n+\tif (linelen < 83 ||\n+\t    line[40] != ' ' ||\n+\t    line[81] != ' ' ||\n+\t    get_sha1_hex(line, old_sha1) ||\n+\t    get_sha1_hex(line + 41, new_sha1))\n+\t\tdie(\"protocol error: expected old/new/ref, got '%s'\", line);\n+\n+\trefname = line + 82;\n+\treflen = linelen - 82;\n+\tcmd = xcalloc(1, sizeof(struct command) + reflen + 1);\n+\thashcpy(cmd->old_sha1, old_sha1);\n+\thashcpy(cmd->new_sha1, new_sha1);\n+\tmemcpy(cmd->ref_name, refname, reflen);\n+\tcmd->ref_name[reflen] = '\\0';\n+\t*tail = cmd;\n+\treturn &cmd->next;\n+}\n+\n static struct command *read_head_info(struct sha1_array *shallow)\n {\n \tstruct command *commands = NULL;\n \tstruct command **p = &commands;\n \tfor (;;) {\n \t\tchar *line;\n-\t\tunsigned char old_sha1[20], new_sha1[20];\n-\t\tstruct command *cmd;\n-\t\tchar *refname;\n-\t\tint len, reflen, linelen;\n+\t\tint len, linelen;\n \n \t\tline = packet_read_line(0, &len);\n \t\tif (!line)\n@@ -866,23 +890,7 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\t\tquiet = 1;\n \t\t}\n \n-\t\tif (linelen < 83 ||\n-\t\t    line[40] != ' ' ||\n-\t\t    line[81] != ' ' ||\n-\t\t    get_sha1_hex(line, old_sha1) ||\n-\t\t    get_sha1_hex(line + 41, new_sha1))\n-\t\t\tdie(\"protocol error: expected old/new/ref, got '%s'\",\n-\t\t\t    line);\n-\n-\t\trefname = line + 82;\n-\t\treflen = linelen - 82;\n-\t\tcmd = xcalloc(1, sizeof(struct command) + reflen + 1);\n-\t\thashcpy(cmd->old_sha1, old_sha1);\n-\t\thashcpy(cmd->new_sha1, new_sha1);\n-\t\tmemcpy(cmd->ref_name, refname, reflen);\n-\t\tcmd->ref_name[reflen] = '\\0';\n-\t\t*p = cmd;\n-\t\tp = &cmd->next;\n+\t\tp = queue_command(p, line, linelen);\n \t}\n \treturn commands;\n }\n-- \n2.1.0-403-g099cf47\n"},{"id":"249604","messageId":"1410993958-32394-6-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 05/23] send-pack: move REF_STATUS_REJECT_NODELETE logic a bit higher","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:40Z","receivedAt":"2014-09-17T22:45:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"20e8b465 (refactor ref status logic for pushing, 2010-01-08)\nrestructured the code to set status for each ref to be pushed, but\ndid not quite go far enough.  We inspect the status set earlier by\nset_refs_status_for_push() and then perform yet another update to\nthe status of a ref with an otherwise OK status to be deleted to\nmark it with REF_STATUS_REJECT_NODELETE when the protocol tells us\nnever to delete.\n\nSplit the latter into a separate loop that comes before we enter the\nper-ref loop.  This way we would have one less condition to check in\nthe main loop.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 15 ++++++++++-----\n 1 file changed, 10 insertions(+), 5 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 6129b0f..22a1709 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -231,6 +231,15 @@ int send_pack(struct send_pack_args *args,\n \t\treturn 0;\n \t}\n \n+\t/*\n+\t * NEEDSWORK: why does delete-refs have to be so specific to\n+\t * send-pack machinery that set_ref_status_for_push() cannot\n+\t * set this bit for us???\n+\t */\n+\tfor (ref = remote_refs; ref; ref = ref->next)\n+\t\tif (ref->deletion && !allow_deleting_refs)\n+\t\t\tref->status = REF_STATUS_REJECT_NODELETE;\n+\n \tif (!args->dry_run)\n \t\tadvertise_shallow_grafts_buf(&req_buf);\n \n@@ -249,17 +258,13 @@ int send_pack(struct send_pack_args *args,\n \t\tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tcase REF_STATUS_REJECT_STALE:\n+\t\tcase REF_STATUS_REJECT_NODELETE:\n \t\tcase REF_STATUS_UPTODATE:\n \t\t\tcontinue;\n \t\tdefault:\n \t\t\t; /* do nothing */\n \t\t}\n \n-\t\tif (ref->deletion && !allow_deleting_refs) {\n-\t\t\tref->status = REF_STATUS_REJECT_NODELETE;\n-\t\t\tcontinue;\n-\t\t}\n-\n \t\tif (!ref->deletion)\n \t\t\tnew_refs++;\n \n-- \n2.1.0-403-g099cf47\n"},{"id":"249606","messageId":"1410993958-32394-7-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 06/23] send-pack: refactor decision to send update per ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:41Z","receivedAt":"2014-09-17T22:45:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A new helper function ref_update_to_be_sent() decides for each ref\nif the update is to be sent based on the status previously set by\nset_ref_status_for_push() and also if this is a mirrored push.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 36 +++++++++++++++++++++---------------\n 1 file changed, 21 insertions(+), 15 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 22a1709..43e98fa 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -190,6 +190,26 @@ static void advertise_shallow_grafts_buf(struct strbuf *sb)\n \tfor_each_commit_graft(advertise_shallow_grafts_cb, sb);\n }\n \n+static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)\n+{\n+\tif (!ref->peer_ref && !args->send_mirror)\n+\t\treturn 0;\n+\n+\t/* Check for statuses set by set_ref_status_for_push() */\n+\tswitch (ref->status) {\n+\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n+\tcase REF_STATUS_REJECT_FETCH_FIRST:\n+\tcase REF_STATUS_REJECT_NEEDS_FORCE:\n+\tcase REF_STATUS_REJECT_STALE:\n+\tcase REF_STATUS_REJECT_NODELETE:\n+\tcase REF_STATUS_UPTODATE:\n+\t\treturn 0;\n+\tdefault:\n+\t\treturn 1;\n+\t}\n+}\n+\n int send_pack(struct send_pack_args *args,\n \t      int fd[], struct child_process *conn,\n \t      struct ref *remote_refs,\n@@ -248,23 +268,9 @@ int send_pack(struct send_pack_args *args,\n \t */\n \tnew_refs = 0;\n \tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\tif (!ref->peer_ref && !args->send_mirror)\n+\t\tif (!ref_update_to_be_sent(ref, args))\n \t\t\tcontinue;\n \n-\t\t/* Check for statuses set by set_ref_status_for_push() */\n-\t\tswitch (ref->status) {\n-\t\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n-\t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n-\t\tcase REF_STATUS_REJECT_FETCH_FIRST:\n-\t\tcase REF_STATUS_REJECT_NEEDS_FORCE:\n-\t\tcase REF_STATUS_REJECT_STALE:\n-\t\tcase REF_STATUS_REJECT_NODELETE:\n-\t\tcase REF_STATUS_UPTODATE:\n-\t\t\tcontinue;\n-\t\tdefault:\n-\t\t\t; /* do nothing */\n-\t\t}\n-\n \t\tif (!ref->deletion)\n \t\t\tnew_refs++;\n \n-- \n2.1.0-403-g099cf47\n"},{"id":"249607","messageId":"1410993958-32394-8-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 07/23] send-pack: always send capabilities","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:42Z","receivedAt":"2014-09-17T22:45:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We tried to avoid sending one extra byte, NUL and nothing behind it\nto signal there is no protocol capabilities being sent, on the first\ncommand packet on the wire, but it just made the code look ugly.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 43e98fa..e81f741 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -281,8 +281,7 @@ int send_pack(struct send_pack_args *args,\n \t\t\tchar *new_hex = sha1_to_hex(ref->new_sha1);\n \t\t\tint quiet = quiet_supported && (args->quiet || !args->progress);\n \n-\t\t\tif (!cmds_sent && (status_report || use_sideband ||\n-\t\t\t\t\t   quiet || agent_supported)) {\n+\t\t\tif (!cmds_sent)\n \t\t\t\tpacket_buf_write(&req_buf,\n \t\t\t\t\t\t \"%s %s %s%c%s%s%s%s%s\",\n \t\t\t\t\t\t old_hex, new_hex, ref->name, 0,\n@@ -292,7 +291,6 @@ int send_pack(struct send_pack_args *args,\n \t\t\t\t\t\t agent_supported ? \" agent=\" : \"\",\n \t\t\t\t\t\t agent_supported ? git_user_agent_sanitized() : \"\"\n \t\t\t\t\t\t);\n-\t\t\t}\n \t\t\telse\n \t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n \t\t\t\t\t\t old_hex, new_hex, ref->name);\n-- \n2.1.0-403-g099cf47\n"},{"id":"249624","messageId":"1410993958-32394-9-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 08/23] send-pack: factor out capability string generation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:43Z","receivedAt":"2014-09-17T22:45:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A run of 'var ? \" var\" : \"\"' fed to a long printf string in a deeply\nnested block was hard to read.  Move it outside the loop and format\nit into a strbuf.\n\nAs an added bonus, the trick to add \"agent=<agent-name>\" by using\ntwo conditionals is replaced by a more readable version.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 21 +++++++++++++--------\n 1 file changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex e81f741..0cb44ab 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -218,6 +218,7 @@ int send_pack(struct send_pack_args *args,\n \tint in = fd[0];\n \tint out = fd[1];\n \tstruct strbuf req_buf = STRBUF_INIT;\n+\tstruct strbuf cap_buf = STRBUF_INIT;\n \tstruct ref *ref;\n \tint new_refs;\n \tint allow_deleting_refs = 0;\n@@ -251,6 +252,15 @@ int send_pack(struct send_pack_args *args,\n \t\treturn 0;\n \t}\n \n+\tif (status_report)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\tif (use_sideband)\n+\t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n+\tif (quiet_supported && (args->quiet || !args->progress))\n+\t\tstrbuf_addstr(&cap_buf, \" quiet\");\n+\tif (agent_supported)\n+\t\tstrbuf_addf(&cap_buf, \" agent=%s\", git_user_agent_sanitized());\n+\n \t/*\n \t * NEEDSWORK: why does delete-refs have to be so specific to\n \t * send-pack machinery that set_ref_status_for_push() cannot\n@@ -279,18 +289,12 @@ int send_pack(struct send_pack_args *args,\n \t\t} else {\n \t\t\tchar *old_hex = sha1_to_hex(ref->old_sha1);\n \t\t\tchar *new_hex = sha1_to_hex(ref->new_sha1);\n-\t\t\tint quiet = quiet_supported && (args->quiet || !args->progress);\n \n \t\t\tif (!cmds_sent)\n \t\t\t\tpacket_buf_write(&req_buf,\n-\t\t\t\t\t\t \"%s %s %s%c%s%s%s%s%s\",\n+\t\t\t\t\t\t \"%s %s %s%c%s\",\n \t\t\t\t\t\t old_hex, new_hex, ref->name, 0,\n-\t\t\t\t\t\t status_report ? \" report-status\" : \"\",\n-\t\t\t\t\t\t use_sideband ? \" side-band-64k\" : \"\",\n-\t\t\t\t\t\t quiet ? \" quiet\" : \"\",\n-\t\t\t\t\t\t agent_supported ? \" agent=\" : \"\",\n-\t\t\t\t\t\t agent_supported ? git_user_agent_sanitized() : \"\"\n-\t\t\t\t\t\t);\n+\t\t\t\t\t\t cap_buf.buf);\n \t\t\telse\n \t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n \t\t\t\t\t\t old_hex, new_hex, ref->name);\n@@ -311,6 +315,7 @@ int send_pack(struct send_pack_args *args,\n \t\tpacket_flush(out);\n \t}\n \tstrbuf_release(&req_buf);\n+\tstrbuf_release(&cap_buf);\n \n \tif (use_sideband && cmds_sent) {\n \t\tmemset(&demux, 0, sizeof(demux));\n-- \n2.1.0-403-g099cf47\n"},{"id":"249608","messageId":"1410993958-32394-10-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 09/23] receive-pack: factor out capability string generation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:44Z","receivedAt":"2014-09-17T22:45:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Similar to the previous one for send-pack, make it easier and\ncleaner to add to capability advertisement.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n builtin/receive-pack.c | 22 ++++++++++++++--------\n 1 file changed, 14 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 587f7da..cbbad54 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -137,15 +137,21 @@ static void show_ref(const char *path, const unsigned char *sha1)\n \tif (ref_is_hidden(path))\n \t\treturn;\n \n-\tif (sent_capabilities)\n+\tif (sent_capabilities) {\n \t\tpacket_write(1, \"%s %s\\n\", sha1_to_hex(sha1), path);\n-\telse\n-\t\tpacket_write(1, \"%s %s%c%s%s agent=%s\\n\",\n-\t\t\t     sha1_to_hex(sha1), path, 0,\n-\t\t\t     \" report-status delete-refs side-band-64k quiet\",\n-\t\t\t     prefer_ofs_delta ? \" ofs-delta\" : \"\",\n-\t\t\t     git_user_agent_sanitized());\n-\tsent_capabilities = 1;\n+\t} else {\n+\t\tstruct strbuf cap = STRBUF_INIT;\n+\n+\t\tstrbuf_addstr(&cap,\n+\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\tif (prefer_ofs_delta)\n+\t\t\tstrbuf_addstr(&cap, \" ofs-delta\");\n+\t\tstrbuf_addf(&cap, \" agent=%s\", git_user_agent_sanitized());\n+\t\tpacket_write(1, \"%s %s%c%s\\n\",\n+\t\t\t     sha1_to_hex(sha1), path, 0, cap.buf);\n+\t\tstrbuf_release(&cap);\n+\t\tsent_capabilities = 1;\n+\t}\n }\n \n static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)\n-- \n2.1.0-403-g099cf47\n"},{"id":"249611","messageId":"1410993958-32394-11-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 10/23] send-pack: rename \"new_refs\" to \"need_pack_data\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:45Z","receivedAt":"2014-09-17T22:45:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The variable counts how many non-deleting command is being sent, but\nis only checked with 0-ness to decide if we need to send the pack\ndata.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 0cb44ab..716c11b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -220,7 +220,7 @@ int send_pack(struct send_pack_args *args,\n \tstruct strbuf req_buf = STRBUF_INIT;\n \tstruct strbuf cap_buf = STRBUF_INIT;\n \tstruct ref *ref;\n-\tint new_refs;\n+\tint need_pack_data = 0;\n \tint allow_deleting_refs = 0;\n \tint status_report = 0;\n \tint use_sideband = 0;\n@@ -276,13 +276,12 @@ int send_pack(struct send_pack_args *args,\n \t/*\n \t * Finally, tell the other end!\n \t */\n-\tnew_refs = 0;\n \tfor (ref = remote_refs; ref; ref = ref->next) {\n \t\tif (!ref_update_to_be_sent(ref, args))\n \t\t\tcontinue;\n \n \t\tif (!ref->deletion)\n-\t\t\tnew_refs++;\n+\t\t\tneed_pack_data = 1;\n \n \t\tif (args->dry_run) {\n \t\t\tref->status = REF_STATUS_OK;\n@@ -327,7 +326,7 @@ int send_pack(struct send_pack_args *args,\n \t\tin = demux.out;\n \t}\n \n-\tif (new_refs && cmds_sent) {\n+\tif (need_pack_data && cmds_sent) {\n \t\tif (pack_objects(out, remote_refs, extra_have, args) < 0) {\n \t\t\tfor (ref = remote_refs; ref; ref = ref->next)\n \t\t\t\tref->status = REF_STATUS_NONE;\n-- \n2.1.0-403-g099cf47\n"},{"id":"249610","messageId":"1410993958-32394-12-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 11/23] send-pack: refactor inspecting and resetting status and sending commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:46Z","receivedAt":"2014-09-17T22:45:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The main loop over remote_refs list inspects the ref status\nto see if we need to generate pack data (i.e. a delete-only push\ndoes not need to send any additional data), resets it to \"expecting\nthe status report\" state, and formats the actual update commands\nto be sent.\n\nSplit the former two out of the main loop, as it will become\nconditional in later steps.\n\nBesides, we should have code that does real thing here, before the\n\"Finally, tell the other end!\" part ;-)\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 49 ++++++++++++++++++++++++++++++-------------------\n 1 file changed, 30 insertions(+), 19 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 716c11b..6dc8a46 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -274,7 +274,8 @@ int send_pack(struct send_pack_args *args,\n \t\tadvertise_shallow_grafts_buf(&req_buf);\n \n \t/*\n-\t * Finally, tell the other end!\n+\t * Clear the status for each ref and see if we need to send\n+\t * the pack data.\n \t */\n \tfor (ref = remote_refs; ref; ref = ref->next) {\n \t\tif (!ref_update_to_be_sent(ref, args))\n@@ -283,25 +284,35 @@ int send_pack(struct send_pack_args *args,\n \t\tif (!ref->deletion)\n \t\t\tneed_pack_data = 1;\n \n-\t\tif (args->dry_run) {\n+\t\tif (args->dry_run || !status_report)\n \t\t\tref->status = REF_STATUS_OK;\n-\t\t} else {\n-\t\t\tchar *old_hex = sha1_to_hex(ref->old_sha1);\n-\t\t\tchar *new_hex = sha1_to_hex(ref->new_sha1);\n-\n-\t\t\tif (!cmds_sent)\n-\t\t\t\tpacket_buf_write(&req_buf,\n-\t\t\t\t\t\t \"%s %s %s%c%s\",\n-\t\t\t\t\t\t old_hex, new_hex, ref->name, 0,\n-\t\t\t\t\t\t cap_buf.buf);\n-\t\t\telse\n-\t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n-\t\t\t\t\t\t old_hex, new_hex, ref->name);\n-\t\t\tref->status = status_report ?\n-\t\t\t\tREF_STATUS_EXPECTING_REPORT :\n-\t\t\t\tREF_STATUS_OK;\n-\t\t\tcmds_sent++;\n-\t\t}\n+\t\telse\n+\t\t\tref->status = REF_STATUS_EXPECTING_REPORT;\n+\t}\n+\n+\t/*\n+\t * Finally, tell the other end!\n+\t */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (args->dry_run)\n+\t\t\tcontinue;\n+\n+\t\tif (!ref_update_to_be_sent(ref, args))\n+\t\t\tcontinue;\n+\n+\t\told_hex = sha1_to_hex(ref->old_sha1);\n+\t\tnew_hex = sha1_to_hex(ref->new_sha1);\n+\t\tif (!cmds_sent)\n+\t\t\tpacket_buf_write(&req_buf,\n+\t\t\t\t\t \"%s %s %s%c%s\",\n+\t\t\t\t\t old_hex, new_hex, ref->name, 0,\n+\t\t\t\t\t cap_buf.buf);\n+\t\telse\n+\t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n+\t\t\t\t\t old_hex, new_hex, ref->name);\n+\t\tcmds_sent++;\n \t}\n \n \tif (args->stateless_rpc) {\n-- \n2.1.0-403-g099cf47\n"},{"id":"249609","messageId":"1410993958-32394-13-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 12/23] send-pack: clarify that cmds_sent is a boolean","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:47Z","receivedAt":"2014-09-17T22:45:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We use it to make sure that the feature request is sent only once on\nthe very first request packet (ignoring the \"shallow \" line, which\nwas an unfortunate mistake we cannot retroactively fix with existing\nreceive-pack already deployed in the field) and we set it to \"true\"\nwith cmds_sent++, not because we care about the actual number of\nupdates sent but because it is merely an idiomatic way.\n\nSet it explicitly to one to clarify that the code that uses this\nvariable only cares about its zero-ness.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 6dc8a46..bb13599 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -304,15 +304,16 @@ int send_pack(struct send_pack_args *args,\n \n \t\told_hex = sha1_to_hex(ref->old_sha1);\n \t\tnew_hex = sha1_to_hex(ref->new_sha1);\n-\t\tif (!cmds_sent)\n+\t\tif (!cmds_sent) {\n \t\t\tpacket_buf_write(&req_buf,\n \t\t\t\t\t \"%s %s %s%c%s\",\n \t\t\t\t\t old_hex, new_hex, ref->name, 0,\n \t\t\t\t\t cap_buf.buf);\n-\t\telse\n+\t\t\tcmds_sent = 1;\n+\t\t} else {\n \t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n \t\t\t\t\t old_hex, new_hex, ref->name);\n-\t\tcmds_sent++;\n+\t\t}\n \t}\n \n \tif (args->stateless_rpc) {\n-- \n2.1.0-403-g099cf47\n"},{"id":"249612","messageId":"1410993958-32394-14-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 13/23] gpg-interface: move parse_gpg_output() to where it should be","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:48Z","receivedAt":"2014-09-17T22:45:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Earlier, ffb6d7d5 (Move commit GPG signature verification to\ncommit.c, 2013-03-31) moved this helper that used to be in pretty.c\n(i.e. the output code path) to commit.c for better reusability.\n\nIt was a good first step in the right direction, but still suffers\nfrom a myopic view that commits will be the only thing we would ever\nwant to sign---we would actually want to be able to reuse it even\nwider.\n\nThe function interprets what GPG said; gpg-interface is obviously a\nbetter place.  Move it there.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n commit.c        | 36 ------------------------------------\n gpg-interface.c | 36 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h | 16 +++++++++++-----\n 3 files changed, 47 insertions(+), 41 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex ae7f2b1..01cdad2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1220,42 +1220,6 @@ free_return:\n \tfree(buf);\n }\n \n-static struct {\n-\tchar result;\n-\tconst char *check;\n-} sigcheck_gpg_status[] = {\n-\t{ 'G', \"\\n[GNUPG:] GOODSIG \" },\n-\t{ 'B', \"\\n[GNUPG:] BADSIG \" },\n-\t{ 'U', \"\\n[GNUPG:] TRUST_NEVER\" },\n-\t{ 'U', \"\\n[GNUPG:] TRUST_UNDEFINED\" },\n-};\n-\n-static void parse_gpg_output(struct signature_check *sigc)\n-{\n-\tconst char *buf = sigc->gpg_status;\n-\tint i;\n-\n-\t/* Iterate over all search strings */\n-\tfor (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {\n-\t\tconst char *found, *next;\n-\n-\t\tif (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) {\n-\t\t\tfound = strstr(buf, sigcheck_gpg_status[i].check);\n-\t\t\tif (!found)\n-\t\t\t\tcontinue;\n-\t\t\tfound += strlen(sigcheck_gpg_status[i].check);\n-\t\t}\n-\t\tsigc->result = sigcheck_gpg_status[i].result;\n-\t\t/* The trust messages are not followed by key/signer information */\n-\t\tif (sigc->result != 'U') {\n-\t\t\tsigc->key = xmemdupz(found, 16);\n-\t\t\tfound += 17;\n-\t\t\tnext = strchrnul(found, '\\n');\n-\t\t\tsigc->signer = xmemdupz(found, next - found);\n-\t\t}\n-\t}\n-}\n-\n void check_commit_signature(const struct commit* commit, struct signature_check *sigc)\n {\n \tstruct strbuf payload = STRBUF_INIT;\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex ff07012..3c9624c 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -21,6 +21,42 @@ void signature_check_clear(struct signature_check *sigc)\n \tsigc->key = NULL;\n }\n \n+static struct {\n+\tchar result;\n+\tconst char *check;\n+} sigcheck_gpg_status[] = {\n+\t{ 'G', \"\\n[GNUPG:] GOODSIG \" },\n+\t{ 'B', \"\\n[GNUPG:] BADSIG \" },\n+\t{ 'U', \"\\n[GNUPG:] TRUST_NEVER\" },\n+\t{ 'U', \"\\n[GNUPG:] TRUST_UNDEFINED\" },\n+};\n+\n+void parse_gpg_output(struct signature_check *sigc)\n+{\n+\tconst char *buf = sigc->gpg_status;\n+\tint i;\n+\n+\t/* Iterate over all search strings */\n+\tfor (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {\n+\t\tconst char *found, *next;\n+\n+\t\tif (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) {\n+\t\t\tfound = strstr(buf, sigcheck_gpg_status[i].check);\n+\t\t\tif (!found)\n+\t\t\t\tcontinue;\n+\t\t\tfound += strlen(sigcheck_gpg_status[i].check);\n+\t\t}\n+\t\tsigc->result = sigcheck_gpg_status[i].result;\n+\t\t/* The trust messages are not followed by key/signer information */\n+\t\tif (sigc->result != 'U') {\n+\t\t\tsigc->key = xmemdupz(found, 16);\n+\t\t\tfound += 17;\n+\t\t\tnext = strchrnul(found, '\\n');\n+\t\t\tsigc->signer = xmemdupz(found, next - found);\n+\t\t}\n+\t}\n+}\n+\n void set_signing_key(const char *key)\n {\n \tfree(configured_signing_key);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 37c23da..82493b7 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -5,16 +5,22 @@ struct signature_check {\n \tchar *payload;\n \tchar *gpg_output;\n \tchar *gpg_status;\n-\tchar result; /* 0 (not checked),\n-\t\t      * N (checked but no further result),\n-\t\t      * U (untrusted good),\n-\t\t      * G (good)\n-\t\t      * B (bad) */\n+\n+\t/*\n+\t * possible \"result\":\n+\t * 0 (not checked)\n+\t * N (checked but no further result)\n+\t * U (untrusted good)\n+\t * G (good)\n+\t * B (bad)\n+\t */\n+\tchar result;\n \tchar *signer;\n \tchar *key;\n };\n \n extern void signature_check_clear(struct signature_check *sigc);\n+extern void parse_gpg_output(struct signature_check *);\n extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);\n extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);\n extern int git_gpg_config(const char *, const char *, void *);\n-- \n2.1.0-403-g099cf47\n"},{"id":"249622","messageId":"1410993958-32394-15-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 14/23] gpg-interface: move parse_signature() to where it should be","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:49Z","receivedAt":"2014-09-17T22:45:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Our signed-tag objects set the standard format used by Git to store\nGPG-signed payload (i.e. the payload followed by its detached\nsignature) [*1*], and it made sense to have a helper to find the\nboundary between the payload and its signature in tag.c back then.\n\nNewer code added later to parse other kinds of objects that learned\nto use the same format to store GPG-signed payload (e.g. signed\ncommits), however, kept using the helper from the same location.\n\nMove it to gpg-interface; the helper is no longer about signed tag,\nbut it is how our code and data interact with GPG.\n\n[Reference]\n*1* http://thread.gmane.org/gmane.linux.kernel/297998/focus=1383\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n gpg-interface.c | 21 +++++++++++++++++++++\n gpg-interface.h |  1 +\n tag.c           | 20 --------------------\n tag.h           |  1 -\n 4 files changed, 22 insertions(+), 21 deletions(-)\n\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 3c9624c..0dd11ea 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -7,6 +7,9 @@\n static char *configured_signing_key;\n static const char *gpg_program = \"gpg\";\n \n+#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n+#define PGP_MESSAGE \"-----BEGIN PGP MESSAGE-----\"\n+\n void signature_check_clear(struct signature_check *sigc)\n {\n \tfree(sigc->payload);\n@@ -57,6 +60,24 @@ void parse_gpg_output(struct signature_check *sigc)\n \t}\n }\n \n+/*\n+ * Look at GPG signed content (e.g. a signed tag object), whose\n+ * payload is followed by a detached signature on it.  Return the\n+ * offset where the embedded detached signature begins, or the end of\n+ * the data when there is no such signature.\n+ */\n+size_t parse_signature(const char *buf, unsigned long size)\n+{\n+\tchar *eol;\n+\tsize_t len = 0;\n+\twhile (len < size && !starts_with(buf + len, PGP_SIGNATURE) &&\n+\t\t\t!starts_with(buf + len, PGP_MESSAGE)) {\n+\t\teol = memchr(buf + len, '\\n', size - len);\n+\t\tlen += eol ? eol - (buf + len) + 1 : size - len;\n+\t}\n+\treturn len;\n+}\n+\n void set_signing_key(const char *key)\n {\n \tfree(configured_signing_key);\ndiff --git a/gpg-interface.h b/gpg-interface.h\nindex 82493b7..87a4f2e 100644\n--- a/gpg-interface.h\n+++ b/gpg-interface.h\n@@ -20,6 +20,7 @@ struct signature_check {\n };\n \n extern void signature_check_clear(struct signature_check *sigc);\n+extern size_t parse_signature(const char *buf, unsigned long size);\n extern void parse_gpg_output(struct signature_check *);\n extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);\n extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);\ndiff --git a/tag.c b/tag.c\nindex 82d841b..5b0ac62 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -4,9 +4,6 @@\n #include \"tree.h\"\n #include \"blob.h\"\n \n-#define PGP_SIGNATURE \"-----BEGIN PGP SIGNATURE-----\"\n-#define PGP_MESSAGE \"-----BEGIN PGP MESSAGE-----\"\n-\n const char *tag_type = \"tag\";\n \n struct object *deref_tag(struct object *o, const char *warn, int warnlen)\n@@ -143,20 +140,3 @@ int parse_tag(struct tag *item)\n \tfree(data);\n \treturn ret;\n }\n-\n-/*\n- * Look at a signed tag object, and return the offset where\n- * the embedded detached signature begins, or the end of the\n- * data when there is no such signature.\n- */\n-size_t parse_signature(const char *buf, unsigned long size)\n-{\n-\tchar *eol;\n-\tsize_t len = 0;\n-\twhile (len < size && !starts_with(buf + len, PGP_SIGNATURE) &&\n-\t\t\t!starts_with(buf + len, PGP_MESSAGE)) {\n-\t\teol = memchr(buf + len, '\\n', size - len);\n-\t\tlen += eol ? eol - (buf + len) + 1 : size - len;\n-\t}\n-\treturn len;\n-}\ndiff --git a/tag.h b/tag.h\nindex bc8a1e4..f4580ae 100644\n--- a/tag.h\n+++ b/tag.h\n@@ -17,6 +17,5 @@ extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long si\n extern int parse_tag(struct tag *item);\n extern struct object *deref_tag(struct object *, const char *, int);\n extern struct object *deref_tag_noverify(struct object *);\n-extern size_t parse_signature(const char *buf, unsigned long size);\n \n #endif /* TAG_H */\n-- \n2.1.0-403-g099cf47\n"},{"id":"249613","messageId":"1410993958-32394-16-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 15/23] pack-protocol doc: typofix for PKT-LINE","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:50Z","receivedAt":"2014-09-17T22:45:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Everywhere else we use PKT-LINE to denote the pkt-line formatted\ndata, but \"shallow/deepen\" messages are described with PKT_LINE().\n\nFix them.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n Documentation/technical/pack-protocol.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex 18dea8d..a845d51 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -212,9 +212,9 @@ out of what the server said it could do with the first 'want' line.\n   want-list         =  first-want\n \t\t       *additional-want\n \n-  shallow-line      =  PKT_LINE(\"shallow\" SP obj-id)\n+  shallow-line      =  PKT-LINE(\"shallow\" SP obj-id)\n \n-  depth-request     =  PKT_LINE(\"deepen\" SP depth)\n+  depth-request     =  PKT-LINE(\"deepen\" SP depth)\n \n   first-want        =  PKT-LINE(\"want\" SP obj-id SP capability-list LF)\n   additional-want   =  PKT-LINE(\"want\" SP obj-id LF)\n-- \n2.1.0-403-g099cf47\n"},{"id":"249620","messageId":"1410993958-32394-17-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 16/23] push: the beginning of \"git push --signed\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:51Z","receivedAt":"2014-09-17T22:45:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"While signed tags and commits assert that the objects thusly signed\ncame from you, who signed these objects, there is not a good way to\nassert that you wanted to have a particular object at the tip of a\nparticular branch.  My signing v2.0.1 tag only means I want to call\nthe version v2.0.1, and it does not mean I want to push it out to my\n'master' branch---it is likely that I only want it in 'maint', so\nthe signature on the object alone is insufficient.\n\nThe only assurance to you that 'maint' points at what I wanted to\nplace there comes from your trust on the hosting site and my\nauthentication with it, which cannot easily audited later.\n\nIntroduce a mechanism that allows you to sign a \"push certificate\"\n(for the lack of better name) every time you push, asserting that\nwhat object you are pushing to update which ref that used to point\nat what other object.  Think of it as a cryptographic protection for\nref updates, similar to signed tags/commits but working on an\northogonal axis.\n\nThe basic flow based on this mechanism goes like this:\n\n 1. You push out your work with \"git push --signed\".\n\n 2. The sending side learns where the remote refs are as usual,\n    together with what protocol extension the receiving end\n    supports.  If the receiving end does not advertise the protocol\n    extension \"push-cert\", an attempt to \"git push --signed\" fails.\n\n    Otherwise, a text file, that looks like the following, is\n    prepared in core:\n\n\tcertificate version 0.1\n\tpusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700\n\n\t7339ca65... 21580ecb... refs/heads/master\n\t3793ac56... 12850bec... refs/heads/next\n\n    The file begins with a few header lines, which may grow as we\n    gain more experience.  The 'pusher' header records the name of\n    the signer (the value of user.signingkey configuration variable,\n    falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the\n    certificate generation.  After the header, a blank line follows,\n    followed by a copy of the protocol message lines.\n\n    Each line shows the old and the new object name at the tip of\n    the ref this push tries to update, in the way identical to how\n    the underlying \"git push\" protocol exchange tells the ref\n    updates to the receiving end (by recording the \"old\" object\n    name, the push certificate also protects against replaying).  It\n    is expected that new command packet types other than the\n    old-new-refname kind will be included in push certificate in the\n    same way as would appear in the plain vanilla command packets in\n    unsigned pushes.\n\n    The user then is asked to sign this push certificate using GPG,\n    formatted in a way similar to how signed tag objects are signed,\n    and the result is sent to the other side (i.e. receive-pack).\n\n    In the protocol exchange, this step comes immediately before the\n    sender tells what the result of the push should be, which in\n    turn comes before it sends the pack data.\n\n 3. When the receiving end sees a push certificate, the certificate\n    is written out as a blob.  The pre-receive hook can learn about\n    the certificate by checking GIT_PUSH_CERT environment variable,\n    which, if present, tells the object name of this blob, and make\n    the decision to allow or reject this push.  Additionally, the\n    post-receive hook can also look at the certificate, which may be\n    a good place to log all the received certificates for later\n    audits.\n\nBecause a push certificate carry the same information as the usual\ncommand packets in the protocol exchange, we can omit the latter\nwhen a push certificate is in use and reduce the protocol overhead.\nThis however is not included in this patch to make it easier to\nreview (in other words, the series at this step should never be\nreleased without the remainder of the series, as it implements an\ninterim protocol that will be incompatible with the final one).\nAs such, the documentation update for the protocol is left out of\nthis step.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n Documentation/config.txt           |  6 +++\n Documentation/git-push.txt         |  9 +++-\n Documentation/git-receive-pack.txt | 19 +++++++-\n builtin/push.c                     |  1 +\n builtin/receive-pack.c             | 52 +++++++++++++++++++++\n send-pack.c                        | 64 ++++++++++++++++++++++++++\n send-pack.h                        |  1 +\n t/t5534-push-signed.sh             | 94 ++++++++++++++++++++++++++++++++++++++\n transport.c                        |  4 ++\n transport.h                        |  5 ++\n 10 files changed, 253 insertions(+), 2 deletions(-)\n create mode 100755 t/t5534-push-signed.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex c55c22a..0d01e32 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2038,6 +2038,12 @@ rebase.autostash::\n \tsuccessful rebase might result in non-trivial conflicts.\n \tDefaults to false.\n \n+receive.acceptpushcert::\n+\tBy default, `git receive-pack` will advertise that it\n+\taccepts `git push --signed`.  Setting this variable to\n+\tfalse disables it (this is a tentative variable that\n+\twill go away at the end of this series).\n+\n receive.autogc::\n \tBy default, git-receive-pack will run \"git-gc --auto\" after\n \treceiving data from git-push and updating refs.  You can stop\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 21cd455..21b3f29 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -10,7 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]\n-\t   [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]\n+\t   [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]\n+\t   [-u | --set-upstream] [--signed]\n \t   [--force-with-lease[=<refname>[:<expect>]]]\n \t   [--no-verify] [<repository> [<refspec>...]]\n \n@@ -129,6 +130,12 @@ already exists on the remote side.\n \tfrom the remote but are pointing at commit-ish that are\n \treachable from the refs being pushed.\n \n+--signed::\n+\tGPG-sign the push request to update refs on the receiving\n+\tside, to allow it to be checked by the hooks and/or be\n+\tlogged.  See linkgit:git-receive-pack[1] for the details\n+\ton the receiving end.\n+\n --receive-pack=<git-receive-pack>::\n --exec=<git-receive-pack>::\n \tPath to the 'git-receive-pack' program on the remote\ndiff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt\nindex b1f7dc6..a2dd743 100644\n--- a/Documentation/git-receive-pack.txt\n+++ b/Documentation/git-receive-pack.txt\n@@ -53,6 +53,11 @@ the update.  Refs to be created will have sha1-old equal to 0\\{40},\n while refs to be deleted will have sha1-new equal to 0\\{40}, otherwise\n sha1-old and sha1-new should be valid objects in the repository.\n \n+When accepting a signed push (see linkgit:git-push[1]), the signed\n+push certificate is stored in a blob and an environment variable\n+`GIT_PUSH_CERT` can be consulted for its object name.  See the\n+description of `post-receive` hook for an example.\n+\n This hook is called before any refname is updated and before any\n fast-forward checks are performed.\n \n@@ -101,9 +106,14 @@ the update.  Refs that were created will have sha1-old equal to\n 0\\{40}, otherwise sha1-old and sha1-new should be valid objects in\n the repository.\n \n+The `GIT_PUSH_CERT` environment variable can be inspected, just as\n+in `pre-receive` hook, after accepting a signed push.\n+\n Using this hook, it is easy to generate mails describing the updates\n to the repository.  This example script sends one mail message per\n-ref listing the commits pushed to the repository:\n+ref listing the commits pushed to the repository, and logs the push\n+certificates of signed pushes to a logger\n+service:\n \n \t#!/bin/sh\n \t# mail out commit update information.\n@@ -119,6 +129,13 @@ ref listing the commits pushed to the repository:\n \t\tfi |\n \t\tmail -s \"Changes to ref $ref\" commit-list@mydomain\n \tdone\n+\t# log signed push certificate, if any\n+\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tthen\n+\t\t(\n+\t\t\tgit cat-file blob ${GIT_PUSH_CERT}\n+\t\t) | mail -s \"push certificate\" push-log@mydomain\n+\tfi\n \texit 0\n \n The exit code from this hook invocation is ignored, however a\ndiff --git a/builtin/push.c b/builtin/push.c\nindex f50e3d5..ae56f73 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -506,6 +506,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT(0, \"no-verify\", &flags, N_(\"bypass pre-push hook\"), TRANSPORT_PUSH_NO_HOOK),\n \t\tOPT_BIT(0, \"follow-tags\", &flags, N_(\"push missing but relevant tags\"),\n \t\t\tTRANSPORT_PUSH_FOLLOW_TAGS),\n+\t\tOPT_BIT(0, \"signed\", &flags, N_(\"GPG sign the push\"), TRANSPORT_PUSH_CERT),\n \t\tOPT_END()\n \t};\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex cbbad54..610b085 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -46,6 +46,9 @@ static void *head_name_to_free;\n static int sent_capabilities;\n static int shallow_update;\n static const char *alt_shallow_file;\n+static int accept_push_cert = 1;\n+static struct strbuf push_cert = STRBUF_INIT;\n+static unsigned char push_cert_sha1[20];\n \n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n@@ -129,6 +132,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.acceptpushcert\") == 0) {\n+\t\taccept_push_cert = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -146,6 +154,8 @@ static void show_ref(const char *path, const unsigned char *sha1)\n \t\t\t      \"report-status delete-refs side-band-64k quiet\");\n \t\tif (prefer_ofs_delta)\n \t\t\tstrbuf_addstr(&cap, \" ofs-delta\");\n+\t\tif (accept_push_cert)\n+\t\t\tstrbuf_addstr(&cap, \" push-cert\");\n \t\tstrbuf_addf(&cap, \" agent=%s\", git_user_agent_sanitized());\n \t\tpacket_write(1, \"%s %s%c%s\\n\",\n \t\t\t     sha1_to_hex(sha1), path, 0, cap.buf);\n@@ -258,6 +268,25 @@ static int copy_to_sideband(int in, int out, void *arg)\n \treturn 0;\n }\n \n+static void prepare_push_cert_sha1(struct child_process *proc)\n+{\n+\tstatic int already_done;\n+\tstruct argv_array env = ARGV_ARRAY_INIT;\n+\n+\tif (!push_cert.len)\n+\t\treturn;\n+\n+\tif (!already_done) {\n+\t\talready_done = 1;\n+\t\tif (write_sha1_file(push_cert.buf, push_cert.len, \"blob\", push_cert_sha1))\n+\t\t\thashclr(push_cert_sha1);\n+\t}\n+\tif (!is_null_sha1(push_cert_sha1)) {\n+\t\targv_array_pushf(&env, \"GIT_PUSH_CERT=%s\", sha1_to_hex(push_cert_sha1));\n+\t\tproc->env = env.argv;\n+\t}\n+}\n+\n typedef int (*feed_fn)(void *, const char **, size_t *);\n static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)\n {\n@@ -277,6 +306,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n \n+\tprepare_push_cert_sha1(&proc);\n+\n \tif (use_sideband) {\n \t\tmemset(&muxer, 0, sizeof(muxer));\n \t\tmuxer.proc = copy_to_sideband;\n@@ -896,6 +927,27 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \t\t\t\tquiet = 1;\n \t\t}\n \n+\t\tif (!strcmp(line, \"push-cert\")) {\n+\t\t\tint true_flush = 0;\n+\t\t\tchar certbuf[1024];\n+\n+\t\t\tfor (;;) {\n+\t\t\t\tlen = packet_read(0, NULL, NULL,\n+\t\t\t\t\t\t  certbuf, sizeof(certbuf), 0);\n+\t\t\t\tif (!len) {\n+\t\t\t\t\ttrue_flush = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tif (!strcmp(certbuf, \"push-cert-end\\n\"))\n+\t\t\t\t\tbreak; /* end of cert */\n+\t\t\t\tstrbuf_addstr(&push_cert, certbuf);\n+\t\t\t}\n+\n+\t\t\tif (true_flush)\n+\t\t\t\tbreak;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tp = queue_command(p, line, linelen);\n \t}\n \treturn commands;\ndiff --git a/send-pack.c b/send-pack.c\nindex bb13599..ef93f33 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -11,6 +11,7 @@\n #include \"transport.h\"\n #include \"version.h\"\n #include \"sha1-array.h\"\n+#include \"gpg-interface.h\"\n \n static int feed_object(const unsigned char *sha1, int fd, int negative)\n {\n@@ -210,6 +211,64 @@ static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_a\n \t}\n }\n \n+/*\n+ * the beginning of the next line, or the end of buffer.\n+ *\n+ * NEEDSWORK: perhaps move this to git-compat-util.h or somewhere and\n+ * convert many similar uses found by \"git grep -A4 memchr\".\n+ */\n+static const char *next_line(const char *line, size_t len)\n+{\n+\tconst char *nl = memchr(line, '\\n', len);\n+\tif (!nl)\n+\t\treturn line + len; /* incomplete line */\n+\treturn nl + 1;\n+}\n+\n+static void generate_push_cert(struct strbuf *req_buf,\n+\t\t\t       const struct ref *remote_refs,\n+\t\t\t       struct send_pack_args *args)\n+{\n+\tconst struct ref *ref;\n+\tchar stamp[60];\n+\tchar *signing_key = xstrdup(get_signing_key());\n+\tconst char *cp, *np;\n+\tstruct strbuf cert = STRBUF_INIT;\n+\tint update_seen = 0;\n+\n+\tdatestamp(stamp, sizeof(stamp));\n+\tstrbuf_addf(&cert, \"certificate version 0.1\\n\");\n+\tstrbuf_addf(&cert, \"pusher %s %s\\n\", signing_key, stamp);\n+\tstrbuf_addstr(&cert, \"\\n\");\n+\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tif (!ref_update_to_be_sent(ref, args))\n+\t\t\tcontinue;\n+\t\tupdate_seen = 1;\n+\t\tstrbuf_addf(&cert, \"%s %s %s\\n\",\n+\t\t\t    sha1_to_hex(ref->old_sha1),\n+\t\t\t    sha1_to_hex(ref->new_sha1),\n+\t\t\t    ref->name);\n+\t}\n+\tif (!update_seen)\n+\t\tgoto free_return;\n+\n+\tif (sign_buffer(&cert, &cert, signing_key))\n+\t\tdie(_(\"failed to sign the push certificate\"));\n+\n+\tpacket_buf_write(req_buf, \"push-cert\\n\");\n+\tfor (cp = cert.buf; cp < cert.buf + cert.len; cp = np) {\n+\t\tnp = next_line(cp, cert.buf + cert.len - cp);\n+\t\tpacket_buf_write(req_buf,\n+\t\t\t\t \"%.*s\", (int)(np - cp), cp);\n+\t}\n+\tpacket_buf_write(req_buf, \"push-cert-end\\n\");\n+\n+free_return:\n+\tfree(signing_key);\n+\tstrbuf_release(&cert);\n+}\n+\n int send_pack(struct send_pack_args *args,\n \t      int fd[], struct child_process *conn,\n \t      struct ref *remote_refs,\n@@ -245,6 +304,8 @@ int send_pack(struct send_pack_args *args,\n \t\tagent_supported = 1;\n \tif (server_supports(\"no-thin\"))\n \t\targs->use_thin_pack = 0;\n+\tif (args->push_cert && !server_supports(\"push-cert\"))\n+\t\tdie(_(\"the receiving end does not support --signed push\"));\n \n \tif (!remote_refs) {\n \t\tfprintf(stderr, \"No refs in common and none specified; doing nothing.\\n\"\n@@ -273,6 +334,9 @@ int send_pack(struct send_pack_args *args,\n \tif (!args->dry_run)\n \t\tadvertise_shallow_grafts_buf(&req_buf);\n \n+\tif (!args->dry_run && args->push_cert)\n+\t\tgenerate_push_cert(&req_buf, remote_refs, args);\n+\n \t/*\n \t * Clear the status for each ref and see if we need to send\n \t * the pack data.\ndiff --git a/send-pack.h b/send-pack.h\nindex 8e84392..3555d8e 100644\n--- a/send-pack.h\n+++ b/send-pack.h\n@@ -11,6 +11,7 @@ struct send_pack_args {\n \t\tuse_thin_pack:1,\n \t\tuse_ofs_delta:1,\n \t\tdry_run:1,\n+\t\tpush_cert:1,\n \t\tstateless_rpc:1;\n };\n \ndiff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh\nnew file mode 100755\nindex 0000000..019ac71\n--- /dev/null\n+++ b/t/t5534-push-signed.sh\n@@ -0,0 +1,94 @@\n+#!/bin/sh\n+\n+test_description='signed push'\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+\n+prepare_dst () {\n+\trm -fr dst &&\n+\ttest_create_repo dst &&\n+\n+\tgit push dst master:noop master:ff master:noff\n+}\n+\n+test_expect_success setup '\n+\t# master, ff and noff branches pointing at the same commit\n+\ttest_tick &&\n+\tgit commit --allow-empty -m initial &&\n+\n+\tgit checkout -b noop &&\n+\tgit checkout -b ff &&\n+\tgit checkout -b noff &&\n+\n+\t# noop stays the same, ff advances, noff rewrites\n+\ttest_tick &&\n+\tgit commit --allow-empty --amend -m rewritten &&\n+\tgit checkout ff &&\n+\n+\ttest_tick &&\n+\tgit commit --allow-empty -m second\n+'\n+\n+test_expect_success 'unsigned push does not send push certificate' '\n+\tprepare_dst &&\n+\tmkdir -p dst/.git/hooks &&\n+\twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n+\t# discard the update list\n+\tcat >/dev/null\n+\t# record the push certificate\n+\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tthen\n+\t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n+\tfi\n+\tEOF\n+\n+\tgit push dst noop ff +noff &&\n+\t! test -f dst/push-cert\n+'\n+\n+test_expect_success 'talking with a receiver without push certificate support' '\n+\tprepare_dst &&\n+\tmkdir -p dst/.git/hooks &&\n+\tgit -C dst config receive.acceptpushcert no &&\n+\twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n+\t# discard the update list\n+\tcat >/dev/null\n+\t# record the push certificate\n+\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tthen\n+\t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n+\tfi\n+\tEOF\n+\n+\tgit push dst noop ff +noff &&\n+\t! test -f dst/push-cert\n+'\n+\n+test_expect_success 'push --signed fails with a receiver without push certificate support' '\n+\tprepare_dst &&\n+\tmkdir -p dst/.git/hooks &&\n+\tgit -C dst config receive.acceptpushcert no &&\n+\ttest_must_fail git push --signed dst noop ff +noff 2>err &&\n+\ttest_i18ngrep \"the receiving end does not support\" err\n+'\n+\n+test_expect_success GPG 'signed push sends push certificate' '\n+\tprepare_dst &&\n+\tmkdir -p dst/.git/hooks &&\n+\twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n+\t# discard the update list\n+\tcat >/dev/null\n+\t# record the push certificate\n+\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tthen\n+\t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n+\tfi\n+\tEOF\n+\n+\tgit push --signed dst noop ff +noff &&\n+\tgrep \"$(git rev-parse noop ff) refs/heads/ff\" dst/push-cert &&\n+\tgrep \"$(git rev-parse noop noff) refs/heads/noff\" dst/push-cert\n+'\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 662421b..07fdf86 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -480,6 +480,9 @@ static int set_git_option(struct git_transport_options *opts,\n \t\t\t\tdie(\"transport: invalid depth option '%s'\", value);\n \t\t}\n \t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_PUSH_CERT)) {\n+\t\topts->push_cert = !!value;\n+\t\treturn 0;\n \t}\n \treturn 1;\n }\n@@ -823,6 +826,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \targs.progress = transport->progress;\n \targs.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);\n \targs.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);\n+\targs.push_cert = !!(flags & TRANSPORT_PUSH_CERT);\n \n \tret = send_pack(&args, data->fd, data->conn, remote_refs,\n \t\t\t&data->extra_have);\ndiff --git a/transport.h b/transport.h\nindex 02ea248..3e0091e 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -12,6 +12,7 @@ struct git_transport_options {\n \tunsigned check_self_contained_and_connected : 1;\n \tunsigned self_contained_and_connected : 1;\n \tunsigned update_shallow : 1;\n+\tunsigned push_cert : 1;\n \tint depth;\n \tconst char *uploadpack;\n \tconst char *receivepack;\n@@ -123,6 +124,7 @@ struct transport {\n #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256\n #define TRANSPORT_PUSH_NO_HOOK 512\n #define TRANSPORT_PUSH_FOLLOW_TAGS 1024\n+#define TRANSPORT_PUSH_CERT 2048\n \n #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)\n #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)\n@@ -156,6 +158,9 @@ struct transport *transport_get(struct remote *, const char *);\n /* Accept refs that may update .git/shallow without --depth */\n #define TRANS_OPT_UPDATE_SHALLOW \"updateshallow\"\n \n+/* Send push certificates */\n+#define TRANS_OPT_PUSH_CERT \"pushcert\"\n+\n /**\n  * Returns 0 if the option was used, non-zero otherwise. Prints a\n  * message to stderr if the option is not used.\n-- \n2.1.0-403-g099cf47\n"},{"id":"249614","messageId":"1410993958-32394-18-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 17/23] receive-pack: GPG-validate push certificates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:52Z","receivedAt":"2014-09-17T22:45:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Reusing the GPG signature check helpers we already have, verify\nthe signature in receive-pack and give the results to the hooks\nvia GIT_PUSH_CERT_{SIGNER,KEY,STATUS} environment variables.\n\nPolicy decisions, such as accepting or rejecting a good signature by\na key that is not fully trusted, is left to the hook and kept\noutside of the core.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n Documentation/git-receive-pack.txt | 24 +++++++++++++++++++-----\n builtin/receive-pack.c             | 31 +++++++++++++++++++++++++++++++\n t/t5534-push-signed.sh             | 18 ++++++++++++++++--\n 3 files changed, 66 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt\nindex a2dd743..e6df234 100644\n--- a/Documentation/git-receive-pack.txt\n+++ b/Documentation/git-receive-pack.txt\n@@ -56,7 +56,21 @@ sha1-old and sha1-new should be valid objects in the repository.\n When accepting a signed push (see linkgit:git-push[1]), the signed\n push certificate is stored in a blob and an environment variable\n `GIT_PUSH_CERT` can be consulted for its object name.  See the\n-description of `post-receive` hook for an example.\n+description of `post-receive` hook for an example.  In addition, the\n+certificate is verified using GPG and the result is exported with\n+the following environment variables:\n+\n+`GIT_PUSH_CERT_SIGNER`::\n+\tThe name and the e-mail address of the owner of the key that\n+\tsigned the push certificate.\n+\n+`GIT_PUSH_CERT_KEY`::\n+\tThe GPG key ID of the key that signed the push certificate.\n+\n+`GIT_PUSH_CERT_STATUS`::\n+\tThe status of GPG verification of the push certificate,\n+\tusing the same mnemonic as used in `%G?` format of `git log`\n+\tfamily of commands (see linkgit:git-log[1]).\n \n This hook is called before any refname is updated and before any\n fast-forward checks are performed.\n@@ -106,13 +120,13 @@ the update.  Refs that were created will have sha1-old equal to\n 0\\{40}, otherwise sha1-old and sha1-new should be valid objects in\n the repository.\n \n-The `GIT_PUSH_CERT` environment variable can be inspected, just as\n+The `GIT_PUSH_CERT*` environment variables can be inspected, just as\n in `pre-receive` hook, after accepting a signed push.\n \n Using this hook, it is easy to generate mails describing the updates\n to the repository.  This example script sends one mail message per\n ref listing the commits pushed to the repository, and logs the push\n-certificates of signed pushes to a logger\n+certificates of signed pushes with good signatures to a logger\n service:\n \n \t#!/bin/sh\n@@ -130,11 +144,11 @@ service:\n \t\tmail -s \"Changes to ref $ref\" commit-list@mydomain\n \tdone\n \t# log signed push certificate, if any\n-\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tif test -n \"${GIT_PUSH_CERT-}\" && test ${GIT_PUSH_CERT_STATUS} = G\n \tthen\n \t\t(\n \t\t\tgit cat-file blob ${GIT_PUSH_CERT}\n-\t\t) | mail -s \"push certificate\" push-log@mydomain\n+\t\t) | mail -s \"push certificate from $GIT_PUSH_CERT_SIGNER\" push-log@mydomain\n \tfi\n \texit 0\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 610b085..c0a3189 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -15,6 +15,8 @@\n #include \"connected.h\"\n #include \"argv-array.h\"\n #include \"version.h\"\n+#include \"tag.h\"\n+#include \"gpg-interface.h\"\n \n static const char receive_pack_usage[] = \"git receive-pack <git-dir>\";\n \n@@ -49,6 +51,7 @@ static const char *alt_shallow_file;\n static int accept_push_cert = 1;\n static struct strbuf push_cert = STRBUF_INIT;\n static unsigned char push_cert_sha1[20];\n+static struct signature_check sigcheck;\n \n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n@@ -277,12 +280,40 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\treturn;\n \n \tif (!already_done) {\n+\t\tstruct strbuf gpg_output = STRBUF_INIT;\n+\t\tstruct strbuf gpg_status = STRBUF_INIT;\n+\t\tint bogs /* beginning_of_gpg_sig */;\n+\n \t\talready_done = 1;\n \t\tif (write_sha1_file(push_cert.buf, push_cert.len, \"blob\", push_cert_sha1))\n \t\t\thashclr(push_cert_sha1);\n+\n+\t\tmemset(&sigcheck, '\\0', sizeof(sigcheck));\n+\t\tsigcheck.result = 'N';\n+\n+\t\tbogs = parse_signature(push_cert.buf, push_cert.len);\n+\t\tif (verify_signed_buffer(push_cert.buf, bogs,\n+\t\t\t\t\t push_cert.buf + bogs, push_cert.len - bogs,\n+\t\t\t\t\t &gpg_output, &gpg_status) < 0) {\n+\t\t\t; /* error running gpg */\n+\t\t} else {\n+\t\t\tsigcheck.payload = push_cert.buf;\n+\t\t\tsigcheck.gpg_output = gpg_output.buf;\n+\t\t\tsigcheck.gpg_status = gpg_status.buf;\n+\t\t\tparse_gpg_output(&sigcheck);\n+\t\t}\n+\n+\t\tstrbuf_release(&gpg_output);\n+\t\tstrbuf_release(&gpg_status);\n \t}\n \tif (!is_null_sha1(push_cert_sha1)) {\n \t\targv_array_pushf(&env, \"GIT_PUSH_CERT=%s\", sha1_to_hex(push_cert_sha1));\n+\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_SIGNER=%s\",\n+\t\t\t\t sigcheck.signer ? sigcheck.signer : \"\");\n+\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_KEY=%s\",\n+\t\t\t\t sigcheck.key ? sigcheck.key : \"\");\n+\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_STATUS=%c\", sigcheck.result);\n+\n \t\tproc->env = env.argv;\n \t}\n }\ndiff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh\nindex 019ac71..4198b6a 100755\n--- a/t/t5534-push-signed.sh\n+++ b/t/t5534-push-signed.sh\n@@ -83,12 +83,26 @@ test_expect_success GPG 'signed push sends push certificate' '\n \tif test -n \"${GIT_PUSH_CERT-}\"\n \tthen\n \t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n-\tfi\n+\tfi &&\n+\n+\tcat >../push-cert-status <<E_O_F\n+\tSIGNER=${GIT_PUSH_CERT_SIGNER-nobody}\n+\tKEY=${GIT_PUSH_CERT_KEY-nokey}\n+\tSTATUS=${GIT_PUSH_CERT_STATUS-nostatus}\n+\tE_O_F\n+\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tSIGNER=C O Mitter <committer@example.com>\n+\tKEY=13B6F51ECDDE430D\n+\tSTATUS=G\n \tEOF\n \n \tgit push --signed dst noop ff +noff &&\n \tgrep \"$(git rev-parse noop ff) refs/heads/ff\" dst/push-cert &&\n-\tgrep \"$(git rev-parse noop noff) refs/heads/noff\" dst/push-cert\n+\tgrep \"$(git rev-parse noop noff) refs/heads/noff\" dst/push-cert &&\n+\ttest_cmp expect dst/push-cert-status\n '\n \n test_done\n-- \n2.1.0-403-g099cf47\n"},{"id":"249621","messageId":"1410993958-32394-19-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 18/23] send-pack: send feature request on push-cert packet","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:53Z","receivedAt":"2014-09-17T22:45:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"We would want to update the interim protocol so that we do not send\nthe usual update commands when the push certificate feature is in\nuse, as the same information is in the certificate.  Once that\nhappens, the push-cert packet may become the only protocol command,\nbut then there is no packet to put the feature request behind, like\nwe always did.\n\nAs we have prepared the receiving end that understands the push-cert\nfeature to accept the feature request on the first protocol packet\n(other than \"shallow \", which was an unfortunate historical mistake\nthat has to come before everything else), we can give the feature\nrequest on the push-cert packet instead of the first update protocol\npacket, in preparation for the next step to actually update to the\nfinal protocol.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n send-pack.c            | 13 ++++++++-----\n t/t5534-push-signed.sh | 13 +++++++++++++\n 2 files changed, 21 insertions(+), 5 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex ef93f33..d392f5b 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -225,9 +225,10 @@ static const char *next_line(const char *line, size_t len)\n \treturn nl + 1;\n }\n \n-static void generate_push_cert(struct strbuf *req_buf,\n-\t\t\t       const struct ref *remote_refs,\n-\t\t\t       struct send_pack_args *args)\n+static int generate_push_cert(struct strbuf *req_buf,\n+\t\t\t      const struct ref *remote_refs,\n+\t\t\t      struct send_pack_args *args,\n+\t\t\t      const char *cap_string)\n {\n \tconst struct ref *ref;\n \tchar stamp[60];\n@@ -256,7 +257,7 @@ static void generate_push_cert(struct strbuf *req_buf,\n \tif (sign_buffer(&cert, &cert, signing_key))\n \t\tdie(_(\"failed to sign the push certificate\"));\n \n-\tpacket_buf_write(req_buf, \"push-cert\\n\");\n+\tpacket_buf_write(req_buf, \"push-cert%c%s\", 0, cap_string);\n \tfor (cp = cert.buf; cp < cert.buf + cert.len; cp = np) {\n \t\tnp = next_line(cp, cert.buf + cert.len - cp);\n \t\tpacket_buf_write(req_buf,\n@@ -267,6 +268,7 @@ static void generate_push_cert(struct strbuf *req_buf,\n free_return:\n \tfree(signing_key);\n \tstrbuf_release(&cert);\n+\treturn update_seen;\n }\n \n int send_pack(struct send_pack_args *args,\n@@ -335,7 +337,8 @@ int send_pack(struct send_pack_args *args,\n \t\tadvertise_shallow_grafts_buf(&req_buf);\n \n \tif (!args->dry_run && args->push_cert)\n-\t\tgenerate_push_cert(&req_buf, remote_refs, args);\n+\t\tcmds_sent = generate_push_cert(&req_buf, remote_refs, args,\n+\t\t\t\t\t       cap_buf.buf);\n \n \t/*\n \t * Clear the status for each ref and see if we need to send\ndiff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh\nindex 4198b6a..2f4b74e 100755\n--- a/t/t5534-push-signed.sh\n+++ b/t/t5534-push-signed.sh\n@@ -73,6 +73,19 @@ test_expect_success 'push --signed fails with a receiver without push certificat\n \ttest_i18ngrep \"the receiving end does not support\" err\n '\n \n+test_expect_success GPG 'no certificate for a signed push with no update' '\n+\tprepare_dst &&\n+\tmkdir -p dst/.git/hooks &&\n+\twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n+\tif test -n \"${GIT_PUSH_CERT-}\"\n+\tthen\n+\t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n+\tfi\n+\tEOF\n+\tgit push dst noop &&\n+\t! test -f dst/push-cert\n+'\n+\n test_expect_success GPG 'signed push sends push certificate' '\n \tprepare_dst &&\n \tmkdir -p dst/.git/hooks &&\n-- \n2.1.0-403-g099cf47\n"},{"id":"249615","messageId":"1410993958-32394-20-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 19/23] signed push: remove duplicated protocol info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:54Z","receivedAt":"2014-09-17T22:45:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"With the interim protocol, we used to send the update commands even\nthough we already send a signed copy of the same information when\npush certificate is in use.  Update the send-pack/receive-pack pair\nnot to do so.\n\nThe notable thing on the receive-pack side is that it makes sure\nthat there is no command sent over the traditional protocol packet\noutside the push certificate.  Otherwise a pusher can claim to be\npushing one set of ref updates in the signed certificate while\nissuing commands to update unrelated refs, and such an update will\nevade later audits.\n\nFinally, start documenting the protocol.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n Documentation/technical/pack-protocol.txt         | 33 ++++++++++++++++++++++-\n Documentation/technical/protocol-capabilities.txt | 12 +++++++--\n builtin/receive-pack.c                            | 26 ++++++++++++++++++\n send-pack.c                                       |  2 +-\n 4 files changed, 69 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex a845d51..4a5c2e8 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -465,7 +465,7 @@ contain all the objects that the server will need to complete the new\n references.\n \n ----\n-  update-request    =  *shallow command-list [pack-file]\n+  update-request    =  *shallow ( command-list | push-cert ) [pack-file]\n \n   shallow           =  PKT-LINE(\"shallow\" SP obj-id)\n \n@@ -481,12 +481,25 @@ references.\n   old-id            =  obj-id\n   new-id            =  obj-id\n \n+  push-cert         = PKT-LINE(\"push-cert\" NUL capability-list LF)\n+\t\t      PKT-LINE(\"certificate version 0.1\" LF)\n+\t\t      PKT-LINE(\"pusher\" SP ident LF)\n+\t\t      PKT-LINE(LF)\n+\t\t      *PKT-LINE(command LF)\n+\t\t      *PKT-LINE(gpg-signature-lines LF)\n+\t\t      PKT-LINE(\"push-cert-end\" LF)\n+\n   pack-file         = \"PACK\" 28*(OCTET)\n ----\n \n If the receiving end does not support delete-refs, the sending end MUST\n NOT ask for delete command.\n \n+If the receiving end does not support push-cert, the sending end\n+MUST NOT send a push-cert command.  When a push-cert command is\n+sent, command-list MUST NOT be sent; the commands recorded in the\n+push certificate is used instead.\n+\n The pack-file MUST NOT be sent if the only command used is 'delete'.\n \n A pack-file MUST be sent if either create or update command is used,\n@@ -501,6 +514,24 @@ was being processed (the obj-id is still the same as the old-id), and\n it will run any update hooks to make sure that the update is acceptable.\n If all of that is fine, the server will then update the references.\n \n+Push Certificate\n+----------------\n+\n+A push certificate begins with a set of header lines.  After the\n+header and an empty line, the protocol commands follow, one per\n+line.\n+\n+Currently, the following header fields are defined:\n+\n+`pusher` ident::\n+\tIdentify the GPG key in \"Human Readable Name <email@address>\"\n+\tformat.\n+\n+The GPG signature lines are a detached signature for the contents\n+recorded in the push certificate before the signature block begins.\n+The detached signature is used to certify that the commands were\n+given by the pusher, who must be the signer.\n+\n Report Status\n -------------\n \ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex e174343..a478cc4 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -18,8 +18,8 @@ was sent.  Server MUST NOT ignore capabilities that client requested\n and server advertised.  As a consequence of these rules, server MUST\n NOT advertise capabilities it does not understand.\n \n-The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and\n-recognized by the receive-pack (push to server) process.\n+The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities\n+are sent and recognized by the receive-pack (push to server) process.\n \n The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized\n by both upload-pack and receive-pack protocols.  The 'agent' capability\n@@ -250,3 +250,11 @@ allow-tip-sha1-in-want\n If the upload-pack server advertises this capability, fetch-pack may\n send \"want\" lines with SHA-1s that exist at the server but are not\n advertised by upload-pack.\n+\n+push-cert\n+---------\n+\n+The receive-pack server that advertises this capability is willing\n+to accept a signed push certificate.  A send-pack client MUST NOT\n+send a push-cert packet unless the receive-pack server advertises\n+this capability.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c0a3189..431af39 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -926,6 +926,28 @@ static struct command **queue_command(struct command **tail,\n \treturn &cmd->next;\n }\n \n+static void queue_commands_from_cert(struct command **tail,\n+\t\t\t\t     struct strbuf *push_cert)\n+{\n+\tconst char *boc, *eoc;\n+\n+\tif (*tail)\n+\t\tdie(\"protocol error: got both push certificate and unsigned commands\");\n+\n+\tboc = strstr(push_cert->buf, \"\\n\\n\");\n+\tif (!boc)\n+\t\tdie(\"malformed push certificate %.*s\", 100, push_cert->buf);\n+\telse\n+\t\tboc += 2;\n+\teoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len);\n+\n+\twhile (boc < eoc) {\n+\t\tconst char *eol = memchr(boc, '\\n', eoc - boc);\n+\t\ttail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);\n+\t\tboc = eol ? eol + 1 : eoc;\n+\t}\n+}\n+\n static struct command *read_head_info(struct sha1_array *shallow)\n {\n \tstruct command *commands = NULL;\n@@ -981,6 +1003,10 @@ static struct command *read_head_info(struct sha1_array *shallow)\n \n \t\tp = queue_command(p, line, linelen);\n \t}\n+\n+\tif (push_cert.len)\n+\t\tqueue_commands_from_cert(p, &push_cert);\n+\n \treturn commands;\n }\n \ndiff --git a/send-pack.c b/send-pack.c\nindex d392f5b..857beb3 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -363,7 +363,7 @@ int send_pack(struct send_pack_args *args,\n \tfor (ref = remote_refs; ref; ref = ref->next) {\n \t\tchar *old_hex, *new_hex;\n \n-\t\tif (args->dry_run)\n+\t\tif (args->dry_run || args->push_cert)\n \t\t\tcontinue;\n \n \t\tif (!ref_update_to_be_sent(ref, args))\n-- \n2.1.0-403-g099cf47\n"},{"id":"249616","messageId":"1410993958-32394-21-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 20/23] signed push: add \"pushee\" header to push certificate","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:55Z","receivedAt":"2014-09-17T22:45:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Record the URL of the intended recipient for a push (after\nanonymizing it if it has authentication material) on a new \"pushee\nURL\" header.  Because the networking configuration (SSH-tunnels,\nproxies, etc.) on the pushing user's side varies, the receiving\nrepository may not know the single canonical URL all the pushing\nusers would refer it as (besides, many sites allow pushing over\nssh://host/path and https://host/path protocols to the same\nrepository but with different local part of the path).  So this\nvalue may not be reliably used for replay-attack prevention\npurposes, but this will still serve as a human readable hint to\nidentify the repository the certificate refers to.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Unchanged since v5.\n\n Documentation/technical/pack-protocol.txt | 6 ++++++\n send-pack.c                               | 5 +++++\n send-pack.h                               | 1 +\n transport.c                               | 1 +\n 4 files changed, 13 insertions(+)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex 4a5c2e8..7b543dc 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -484,6 +484,7 @@ references.\n   push-cert         = PKT-LINE(\"push-cert\" NUL capability-list LF)\n \t\t      PKT-LINE(\"certificate version 0.1\" LF)\n \t\t      PKT-LINE(\"pusher\" SP ident LF)\n+\t\t      PKT-LINE(\"pushee\" SP url LF)\n \t\t      PKT-LINE(LF)\n \t\t      *PKT-LINE(command LF)\n \t\t      *PKT-LINE(gpg-signature-lines LF)\n@@ -527,6 +528,11 @@ Currently, the following header fields are defined:\n \tIdentify the GPG key in \"Human Readable Name <email@address>\"\n \tformat.\n \n+`pushee` url::\n+\tThe repository URL (anonymized, if the URL contains\n+\tauthentication material) the user who ran `git push`\n+\tintended to push into.\n+\n The GPG signature lines are a detached signature for the contents\n recorded in the push certificate before the signature block begins.\n The detached signature is used to certify that the commands were\ndiff --git a/send-pack.c b/send-pack.c\nindex 857beb3..9c2c649 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -240,6 +240,11 @@ static int generate_push_cert(struct strbuf *req_buf,\n \tdatestamp(stamp, sizeof(stamp));\n \tstrbuf_addf(&cert, \"certificate version 0.1\\n\");\n \tstrbuf_addf(&cert, \"pusher %s %s\\n\", signing_key, stamp);\n+\tif (args->url && *args->url) {\n+\t\tchar *anon_url = transport_anonymize_url(args->url);\n+\t\tstrbuf_addf(&cert, \"pushee %s\\n\", anon_url);\n+\t\tfree(anon_url);\n+\t}\n \tstrbuf_addstr(&cert, \"\\n\");\n \n \tfor (ref = remote_refs; ref; ref = ref->next) {\ndiff --git a/send-pack.h b/send-pack.h\nindex 3555d8e..5635457 100644\n--- a/send-pack.h\n+++ b/send-pack.h\n@@ -2,6 +2,7 @@\n #define SEND_PACK_H\n \n struct send_pack_args {\n+\tconst char *url;\n \tunsigned verbose:1,\n \t\tquiet:1,\n \t\tporcelain:1,\ndiff --git a/transport.c b/transport.c\nindex 07fdf86..1df1375 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -827,6 +827,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \targs.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);\n \targs.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);\n \targs.push_cert = !!(flags & TRANSPORT_PUSH_CERT);\n+\targs.url = transport->url;\n \n \tret = send_pack(&args, data->fd, data->conn, remote_refs,\n \t\t\t&data->extra_have);\n-- \n2.1.0-403-g099cf47\n"},{"id":"249619","messageId":"1410993958-32394-22-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 21/23] signed push: fortify against replay attacks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:56Z","receivedAt":"2014-09-17T22:45:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"In order to prevent a valid push certificate for pushing into an\nrepository from getting replayed in a different push operation, send\na nonce string from the receive-pack process and have the signer\ninclude it in the push certificate.  The receiving end uses an HMAC\nhash of the path to the repository it serves and the current time\nstamp, hashed with a secret seed (the secret seed does not have to\nbe per-repository but can be defined in /etc/gitconfig) to generate\nthe nonce, in order to ensure that a random third party cannot forge\na nonce that looks like it originated from it.\n\nThe original nonce is exported as GIT_PUSH_CERT_NONCE for the hooks\nto examine and match against the value on the \"nonce\" header in the\ncertificate to notice a replay, but returned \"nonce\" header in the\npush certificate is examined by receive-pack and the result is\nexported as GIT_PUSH_CERT_NONCE_STATUS, whose value would be \"OK\"\nif the nonce recorded in the certificate matches what we expect, so\nthat the hooks can more easily check.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * check_nonce() in v5 leaked memory pointed by local variable\n   nonce, which has been plugged with this version.  t5534 did\n   not check GIT_PUSH_CERT_NONCE_STATUS passed to the hook but\n   now it does.\n\n Documentation/config.txt                          |  12 +-\n Documentation/git-receive-pack.txt                |  19 ++++\n Documentation/technical/pack-protocol.txt         |   6 +\n Documentation/technical/protocol-capabilities.txt |   7 +-\n builtin/receive-pack.c                            | 132 ++++++++++++++++++++--\n send-pack.c                                       |  18 ++-\n t/t5534-push-signed.sh                            |  22 ++--\n 7 files changed, 187 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0d01e32..dd6fd65 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2038,17 +2038,17 @@ rebase.autostash::\n \tsuccessful rebase might result in non-trivial conflicts.\n \tDefaults to false.\n \n-receive.acceptpushcert::\n-\tBy default, `git receive-pack` will advertise that it\n-\taccepts `git push --signed`.  Setting this variable to\n-\tfalse disables it (this is a tentative variable that\n-\twill go away at the end of this series).\n-\n receive.autogc::\n \tBy default, git-receive-pack will run \"git-gc --auto\" after\n \treceiving data from git-push and updating refs.  You can stop\n \tit by setting this variable to false.\n \n+receive.certnonceseed::\n+\tBy setting this variable to a string, `git receive-pack`\n+\twill accept a `git push --signed` and verifies it by using\n+\ta \"nonce\" protected by HMAC using this string as a secret\n+\tkey.\n+\n receive.fsckObjects::\n \tIf it is set to true, git-receive-pack will check all received\n \tobjects. It will abort in the case of a malformed object or a\ndiff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt\nindex e6df234..2d4b452 100644\n--- a/Documentation/git-receive-pack.txt\n+++ b/Documentation/git-receive-pack.txt\n@@ -72,6 +72,24 @@ the following environment variables:\n \tusing the same mnemonic as used in `%G?` format of `git log`\n \tfamily of commands (see linkgit:git-log[1]).\n \n+`GIT_PUSH_CERT_NONCE`::\n+\tThe nonce string the process asked the signer to include\n+\tin the push certificate.  If this does not match the value\n+\trecorded on the \"nonce\" header in the push certificate, it\n+\tmay indicate that the certificate is a valid one that is\n+\tbeing replayed from a separate \"git push\" session.\n+\n+`GIT_PUSH_CERT_NONCE_STATUS`::\n+`UNSOLICITED`;;\n+\t\"git push --signed\" sent a nonce when we did not ask it to\n+\tsend one.\n+`MISSING`;;\n+\t\"git push --signed\" did not send any nonce header.\n+`BAD`;;\n+\t\"git push --signed\" sent a bogus nonce.\n+`OK`;;\n+\t\"git push --signed\" sent the nonce we asked it to send.\n+\n This hook is called before any refname is updated and before any\n fast-forward checks are performed.\n \n@@ -147,6 +165,7 @@ service:\n \tif test -n \"${GIT_PUSH_CERT-}\" && test ${GIT_PUSH_CERT_STATUS} = G\n \tthen\n \t\t(\n+\t\t\techo expected nonce is ${GIT_PUSH_NONCE}\n \t\t\tgit cat-file blob ${GIT_PUSH_CERT}\n \t\t) | mail -s \"push certificate from $GIT_PUSH_CERT_SIGNER\" push-log@mydomain\n \tfi\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex 7b543dc..dda1206 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -485,6 +485,7 @@ references.\n \t\t      PKT-LINE(\"certificate version 0.1\" LF)\n \t\t      PKT-LINE(\"pusher\" SP ident LF)\n \t\t      PKT-LINE(\"pushee\" SP url LF)\n+\t\t      PKT-LINE(\"nonce\" SP nonce LF)\n \t\t      PKT-LINE(LF)\n \t\t      *PKT-LINE(command LF)\n \t\t      *PKT-LINE(gpg-signature-lines LF)\n@@ -533,6 +534,11 @@ Currently, the following header fields are defined:\n \tauthentication material) the user who ran `git push`\n \tintended to push into.\n \n+`nonce` nonce::\n+\tThe 'nonce' string the receiving repository asked the\n+\tpushing user to include in the certificate, to prevent\n+\treplay attacks.\n+\n The GPG signature lines are a detached signature for the contents\n recorded in the push certificate before the signature block begins.\n The detached signature is used to certify that the commands were\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex a478cc4..0c92dee 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -251,10 +251,11 @@ If the upload-pack server advertises this capability, fetch-pack may\n send \"want\" lines with SHA-1s that exist at the server but are not\n advertised by upload-pack.\n \n-push-cert\n----------\n+push-cert=<nonce>\n+-----------------\n \n The receive-pack server that advertises this capability is willing\n-to accept a signed push certificate.  A send-pack client MUST NOT\n+to accept a signed push certificate, and asks the <nonce> to be\n+included in the push certificate.  A send-pack client MUST NOT\n send a push-cert packet unless the receive-pack server advertises\n this capability.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 431af39..91d1a6f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -48,10 +48,17 @@ static void *head_name_to_free;\n static int sent_capabilities;\n static int shallow_update;\n static const char *alt_shallow_file;\n-static int accept_push_cert = 1;\n static struct strbuf push_cert = STRBUF_INIT;\n static unsigned char push_cert_sha1[20];\n static struct signature_check sigcheck;\n+static const char *push_cert_nonce;\n+static const char *cert_nonce_seed;\n+\n+static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n+static const char *NONCE_BAD = \"BAD\";\n+static const char *NONCE_MISSING = \"MISSING\";\n+static const char *NONCE_OK = \"OK\";\n+static const char *nonce_status;\n \n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n@@ -135,10 +142,8 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\tif (strcmp(var, \"receive.acceptpushcert\") == 0) {\n-\t\taccept_push_cert = git_config_bool(var, value);\n-\t\treturn 0;\n-\t}\n+\tif (strcmp(var, \"receive.certnonceseed\") == 0)\n+\t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \treturn git_default_config(var, value, cb);\n }\n@@ -157,8 +162,8 @@ static void show_ref(const char *path, const unsigned char *sha1)\n \t\t\t      \"report-status delete-refs side-band-64k quiet\");\n \t\tif (prefer_ofs_delta)\n \t\t\tstrbuf_addstr(&cap, \" ofs-delta\");\n-\t\tif (accept_push_cert)\n-\t\t\tstrbuf_addstr(&cap, \" push-cert\");\n+\t\tif (push_cert_nonce)\n+\t\t\tstrbuf_addf(&cap, \" push-cert=%s\", push_cert_nonce);\n \t\tstrbuf_addf(&cap, \" agent=%s\", git_user_agent_sanitized());\n \t\tpacket_write(1, \"%s %s%c%s\\n\",\n \t\t\t     sha1_to_hex(sha1), path, 0, cap.buf);\n@@ -271,6 +276,110 @@ static int copy_to_sideband(int in, int out, void *arg)\n \treturn 0;\n }\n \n+#define HMAC_BLOCK_SIZE 64\n+\n+static void hmac_sha1(unsigned char out[20],\n+\t\t      const char *key_in, size_t key_len,\n+\t\t      const char *text, size_t text_len)\n+{\n+\tunsigned char key[HMAC_BLOCK_SIZE];\n+\tunsigned char k_ipad[HMAC_BLOCK_SIZE];\n+\tunsigned char k_opad[HMAC_BLOCK_SIZE];\n+\tint i;\n+\tgit_SHA_CTX ctx;\n+\n+\t/* RFC 2104 2. (1) */\n+\tmemset(key, '\\0', HMAC_BLOCK_SIZE);\n+\tif (HMAC_BLOCK_SIZE < key_len) {\n+\t\tgit_SHA1_Init(&ctx);\n+\t\tgit_SHA1_Update(&ctx, key_in, key_len);\n+\t\tgit_SHA1_Final(key, &ctx);\n+\t} else {\n+\t\tmemcpy(key, key_in, key_len);\n+\t}\n+\n+\t/* RFC 2104 2. (2) & (5) */\n+\tfor (i = 0; i < sizeof(key); i++) {\n+\t\tk_ipad[i] = key[i] ^ 0x36;\n+\t\tk_opad[i] = key[i] ^ 0x5c;\n+\t}\n+\n+\t/* RFC 2104 2. (3) & (4) */\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));\n+\tgit_SHA1_Update(&ctx, text, text_len);\n+\tgit_SHA1_Final(out, &ctx);\n+\n+\t/* RFC 2104 2. (6) & (7) */\n+\tgit_SHA1_Init(&ctx);\n+\tgit_SHA1_Update(&ctx, k_opad, sizeof(k_opad));\n+\tgit_SHA1_Update(&ctx, out, sizeof(out));\n+\tgit_SHA1_Final(out, &ctx);\n+}\n+\n+static char *prepare_push_cert_nonce(const char *path, unsigned long stamp)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tunsigned char sha1[20];\n+\n+\tstrbuf_addf(&buf, \"%s:%lu\", path, stamp);\n+\thmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;\n+\tstrbuf_release(&buf);\n+\n+\t/* RFC 2104 5. HMAC-SHA1-80 */\n+\tstrbuf_addf(&buf, \"%lu-%.*s\", stamp, 20, sha1_to_hex(sha1));\n+\treturn strbuf_detach(&buf, NULL);\n+}\n+\n+/*\n+ * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing\n+ * after dropping \"_commit\" from its name and possibly moving it out\n+ * of commit.c\n+ */\n+static char *find_header(const char *msg, size_t len, const char *key)\n+{\n+\tint key_len = strlen(key);\n+\tconst char *line = msg;\n+\n+\twhile (line && line < msg + len) {\n+\t\tconst char *eol = strchrnul(line, '\\n');\n+\n+\t\tif ((msg + len <= eol) || line == eol)\n+\t\t\treturn NULL;\n+\t\tif (line + key_len < eol &&\n+\t\t    !memcmp(line, key, key_len) && line[key_len] == ' ') {\n+\t\t\tint offset = key_len + 1;\n+\t\t\treturn xmemdupz(line + offset, (eol - line) - offset);\n+\t\t}\n+\t\tline = *eol ? eol + 1 : NULL;\n+\t}\n+\treturn NULL;\n+}\n+\n+static const char *check_nonce(const char *buf, size_t len)\n+{\n+\tchar *nonce = find_header(buf, len, \"nonce\");\n+\tconst char *retval = NONCE_BAD;\n+\n+\tif (!nonce) {\n+\t\tretval = NONCE_MISSING;\n+\t\tgoto leave;\n+\t} else if (!push_cert_nonce) {\n+\t\tretval = NONCE_UNSOLICITED;\n+\t\tgoto leave;\n+\t} else if (!strcmp(push_cert_nonce, nonce)) {\n+\t\tretval = NONCE_OK;\n+\t\tgoto leave;\n+\t}\n+\n+\t/* returned nonce MUST match what we gave out earlier */\n+\tretval = NONCE_BAD;\n+\n+leave:\n+\tfree(nonce);\n+\treturn retval;\n+}\n+\n static void prepare_push_cert_sha1(struct child_process *proc)\n {\n \tstatic int already_done;\n@@ -305,6 +414,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n \t\tstrbuf_release(&gpg_output);\n \t\tstrbuf_release(&gpg_status);\n+\t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\n \tif (!is_null_sha1(push_cert_sha1)) {\n \t\targv_array_pushf(&env, \"GIT_PUSH_CERT=%s\", sha1_to_hex(push_cert_sha1));\n@@ -313,7 +423,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\targv_array_pushf(&env, \"GIT_PUSH_CERT_KEY=%s\",\n \t\t\t\t sigcheck.key ? sigcheck.key : \"\");\n \t\targv_array_pushf(&env, \"GIT_PUSH_CERT_STATUS=%c\", sigcheck.result);\n-\n+\t\tif (push_cert_nonce) {\n+\t\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_NONCE=%s\", push_cert_nonce);\n+\t\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_NONCE_STATUS=%s\", nonce_status);\n+\t\t}\n \t\tproc->env = env.argv;\n \t}\n }\n@@ -1296,6 +1409,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tdie(\"'%s' does not appear to be a git repository\", dir);\n \n \tgit_config(receive_pack_config, NULL);\n+\tif (cert_nonce_seed)\n+\t\tpush_cert_nonce = prepare_push_cert_nonce(dir, time(NULL));\n \n \tif (0 <= transfer_unpack_limit)\n \t\tunpack_limit = transfer_unpack_limit;\n@@ -1340,5 +1455,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tpacket_flush(1);\n \tsha1_array_clear(&shallow);\n \tsha1_array_clear(&ref);\n+\tfree((void *)push_cert_nonce);\n \treturn 0;\n }\ndiff --git a/send-pack.c b/send-pack.c\nindex 9c2c649..7ad1a59 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -228,7 +228,8 @@ static const char *next_line(const char *line, size_t len)\n static int generate_push_cert(struct strbuf *req_buf,\n \t\t\t      const struct ref *remote_refs,\n \t\t\t      struct send_pack_args *args,\n-\t\t\t      const char *cap_string)\n+\t\t\t      const char *cap_string,\n+\t\t\t      const char *push_cert_nonce)\n {\n \tconst struct ref *ref;\n \tchar stamp[60];\n@@ -245,6 +246,8 @@ static int generate_push_cert(struct strbuf *req_buf,\n \t\tstrbuf_addf(&cert, \"pushee %s\\n\", anon_url);\n \t\tfree(anon_url);\n \t}\n+\tif (push_cert_nonce[0])\n+\t\tstrbuf_addf(&cert, \"nonce %s\\n\", push_cert_nonce);\n \tstrbuf_addstr(&cert, \"\\n\");\n \n \tfor (ref = remote_refs; ref; ref = ref->next) {\n@@ -295,6 +298,7 @@ int send_pack(struct send_pack_args *args,\n \tunsigned cmds_sent = 0;\n \tint ret;\n \tstruct async demux;\n+\tconst char *push_cert_nonce = NULL;\n \n \t/* Does the other end support the reporting? */\n \tif (server_supports(\"report-status\"))\n@@ -311,8 +315,14 @@ int send_pack(struct send_pack_args *args,\n \t\tagent_supported = 1;\n \tif (server_supports(\"no-thin\"))\n \t\targs->use_thin_pack = 0;\n-\tif (args->push_cert && !server_supports(\"push-cert\"))\n-\t\tdie(_(\"the receiving end does not support --signed push\"));\n+\tif (args->push_cert) {\n+\t\tint len;\n+\n+\t\tpush_cert_nonce = server_feature_value(\"push-cert\", &len);\n+\t\tif (!push_cert_nonce)\n+\t\t\tdie(_(\"the receiving end does not support --signed push\"));\n+\t\tpush_cert_nonce = xmemdupz(push_cert_nonce, len);\n+\t}\n \n \tif (!remote_refs) {\n \t\tfprintf(stderr, \"No refs in common and none specified; doing nothing.\\n\"\n@@ -343,7 +353,7 @@ int send_pack(struct send_pack_args *args,\n \n \tif (!args->dry_run && args->push_cert)\n \t\tcmds_sent = generate_push_cert(&req_buf, remote_refs, args,\n-\t\t\t\t\t       cap_buf.buf);\n+\t\t\t\t\t       cap_buf.buf, push_cert_nonce);\n \n \t/*\n \t * Clear the status for each ref and see if we need to send\ndiff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh\nindex 2f4b74e..2786346 100755\n--- a/t/t5534-push-signed.sh\n+++ b/t/t5534-push-signed.sh\n@@ -50,7 +50,6 @@ test_expect_success 'unsigned push does not send push certificate' '\n test_expect_success 'talking with a receiver without push certificate support' '\n \tprepare_dst &&\n \tmkdir -p dst/.git/hooks &&\n-\tgit -C dst config receive.acceptpushcert no &&\n \twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n \t# discard the update list\n \tcat >/dev/null\n@@ -68,7 +67,6 @@ test_expect_success 'talking with a receiver without push certificate support' '\n test_expect_success 'push --signed fails with a receiver without push certificate support' '\n \tprepare_dst &&\n \tmkdir -p dst/.git/hooks &&\n-\tgit -C dst config receive.acceptpushcert no &&\n \ttest_must_fail git push --signed dst noop ff +noff 2>err &&\n \ttest_i18ngrep \"the receiving end does not support\" err\n '\n@@ -89,6 +87,7 @@ test_expect_success GPG 'no certificate for a signed push with no update' '\n test_expect_success GPG 'signed push sends push certificate' '\n \tprepare_dst &&\n \tmkdir -p dst/.git/hooks &&\n+\tgit -C dst config receive.certnonceseed sekrit &&\n \twrite_script dst/.git/hooks/post-receive <<-\\EOF &&\n \t# discard the update list\n \tcat >/dev/null\n@@ -102,17 +101,24 @@ test_expect_success GPG 'signed push sends push certificate' '\n \tSIGNER=${GIT_PUSH_CERT_SIGNER-nobody}\n \tKEY=${GIT_PUSH_CERT_KEY-nokey}\n \tSTATUS=${GIT_PUSH_CERT_STATUS-nostatus}\n+\tNONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}\n+\tNONCE=${GIT_PUSH_CERT_NONCE-nononce}\n \tE_O_F\n \n \tEOF\n \n-\tcat >expect <<-\\EOF &&\n-\tSIGNER=C O Mitter <committer@example.com>\n-\tKEY=13B6F51ECDDE430D\n-\tSTATUS=G\n-\tEOF\n-\n \tgit push --signed dst noop ff +noff &&\n+\n+\t(\n+\t\tcat <<-\\EOF &&\n+\t\tSIGNER=C O Mitter <committer@example.com>\n+\t\tKEY=13B6F51ECDDE430D\n+\t\tSTATUS=G\n+\t\tNONCE_STATUS=OK\n+\t\tEOF\n+\t\tsed -n -e \"s/^nonce /NONCE=/p\" -e \"/^$/q\" dst/push-cert\n+\t) >expect &&\n+\n \tgrep \"$(git rev-parse noop ff) refs/heads/ff\" dst/push-cert &&\n \tgrep \"$(git rev-parse noop noff) refs/heads/noff\" dst/push-cert &&\n \ttest_cmp expect dst/push-cert-status\n-- \n2.1.0-403-g099cf47\n"},{"id":"249617","messageId":"1410993958-32394-23-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 22/23] signed push: teach smart-HTTP to pass \"git push --signed\" around","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:57Z","receivedAt":"2014-09-17T22:45:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The \"--signed\" option received by \"git push\" is first passed to the\ntransport layer, which the native transport directly uses to notice\nthat a push certificate needs to be sent.  When the transport-helper\nis involved, however, the option needs to be told to the helper with\nset_helper_option(), and the helper needs to take necessary action.\nFor the smart-HTTP helper, the \"necessary action\" involves spawning\nthe \"git send-pack\" subprocess with the \"--signed\" option.\n\nOnce the above all gets wired in, the smart-HTTP transport now can\nuse the push certificate mechanism to authenticate its pushes.\n\nAdd a test that is modeled after tests for the native transport in\nt5534-push-signed.sh to t5541-http-push-smart.sh.  Update the test\nApache configuration to pass GNUPGHOME environment variable through.\nAs PassEnv would trigger warnings for an environment variable that\nis not set, export it from test-lib.sh set to a harmless value when\nGnuPG is not being used in the tests.\n\nNote that the added test is deliberately loose and does not check\nthe nonce in this step.  This is because the stateless RPC mode is\ninevitably flaky and a nonce that comes back in the actual push\nprocessing is one issued by a different process; if the two\ninteractions with the server crossed a second boundary, the nonces\nwill not match and such a check will fail.  A later patch in the\nseries will work around this shortcoming.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * This used to be after \"nonce slop\" patch in v5 (and remote-curl\n   integration was missing).\n\n builtin/send-pack.c        |  4 ++++\n remote-curl.c              | 13 ++++++++++++-\n t/lib-httpd/apache.conf    |  1 +\n t/t5541-http-push-smart.sh | 36 ++++++++++++++++++++++++++++++++++++\n t/test-lib.sh              |  3 ++-\n transport-helper.c         |  9 ++++++++-\n 6 files changed, 63 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex f420b74..ca28d8d 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -153,6 +153,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\targs.verbose = 1;\n \t\t\t\tcontinue;\n \t\t\t}\n+\t\t\tif (!strcmp(arg, \"--signed\")) {\n+\t\t\t\targs.push_cert = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t\tif (!strcmp(arg, \"--progress\")) {\n \t\t\t\tprogress = 1;\n \t\t\t\tcontinue;\ndiff --git a/remote-curl.c b/remote-curl.c\nindex 0fcf2ce..1ea4e95 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -25,7 +25,8 @@ struct options {\n \t\tupdate_shallow : 1,\n \t\tfollowtags : 1,\n \t\tdry_run : 1,\n-\t\tthin : 1;\n+\t\tthin : 1,\n+\t\tpush_cert : 1;\n };\n static struct options options;\n static struct string_list cas_options = STRING_LIST_INIT_DUP;\n@@ -106,6 +107,14 @@ static int set_option(const char *name, const char *value)\n \t\telse\n \t\t\treturn -1;\n \t\treturn 0;\n+\t} else if (!strcmp(name, \"pushcert\")) {\n+\t\tif (!strcmp(value, \"true\"))\n+\t\t\toptions.push_cert = 1;\n+\t\telse if (!strcmp(value, \"false\"))\n+\t\t\toptions.push_cert = 0;\n+\t\telse\n+\t\t\treturn -1;\n+\t\treturn 0;\n \t} else {\n \t\treturn 1 /* unsupported */;\n \t}\n@@ -872,6 +881,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)\n \t\targv_array_push(&args, \"--thin\");\n \tif (options.dry_run)\n \t\targv_array_push(&args, \"--dry-run\");\n+\tif (options.push_cert)\n+\t\targv_array_push(&args, \"--signed\");\n \tif (options.verbosity == 0)\n \t\targv_array_push(&args, \"--quiet\");\n \telse if (options.verbosity > 1)\ndiff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf\nindex b384d79..7713dd2 100644\n--- a/t/lib-httpd/apache.conf\n+++ b/t/lib-httpd/apache.conf\n@@ -68,6 +68,7 @@ LockFile accept.lock\n \n PassEnv GIT_VALGRIND\n PassEnv GIT_VALGRIND_OPTIONS\n+PassEnv GNUPGHOME\n \n Alias /dumb/ www/\n Alias /auth/dumb/ www/auth/dumb/\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 73af16f..24926a4 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -12,6 +12,7 @@ if test -n \"$NO_CURL\"; then\n fi\n \n ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n . \"$TEST_DIRECTORY\"/lib-httpd.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n start_httpd\n@@ -323,5 +324,40 @@ test_expect_success 'push into half-auth-complete requires password' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success GPG 'push with post-receive to inspect certificate' '\n+\t(\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH\"/test_repo.git &&\n+\t\tmkdir -p hooks &&\n+\t\twrite_script hooks/post-receive <<-\\EOF &&\n+\t\t# discard the update list\n+\t\tcat >/dev/null\n+\t\t# record the push certificate\n+\t\tif test -n \"${GIT_PUSH_CERT-}\"\n+\t\tthen\n+\t\t\tgit cat-file blob $GIT_PUSH_CERT >../push-cert\n+\t\tfi &&\n+\t\tcat >../push-cert-status <<E_O_F\n+\t\tSIGNER=${GIT_PUSH_CERT_SIGNER-nobody}\n+\t\tKEY=${GIT_PUSH_CERT_KEY-nokey}\n+\t\tSTATUS=${GIT_PUSH_CERT_STATUS-nostatus}\n+\t\tE_O_F\n+\t\tEOF\n+\n+\t\tgit config receive.certnonceseed sekrit\n+\t) &&\n+\tcd \"$ROOT_PATH/test_repo_clone\" &&\n+\ttest_commit cert-test &&\n+\tgit push --signed \"$HTTPD_URL/smart/test_repo.git\" &&\n+\t(\n+\t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH\" &&\n+\t\tcat <<-\\EOF\n+\t\tSIGNER=C O Mitter <committer@example.com>\n+\t\tKEY=13B6F51ECDDE430D\n+\t\tSTATUS=G\n+\t\tEOF\n+\t) >expect &&\n+\ttest_cmp expect \"$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status\"\n+'\n+\n stop_httpd\n test_done\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex b1bc65b..d5939b7 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -813,7 +813,8 @@ rm -fr \"$TRASH_DIRECTORY\" || {\n }\n \n HOME=\"$TRASH_DIRECTORY\"\n-export HOME\n+GNUPGHOME=\"$HOME/gnupg-home-not-used\"\n+export HOME GNUPGHOME\n \n if test -z \"$TEST_NO_CREATE_REPO\"\n then\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 3d8fe7d..4b1a261 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -259,7 +259,8 @@ static const char *unsupported_options[] = {\n static const char *boolean_options[] = {\n \tTRANS_OPT_THIN,\n \tTRANS_OPT_KEEP,\n-\tTRANS_OPT_FOLLOWTAGS\n+\tTRANS_OPT_FOLLOWTAGS,\n+\tTRANS_OPT_PUSH_CERT\n \t};\n \n static int set_helper_option(struct transport *transport,\n@@ -835,6 +836,9 @@ static int push_refs_with_push(struct transport *transport,\n \tif (flags & TRANSPORT_PUSH_DRY_RUN) {\n \t\tif (set_helper_option(transport, \"dry-run\", \"true\") != 0)\n \t\t\tdie(\"helper %s does not support dry-run\", data->name);\n+\t} else if (flags & TRANSPORT_PUSH_CERT) {\n+\t\tif (set_helper_option(transport, TRANS_OPT_PUSH_CERT, \"true\") != 0)\n+\t\t\tdie(\"helper %s does not support --signed\", data->name);\n \t}\n \n \tstrbuf_addch(&buf, '\\n');\n@@ -859,6 +863,9 @@ static int push_refs_with_export(struct transport *transport,\n \tif (flags & TRANSPORT_PUSH_DRY_RUN) {\n \t\tif (set_helper_option(transport, \"dry-run\", \"true\") != 0)\n \t\t\tdie(\"helper %s does not support dry-run\", data->name);\n+\t} else if (flags & TRANSPORT_PUSH_CERT) {\n+\t\tif (set_helper_option(transport, TRANS_OPT_PUSH_CERT, \"true\") != 0)\n+\t\t\tdie(\"helper %s does not support dry-run\", data->name);\n \t}\n \n \tif (flags & TRANSPORT_PUSH_FORCE) {\n-- \n2.1.0-403-g099cf47\n"},{"id":"249618","messageId":"1410993958-32394-24-git-send-email-gitster@pobox.com","threadId":"37585","inReplyTo":"1410993958-32394-1-git-send-email-gitster@pobox.com","subject":"[PATCH v6 23/23] signed push: allow stale nonce in stateless mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-09-17T22:45:58Z","receivedAt":"2014-09-17T22:45:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"When operating with the stateless RPC mode, we will receive a nonce\nissued by another instance of us that advertised our capability and\nrefs some time ago.  Update the logic to check received nonce to\ndetect this case, compute how much time has passed since the nonce\nwas issued and report the status with a new environment variable\nGIT_PUSH_CERT_NONCE_SLOP to the hooks.\n\nGIT_PUSH_CERT_NONCE_STATUS will report \"SLOP\" in such a case.  The\nhooks are free to decide how large a slop it is willing to accept.\n\nStrictly speaking, the \"nonce\" is not really a \"nonce\" anymore in\nthe stateless RPC mode, as it will happily take any \"nonce\" issued\nby it (which is protected by HMAC and its secret key) as long as it\nis fresh enough.  The degree of this security degradation, relative\nto the native protocol, is about the same as the \"we make sure that\nthe 'git push' decided to update our refs with new objects based on\nthe freshest observation of our refs by making sure the values they\nclaim the original value of the refs they ask us to update exactly\nmatch the current state\" security is loosened to accomodate the\nstateless RPC mode in the existing code without this series, so\nthere is no need for those who are already using smart HTTP to push\nto their repositories to be alarmed any more than they already are.\n\nIn addition, the server operator can set receive.certnonceslop\nconfiguration variable to specify how stale a nonce can be (in\nseconds).  When this variable is set, and if the nonce received in\nthe certificate that passes the HMAC check was less than that many\nseconds old, hooks are given \"OK\" in GIT_PUSH_CERT_NONCE_STATUS\n(instead of \"SLOP\") and the received nonce value is given in\nGIT_PUSH_CERT_NONCE, which makes it easier for a simple-minded\nhook to check if the certificate we received is recent enough.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This roughly corresponds to 22/23 in v5; instead of always\n   forcing the hooks to check NONCE_SLOP, v6 also adds a mechanism\n   for the server operators to tell receive-pack to \"lie\" about the\n   nonce it requested when the push certificate received is fresh\n   enough to make it easier to code hooks.\n\n Documentation/config.txt           | 13 ++++++\n Documentation/git-receive-pack.txt | 13 ++++++\n builtin/receive-pack.c             | 89 +++++++++++++++++++++++++++++++++-----\n t/t5541-http-push-smart.sh         |  9 +++-\n 4 files changed, 112 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex dd6fd65..d73366f 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -2049,6 +2049,19 @@ receive.certnonceseed::\n \ta \"nonce\" protected by HMAC using this string as a secret\n \tkey.\n \n+receive.certnonceslop::\n+\tWhen a `git push --signed` sent a push certificate with a\n+\t\"nonce\" that was issued by a receive-pack serving the same\n+\trepository within this many seconds, export the \"nonce\"\n+\tfound in the certificate to `GIT_PUSH_CERT_NONCE` to the\n+\thooks (instead of what the receive-pack asked the sending\n+\tside to include).  This may allow writing checks in\n+\t`pre-receive` and `post-receive` a bit easier.  Instead of\n+\tchecking `GIT_PUSH_CERT_NONCE_SLOP` environment variable\n+\tthat records by how many seconds the nonce is stale to\n+\tdecide if they want to accept the certificate, they only\n+\tcan check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.\n+\n receive.fsckObjects::\n \tIf it is set to true, git-receive-pack will check all received\n \tobjects. It will abort in the case of a malformed object or a\ndiff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt\nindex 2d4b452..9016960 100644\n--- a/Documentation/git-receive-pack.txt\n+++ b/Documentation/git-receive-pack.txt\n@@ -89,6 +89,19 @@ the following environment variables:\n \t\"git push --signed\" sent a bogus nonce.\n `OK`;;\n \t\"git push --signed\" sent the nonce we asked it to send.\n+`SLOP`;;\n+\t\"git push --signed\" sent a nonce different from what we\n+\tasked it to send now, but in a previous session.  See\n+\t`GIT_PUSH_CERT_NONCE_SLOP` environment variable.\n+\n+`GIT_PUSH_CERT_NONCE_SLOP`::\n+\t\"git push --signed\" sent a nonce different from what we\n+\tasked it to send now, but in a different session whose\n+\tstarting time is different by this many seconds from the\n+\tcurrent session.  Only meaningful when\n+\t`GIT_PUSH_CERT_NONCE_STATUS` says `SLOP`.\n+\tAlso read about `receive.certnonceslop` variable in\n+\tlinkgit:git-config[1].\n \n This hook is called before any refname is updated and before any\n fast-forward checks are performed.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 91d1a6f..efb13b1 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -43,6 +43,8 @@ static int prefer_ofs_delta = 1;\n static int auto_update_server_info;\n static int auto_gc = 1;\n static int fix_thin = 1;\n+static int stateless_rpc;\n+static const char *service_dir;\n static const char *head_name;\n static void *head_name_to_free;\n static int sent_capabilities;\n@@ -58,7 +60,10 @@ static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n static const char *NONCE_MISSING = \"MISSING\";\n static const char *NONCE_OK = \"OK\";\n+static const char *NONCE_SLOP = \"SLOP\";\n static const char *nonce_status;\n+static long nonce_stamp_slop;\n+static unsigned long nonce_stamp_slop_limit;\n \n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n@@ -145,6 +150,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \tif (strcmp(var, \"receive.certnonceseed\") == 0)\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n+\tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -359,6 +369,8 @@ static char *find_header(const char *msg, size_t len, const char *key)\n static const char *check_nonce(const char *buf, size_t len)\n {\n \tchar *nonce = find_header(buf, len, \"nonce\");\n+\tunsigned long stamp, ostamp;\n+\tchar *bohmac, *expect = NULL;\n \tconst char *retval = NONCE_BAD;\n \n \tif (!nonce) {\n@@ -372,11 +384,67 @@ static const char *check_nonce(const char *buf, size_t len)\n \t\tgoto leave;\n \t}\n \n-\t/* returned nonce MUST match what we gave out earlier */\n-\tretval = NONCE_BAD;\n+\tif (!stateless_rpc) {\n+\t\t/* returned nonce MUST match what we gave out earlier */\n+\t\tretval = NONCE_BAD;\n+\t\tgoto leave;\n+\t}\n+\n+\t/*\n+\t * In stateless mode, we may be receiving a nonce issued by\n+\t * another instance of the server that serving the same\n+\t * repository, and the timestamps may not match, but the\n+\t * nonce-seed and dir should match, so we can recompute and\n+\t * report the time slop.\n+\t *\n+\t * In addition, when a nonce issued by another instance has\n+\t * timestamp within receive.certnonceslop seconds, we pretend\n+\t * as if we issued that nonce when reporting to the hook.\n+\t */\n+\n+\t/* nonce is concat(<seconds-since-epoch>, \"-\", <hmac>) */\n+\tif (*nonce <= '0' || '9' < *nonce) {\n+\t\tretval = NONCE_BAD;\n+\t\tgoto leave;\n+\t}\n+\tstamp = strtoul(nonce, &bohmac, 10);\n+\tif (bohmac == nonce || bohmac[0] != '-') {\n+\t\tretval = NONCE_BAD;\n+\t\tgoto leave;\n+\t}\n+\n+\texpect = prepare_push_cert_nonce(service_dir, stamp);\n+\tif (strcmp(expect, nonce)) {\n+\t\t/* Not what we would have signed earlier */\n+\t\tretval = NONCE_BAD;\n+\t\tgoto leave;\n+\t}\n+\n+\t/*\n+\t * By how many seconds is this nonce stale?  Negative value\n+\t * would mean it was issued by another server with its clock\n+\t * skewed in the future.\n+\t */\n+\tostamp = strtoul(push_cert_nonce, NULL, 10);\n+\tnonce_stamp_slop = (long)ostamp - (long)stamp;\n+\n+\tif (nonce_stamp_slop_limit &&\n+\t    abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {\n+\t\t/*\n+\t\t * Pretend as if the received nonce (which passes the\n+\t\t * HMAC check, so it is not a forged by third-party)\n+\t\t * is what we issued.\n+\t\t */\n+\t\tfree((void *)push_cert_nonce);\n+\t\tpush_cert_nonce = xstrdup(nonce);\n+\t\tretval = NONCE_OK;\n+\t} else {\n+\t\tretval = NONCE_SLOP;\n+\t}\n \n leave:\n \tfree(nonce);\n+\tfree(expect);\n \treturn retval;\n }\n \n@@ -426,6 +494,9 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tif (push_cert_nonce) {\n \t\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_NONCE=%s\", push_cert_nonce);\n \t\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_NONCE_STATUS=%s\", nonce_status);\n+\t\t\tif (nonce_status == NONCE_SLOP)\n+\t\t\t\targv_array_pushf(&env, \"GIT_PUSH_CERT_NONCE_SLOP=%ld\",\n+\t\t\t\t\t\t nonce_stamp_slop);\n \t\t}\n \t\tproc->env = env.argv;\n \t}\n@@ -1361,9 +1432,7 @@ static int delete_only(struct command *commands)\n int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n {\n \tint advertise_refs = 0;\n-\tint stateless_rpc = 0;\n \tint i;\n-\tconst char *dir = NULL;\n \tstruct command *commands;\n \tstruct sha1_array shallow = SHA1_ARRAY_INIT;\n \tstruct sha1_array ref = SHA1_ARRAY_INIT;\n@@ -1396,21 +1465,21 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \n \t\t\tusage(receive_pack_usage);\n \t\t}\n-\t\tif (dir)\n+\t\tif (service_dir)\n \t\t\tusage(receive_pack_usage);\n-\t\tdir = arg;\n+\t\tservice_dir = arg;\n \t}\n-\tif (!dir)\n+\tif (!service_dir)\n \t\tusage(receive_pack_usage);\n \n \tsetup_path();\n \n-\tif (!enter_repo(dir, 0))\n-\t\tdie(\"'%s' does not appear to be a git repository\", dir);\n+\tif (!enter_repo(service_dir, 0))\n+\t\tdie(\"'%s' does not appear to be a git repository\", service_dir);\n \n \tgit_config(receive_pack_config, NULL);\n \tif (cert_nonce_seed)\n-\t\tpush_cert_nonce = prepare_push_cert_nonce(dir, time(NULL));\n+\t\tpush_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));\n \n \tif (0 <= transfer_unpack_limit)\n \t\tunpack_limit = transfer_unpack_limit;\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 24926a4..ffb3af4 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -340,21 +340,26 @@ test_expect_success GPG 'push with post-receive to inspect certificate' '\n \t\tSIGNER=${GIT_PUSH_CERT_SIGNER-nobody}\n \t\tKEY=${GIT_PUSH_CERT_KEY-nokey}\n \t\tSTATUS=${GIT_PUSH_CERT_STATUS-nostatus}\n+\t\tNONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}\n+\t\tNONCE=${GIT_PUSH_CERT_NONCE-nononce}\n \t\tE_O_F\n \t\tEOF\n \n-\t\tgit config receive.certnonceseed sekrit\n+\t\tgit config receive.certnonceseed sekrit &&\n+\t\tgit config receive.certnonceslop 30\n \t) &&\n \tcd \"$ROOT_PATH/test_repo_clone\" &&\n \ttest_commit cert-test &&\n \tgit push --signed \"$HTTPD_URL/smart/test_repo.git\" &&\n \t(\n \t\tcd \"$HTTPD_DOCUMENT_ROOT_PATH\" &&\n-\t\tcat <<-\\EOF\n+\t\tcat <<-\\EOF &&\n \t\tSIGNER=C O Mitter <committer@example.com>\n \t\tKEY=13B6F51ECDDE430D\n \t\tSTATUS=G\n+\t\tNONCE_STATUS=OK\n \t\tEOF\n+\t\tsed -n -e \"s/^nonce /NONCE=/p\" -e \"/^$/q\" push-cert\n \t) >expect &&\n \ttest_cmp expect \"$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status\"\n '\n-- \n2.1.0-403-g099cf47\n"}]}