2017-03-15 21:13:17 +01:00
|
|
|
/* Copyright (c) 2012-2017, The Tor Project, Inc. */
|
2012-07-05 23:01:42 +02:00
|
|
|
/* See LICENSE for licensing information */
|
|
|
|
|
2016-10-18 18:31:50 +02:00
|
|
|
/**
|
2012-07-05 23:01:42 +02:00
|
|
|
* \file replaycache.c
|
|
|
|
*
|
|
|
|
* \brief Self-scrubbing replay cache for rendservice.c
|
2016-10-15 02:08:51 +02:00
|
|
|
*
|
|
|
|
* To prevent replay attacks, hidden services need to recognize INTRODUCE2
|
|
|
|
* cells that they've already seen, and drop them. If they didn't, then
|
|
|
|
* sending the same INTRODUCE2 cell over and over would force the hidden
|
|
|
|
* service to make a huge number of circuits to the same rendezvous
|
|
|
|
* point, aiding traffic analysis.
|
|
|
|
*
|
|
|
|
* (It's not that simple, actually. We only check for replays in the
|
|
|
|
* RSA-encrypted portion of the handshake, since the rest of the handshake is
|
|
|
|
* malleable.)
|
|
|
|
*
|
|
|
|
* This module is used from rendservice.c.
|
2012-07-05 23:01:42 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define REPLAYCACHE_PRIVATE
|
|
|
|
|
|
|
|
#include "or.h"
|
|
|
|
#include "replaycache.h"
|
|
|
|
|
|
|
|
/** Free the replaycache r and all of its entries.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
replaycache_free(replaycache_t *r)
|
|
|
|
{
|
|
|
|
if (!r) {
|
|
|
|
log_info(LD_BUG, "replaycache_free() called on NULL");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-23 23:08:53 +01:00
|
|
|
if (r->digests_seen) digest256map_free(r->digests_seen, tor_free_);
|
2012-07-05 23:01:42 +02:00
|
|
|
|
|
|
|
tor_free(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Allocate a new, empty replay detection cache, where horizon is the time
|
|
|
|
* for entries to age out and interval is the time after which the cache
|
|
|
|
* should be scrubbed for old entries.
|
|
|
|
*/
|
|
|
|
|
|
|
|
replaycache_t *
|
|
|
|
replaycache_new(time_t horizon, time_t interval)
|
|
|
|
{
|
|
|
|
replaycache_t *r = NULL;
|
|
|
|
|
|
|
|
if (horizon < 0) {
|
|
|
|
log_info(LD_BUG, "replaycache_new() called with negative"
|
|
|
|
" horizon parameter");
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (interval < 0) {
|
|
|
|
log_info(LD_BUG, "replaycache_new() called with negative interval"
|
|
|
|
" parameter");
|
|
|
|
interval = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = tor_malloc(sizeof(*r));
|
|
|
|
r->scrub_interval = interval;
|
|
|
|
r->scrubbed = 0;
|
|
|
|
r->horizon = horizon;
|
2015-11-23 23:08:53 +01:00
|
|
|
r->digests_seen = digest256map_new();
|
2012-07-05 23:01:42 +02:00
|
|
|
|
|
|
|
err:
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** See documentation for replaycache_add_and_test()
|
|
|
|
*/
|
|
|
|
|
2013-06-06 23:58:28 +02:00
|
|
|
STATIC int
|
2012-07-05 23:01:42 +02:00
|
|
|
replaycache_add_and_test_internal(
|
2013-08-04 07:36:32 +02:00
|
|
|
time_t present, replaycache_t *r, const void *data, size_t len,
|
2012-07-05 23:01:42 +02:00
|
|
|
time_t *elapsed)
|
|
|
|
{
|
|
|
|
int rv = 0;
|
2015-11-23 23:08:53 +01:00
|
|
|
uint8_t digest[DIGEST256_LEN];
|
2012-07-05 23:01:42 +02:00
|
|
|
time_t *access_time;
|
|
|
|
|
|
|
|
/* sanity check */
|
2013-08-04 07:36:32 +02:00
|
|
|
if (present <= 0 || !r || !data || len == 0) {
|
2012-07-05 23:01:42 +02:00
|
|
|
log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
|
|
|
|
" parameters; please fix this.");
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* compute digest */
|
2015-11-23 23:08:53 +01:00
|
|
|
crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256);
|
2012-07-05 23:01:42 +02:00
|
|
|
|
|
|
|
/* check map */
|
2015-11-23 23:08:53 +01:00
|
|
|
access_time = digest256map_get(r->digests_seen, digest);
|
2012-07-05 23:01:42 +02:00
|
|
|
|
|
|
|
/* seen before? */
|
|
|
|
if (access_time != NULL) {
|
|
|
|
/*
|
|
|
|
* If it's far enough in the past, no hit. If the horizon is zero, we
|
|
|
|
* never expire.
|
|
|
|
*/
|
|
|
|
if (*access_time >= present - r->horizon || r->horizon == 0) {
|
|
|
|
/* replay cache hit, return 1 */
|
|
|
|
rv = 1;
|
|
|
|
/* If we want to output an elapsed time, do so */
|
|
|
|
if (elapsed) {
|
|
|
|
if (present >= *access_time) {
|
|
|
|
*elapsed = present - *access_time;
|
|
|
|
} else {
|
|
|
|
/* We shouldn't really be seeing hits from the future, but... */
|
|
|
|
*elapsed = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If it's ahead of the cached time, update
|
|
|
|
*/
|
|
|
|
if (*access_time < present) {
|
|
|
|
*access_time = present;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* No, so no hit and update the digest map with the current time */
|
|
|
|
access_time = tor_malloc(sizeof(*access_time));
|
|
|
|
*access_time = present;
|
2015-11-23 23:08:53 +01:00
|
|
|
digest256map_set(r->digests_seen, digest, access_time);
|
2012-07-05 23:01:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* now scrub the cache if it's time */
|
|
|
|
replaycache_scrub_if_needed_internal(present, r);
|
|
|
|
|
|
|
|
done:
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** See documentation for replaycache_scrub_if_needed()
|
|
|
|
*/
|
|
|
|
|
2013-06-06 23:58:28 +02:00
|
|
|
STATIC void
|
2012-07-05 23:01:42 +02:00
|
|
|
replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
|
|
|
|
{
|
2015-11-23 23:08:53 +01:00
|
|
|
digest256map_iter_t *itr = NULL;
|
|
|
|
const uint8_t *digest;
|
2012-07-05 23:01:42 +02:00
|
|
|
void *valp;
|
|
|
|
time_t *access_time;
|
|
|
|
|
|
|
|
/* sanity check */
|
|
|
|
if (!r || !(r->digests_seen)) {
|
|
|
|
log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with"
|
|
|
|
" stupid parameters; please fix this.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */
|
|
|
|
if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return;
|
|
|
|
|
|
|
|
/* if we're never expiring, don't bother scrubbing */
|
|
|
|
if (r->horizon == 0) return;
|
|
|
|
|
|
|
|
/* okay, scrub time */
|
2015-11-23 23:08:53 +01:00
|
|
|
itr = digest256map_iter_init(r->digests_seen);
|
|
|
|
while (!digest256map_iter_done(itr)) {
|
|
|
|
digest256map_iter_get(itr, &digest, &valp);
|
2012-07-05 23:01:42 +02:00
|
|
|
access_time = (time_t *)valp;
|
2013-07-16 15:01:50 +02:00
|
|
|
/* aged out yet? */
|
|
|
|
if (*access_time < present - r->horizon) {
|
2012-07-05 23:01:42 +02:00
|
|
|
/* Advance the iterator and remove this one */
|
2015-11-23 23:08:53 +01:00
|
|
|
itr = digest256map_iter_next_rmv(r->digests_seen, itr);
|
2012-07-05 23:01:42 +02:00
|
|
|
/* Free the value removed */
|
|
|
|
tor_free(access_time);
|
|
|
|
} else {
|
|
|
|
/* Just advance the iterator */
|
2015-11-23 23:08:53 +01:00
|
|
|
itr = digest256map_iter_next(r->digests_seen, itr);
|
2012-07-05 23:01:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update scrubbed timestamp */
|
|
|
|
if (present > r->scrubbed) r->scrubbed = present;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Test the buffer of length len point to by data against the replay cache r;
|
|
|
|
* the digest of the buffer will be added to the cache at the current time,
|
|
|
|
* and the function will return 1 if it was already seen within the cache's
|
|
|
|
* horizon, or 0 otherwise.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
2013-08-04 07:36:32 +02:00
|
|
|
replaycache_add_and_test(replaycache_t *r, const void *data, size_t len)
|
2012-07-05 23:01:42 +02:00
|
|
|
{
|
|
|
|
return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Like replaycache_add_and_test(), but if it's a hit also return the time
|
|
|
|
* elapsed since this digest was last seen.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
replaycache_add_test_and_elapsed(
|
2013-08-04 07:36:32 +02:00
|
|
|
replaycache_t *r, const void *data, size_t len, time_t *elapsed)
|
2012-07-05 23:01:42 +02:00
|
|
|
{
|
|
|
|
return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Scrub aged entries out of r if sufficiently long has elapsed since r was
|
|
|
|
* last scrubbed.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
replaycache_scrub_if_needed(replaycache_t *r)
|
|
|
|
{
|
|
|
|
replaycache_scrub_if_needed_internal(time(NULL), r);
|
|
|
|
}
|
|
|
|
|