summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt13
-rw-r--r--Documentation/git-receive-pack.txt13
-rw-r--r--builtin/receive-pack.c89
-rwxr-xr-xt/t5541-http-push-smart.sh9
4 files changed, 112 insertions, 12 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index dd6fd65..d73366f 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2049,6 +2049,19 @@ receive.certnonceseed::
a "nonce" protected by HMAC using this string as a secret
key.
+receive.certnonceslop::
+ When a `git push --signed` sent a push certificate with a
+ "nonce" that was issued by a receive-pack serving the same
+ repository within this many seconds, export the "nonce"
+ found in the certificate to `GIT_PUSH_CERT_NONCE` to the
+ hooks (instead of what the receive-pack asked the sending
+ side to include). This may allow writing checks in
+ `pre-receive` and `post-receive` a bit easier. Instead of
+ checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
+ that records by how many seconds the nonce is stale to
+ decide if they want to accept the certificate, they only
+ can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
+
receive.fsckObjects::
If it is set to true, git-receive-pack will check all received
objects. It will abort in the case of a malformed object or a
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index 2d4b452..9016960 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -89,6 +89,19 @@ the following environment variables:
"git push --signed" sent a bogus nonce.
`OK`;;
"git push --signed" sent the nonce we asked it to send.
+`SLOP`;;
+ "git push --signed" sent a nonce different from what we
+ asked it to send now, but in a previous session. See
+ `GIT_PUSH_CERT_NONCE_SLOP` environment variable.
+
+`GIT_PUSH_CERT_NONCE_SLOP`::
+ "git push --signed" sent a nonce different from what we
+ asked it to send now, but in a different session whose
+ starting time is different by this many seconds from the
+ current session. Only meaningful when
+ `GIT_PUSH_CERT_NONCE_STATUS` says `SLOP`.
+ Also read about `receive.certnonceslop` variable in
+ linkgit:git-config[1].
This hook is called before any refname is updated and before any
fast-forward checks are performed.
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 91d1a6f..efb13b1 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -43,6 +43,8 @@ static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
static int fix_thin = 1;
+static int stateless_rpc;
+static const char *service_dir;
static const char *head_name;
static void *head_name_to_free;
static int sent_capabilities;
@@ -58,7 +60,10 @@ static const char *NONCE_UNSOLICITED = "UNSOLICITED";
static const char *NONCE_BAD = "BAD";
static const char *NONCE_MISSING = "MISSING";
static const char *NONCE_OK = "OK";
+static const char *NONCE_SLOP = "SLOP";
static const char *nonce_status;
+static long nonce_stamp_slop;
+static unsigned long nonce_stamp_slop_limit;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
@@ -145,6 +150,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
if (strcmp(var, "receive.certnonceseed") == 0)
return git_config_string(&cert_nonce_seed, var, value);
+ if (strcmp(var, "receive.certnonceslop") == 0) {
+ nonce_stamp_slop_limit = git_config_ulong(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -359,6 +369,8 @@ static char *find_header(const char *msg, size_t len, const char *key)
static const char *check_nonce(const char *buf, size_t len)
{
char *nonce = find_header(buf, len, "nonce");
+ unsigned long stamp, ostamp;
+ char *bohmac, *expect = NULL;
const char *retval = NONCE_BAD;
if (!nonce) {
@@ -372,11 +384,67 @@ static const char *check_nonce(const char *buf, size_t len)
goto leave;
}
- /* returned nonce MUST match what we gave out earlier */
- retval = NONCE_BAD;
+ if (!stateless_rpc) {
+ /* returned nonce MUST match what we gave out earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * In stateless mode, we may be receiving a nonce issued by
+ * another instance of the server that serving the same
+ * repository, and the timestamps may not match, but the
+ * nonce-seed and dir should match, so we can recompute and
+ * report the time slop.
+ *
+ * In addition, when a nonce issued by another instance has
+ * timestamp within receive.certnonceslop seconds, we pretend
+ * as if we issued that nonce when reporting to the hook.
+ */
+
+ /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */
+ if (*nonce <= '0' || '9' < *nonce) {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+ stamp = strtoul(nonce, &bohmac, 10);
+ if (bohmac == nonce || bohmac[0] != '-') {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ expect = prepare_push_cert_nonce(service_dir, stamp);
+ if (strcmp(expect, nonce)) {
+ /* Not what we would have signed earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * By how many seconds is this nonce stale? Negative value
+ * would mean it was issued by another server with its clock
+ * skewed in the future.
+ */
+ ostamp = strtoul(push_cert_nonce, NULL, 10);
+ nonce_stamp_slop = (long)ostamp - (long)stamp;
+
+ if (nonce_stamp_slop_limit &&
+ abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
+ /*
+ * Pretend as if the received nonce (which passes the
+ * HMAC check, so it is not a forged by third-party)
+ * is what we issued.
+ */
+ free((void *)push_cert_nonce);
+ push_cert_nonce = xstrdup(nonce);
+ retval = NONCE_OK;
+ } else {
+ retval = NONCE_SLOP;
+ }
leave:
free(nonce);
+ free(expect);
return retval;
}
@@ -426,6 +494,9 @@ static void prepare_push_cert_sha1(struct child_process *proc)
if (push_cert_nonce) {
argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce);
argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status);
+ if (nonce_status == NONCE_SLOP)
+ argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld",
+ nonce_stamp_slop);
}
proc->env = env.argv;
}
@@ -1361,9 +1432,7 @@ static int delete_only(struct command *commands)
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int advertise_refs = 0;
- int stateless_rpc = 0;
int i;
- const char *dir = NULL;
struct command *commands;
struct sha1_array shallow = SHA1_ARRAY_INIT;
struct sha1_array ref = SHA1_ARRAY_INIT;
@@ -1396,21 +1465,21 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
usage(receive_pack_usage);
}
- if (dir)
+ if (service_dir)
usage(receive_pack_usage);
- dir = arg;
+ service_dir = arg;
}
- if (!dir)
+ if (!service_dir)
usage(receive_pack_usage);
setup_path();
- if (!enter_repo(dir, 0))
- die("'%s' does not appear to be a git repository", dir);
+ if (!enter_repo(service_dir, 0))
+ die("'%s' does not appear to be a git repository", service_dir);
git_config(receive_pack_config, NULL);
if (cert_nonce_seed)
- push_cert_nonce = prepare_push_cert_nonce(dir, time(NULL));
+ push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
if (0 <= transfer_unpack_limit)
unpack_limit = transfer_unpack_limit;
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 24926a4..ffb3af4 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -340,21 +340,26 @@ test_expect_success GPG 'push with post-receive to inspect certificate' '
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
KEY=${GIT_PUSH_CERT_KEY-nokey}
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+ NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
+ NONCE=${GIT_PUSH_CERT_NONCE-nononce}
E_O_F
EOF
- git config receive.certnonceseed sekrit
+ git config receive.certnonceseed sekrit &&
+ git config receive.certnonceslop 30
) &&
cd "$ROOT_PATH/test_repo_clone" &&
test_commit cert-test &&
git push --signed "$HTTPD_URL/smart/test_repo.git" &&
(
cd "$HTTPD_DOCUMENT_ROOT_PATH" &&
- cat <<-\EOF
+ cat <<-\EOF &&
SIGNER=C O Mitter <committer@example.com>
KEY=13B6F51ECDDE430D
STATUS=G
+ NONCE_STATUS=OK
EOF
+ sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" push-cert
) >expect &&
test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status"
'