Skip to content

Commit edc6dcc

Browse files
bk2204gitster
authored andcommitted
builtin/receive-pack: use constant-time comparison for HMAC value
When we're comparing a push cert nonce, we currently do so using strcmp. Most implementations of strcmp short-circuit and exit as soon as they know whether two values are equal. This, however, is a problem when we're comparing the output of HMAC, as it leaks information in the time taken about how much of the two values match if they do indeed differ. In our case, the nonce is used to prevent replay attacks against our server via the embedded timestamp and replay attacks using requests from a different server via the HMAC. Push certs, which contain the nonces, are signed, so an attacker cannot tamper with the nonces without breaking validation of the signature. They can, of course, create their own signatures with invalid nonces, but they can also create their own signatures with valid nonces, so there's nothing to be gained. Thus, there is no security problem. Even though it doesn't appear that there are any negative consequences from the current technique, for safety and to encourage good practices, let's use a constant time comparison function for nonce verification. POSIX does not provide one, but they are easy to write. The technique we use here is also used in NaCl and the Go standard library and relies on the fact that bitwise or and xor are constant time on all known architectures. We need not be concerned about exiting early if the actual and expected lengths differ, since the standard cryptographic assumption is that everyone, including an attacker, knows the format of and algorithm used in our nonces (and in any event, they have the source code and can determine it easily). As a result, we assume everyone knows how long our nonces should be. This philosophy is also taken by the Go standard library and other cryptographic libraries when performing constant time comparisons on HMAC values. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b6d4d82 commit edc6dcc

File tree

1 file changed

+20
-1
lines changed

1 file changed

+20
-1
lines changed

builtin/receive-pack.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,12 +498,25 @@ static char *find_header(const char *msg, size_t len, const char *key,
498498
return NULL;
499499
}
500500

501+
/*
502+
* Return zero if a and b are equal up to n bytes and nonzero if they are not.
503+
* This operation is guaranteed to run in constant time to avoid leaking data.
504+
*/
505+
static int constant_memequal(const char *a, const char *b, size_t n)
506+
{
507+
int res = 0;
508+
for (size_t i = 0; i < n; i++)
509+
res |= a[i] ^ b[i];
510+
return res;
511+
}
512+
501513
static const char *check_nonce(const char *buf, size_t len)
502514
{
503515
char *nonce = find_header(buf, len, "nonce", NULL);
504516
timestamp_t stamp, ostamp;
505517
char *bohmac, *expect = NULL;
506518
const char *retval = NONCE_BAD;
519+
size_t noncelen;
507520

508521
if (!nonce) {
509522
retval = NONCE_MISSING;
@@ -545,8 +558,14 @@ static const char *check_nonce(const char *buf, size_t len)
545558
goto leave;
546559
}
547560

561+
noncelen = strlen(nonce);
548562
expect = prepare_push_cert_nonce(service_dir, stamp);
549-
if (strcmp(expect, nonce)) {
563+
if (noncelen != strlen(expect)) {
564+
/* This is not even the right size. */
565+
retval = NONCE_BAD;
566+
goto leave;
567+
}
568+
if (constant_memequal(expect, nonce, noncelen)) {
550569
/* Not what we would have signed earlier */
551570
retval = NONCE_BAD;
552571
goto leave;

0 commit comments

Comments
 (0)