In order to prevent a valid push certificate for pushing into an
repository from getting replayed in a different push operation, send
a nonce string from the receive-pack process and have the signer
include it in the push certificate. The receiving end uses an HMAC
hash of the path to the repository it serves and the current time
stamp, hashed with a secret seed (the secret seed does not have to
be per-repository but can be defined in /etc/gitconfig) to generate
the nonce, in order to ensure that a random third party cannot forge
a nonce that looks like it originated from it.
The original nonce is exported as GIT_PUSH_CERT_NONCE for the hooks
to examine and match against the value on the "nonce" header in the
certificate to notice a replay, but returned "nonce" header in the
push certificate is examined by receive-pack and the result is
exported as GIT_PUSH_CERT_NONCE_STATUS, whose value would be "OK"
if the nonce recorded in the certificate matches what we expect, so
that the hooks can more easily check.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
@ -251,10 +251,11 @@ If the upload-pack server advertises this capability, fetch-pack may
@@ -251,10 +251,11 @@ If the upload-pack server advertises this capability, fetch-pack may
send "want" lines with SHA-1s that exist at the server but are not
advertised by upload-pack.
push-cert
---------
push-cert=<nonce>
-----------------
The receive-pack server that advertises this capability is willing
to accept a signed push certificate. A send-pack client MUST NOT
to accept a signed push certificate, and asks the <nonce> to be
included in the push certificate. A send-pack client MUST NOT
send a push-cert packet unless the receive-pack server advertises
@ -271,6 +276,110 @@ static int copy_to_sideband(int in, int out, void *arg)
@@ -271,6 +276,110 @@ static int copy_to_sideband(int in, int out, void *arg)
return 0;
}
#define HMAC_BLOCK_SIZE 64
static void hmac_sha1(unsigned char out[20],
const char *key_in, size_t key_len,
const char *text, size_t text_len)
{
unsigned char key[HMAC_BLOCK_SIZE];
unsigned char k_ipad[HMAC_BLOCK_SIZE];
unsigned char k_opad[HMAC_BLOCK_SIZE];
int i;
git_SHA_CTX ctx;
/* RFC 2104 2. (1) */
memset(key, '\0', HMAC_BLOCK_SIZE);
if (HMAC_BLOCK_SIZE < key_len) {
git_SHA1_Init(&ctx);
git_SHA1_Update(&ctx, key_in, key_len);
git_SHA1_Final(key, &ctx);
} else {
memcpy(key, key_in, key_len);
}
/* RFC 2104 2. (2) & (5) */
for (i = 0; i < sizeof(key); i++) {
k_ipad[i] = key[i] ^ 0x36;
k_opad[i] = key[i] ^ 0x5c;
}
/* RFC 2104 2. (3) & (4) */
git_SHA1_Init(&ctx);
git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));
git_SHA1_Update(&ctx, text, text_len);
git_SHA1_Final(out, &ctx);
/* RFC 2104 2. (6) & (7) */
git_SHA1_Init(&ctx);
git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
git_SHA1_Update(&ctx, out, sizeof(out));
git_SHA1_Final(out, &ctx);
}
static char *prepare_push_cert_nonce(const char *path, unsigned long stamp)
@ -68,7 +67,6 @@ test_expect_success 'talking with a receiver without push certificate support' '
@@ -68,7 +67,6 @@ test_expect_success 'talking with a receiver without push certificate support' '
test_expect_success 'push --signed fails with a receiver without push certificate support' '
test_i18ngrep "the receiving end does not support" err
'
@ -89,6 +87,7 @@ test_expect_success GPG 'no certificate for a signed push with no update' '
@@ -89,6 +87,7 @@ test_expect_success GPG 'no certificate for a signed push with no update' '