#define HS_CACHE_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "lib/crypt_ops/crypto_format.h"
#include "lib/crypt_ops/crypto_util.h"
#include "feature/hs/hs_ident.h"
#include "feature/hs/hs_common.h"
#include "feature/hs/hs_client.h"
#include "feature/hs/hs_descriptor.h"
#include "feature/nodelist/microdesc.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/rend/rendcache.h"
#include "feature/hs/hs_cache.h"
#include "feature/nodelist/networkstatus_st.h"
static int cached_client_descriptor_has_expired(time_t now,
const hs_cache_client_descriptor_t *cached_desc);
static inline bool
entry_has_decrypted_descriptor(const hs_cache_client_descriptor_t *entry)
{
tor_assert(entry);
return (entry->desc != NULL);
}
static digest256map_t *hs_cache_v3_dir;
static void
remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc)
{
tor_assert(desc);
digest256map_remove(hs_cache_v3_dir, desc->key);
}
static void
store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc)
{
tor_assert(desc);
digest256map_set(hs_cache_v3_dir, desc->key, desc);
}
static hs_cache_dir_descriptor_t *
lookup_v3_desc_as_dir(const uint8_t *key)
{
tor_assert(key);
return digest256map_get(hs_cache_v3_dir, key);
}
#define cache_dir_desc_free(val) \
FREE_AND_NULL(hs_cache_dir_descriptor_t, cache_dir_desc_free_, (val))
static void
cache_dir_desc_free_(hs_cache_dir_descriptor_t *desc)
{
if (desc == NULL) {
return;
}
hs_desc_plaintext_data_free(desc->plaintext_data);
tor_free(desc->encoded_desc);
tor_free(desc);
}
static void
cache_dir_desc_free_void(void *ptr)
{
cache_dir_desc_free_(ptr);
}
static hs_cache_dir_descriptor_t *
cache_dir_desc_new(const char *desc)
{
hs_cache_dir_descriptor_t *dir_desc;
tor_assert(desc);
dir_desc = tor_malloc_zero(sizeof(hs_cache_dir_descriptor_t));
dir_desc->plaintext_data =
tor_malloc_zero(sizeof(hs_desc_plaintext_data_t));
dir_desc->encoded_desc = tor_strdup(desc);
if (hs_desc_decode_plaintext(desc, dir_desc->plaintext_data) < 0) {
log_debug(LD_DIR, "Unable to decode descriptor. Rejecting.");
goto err;
}
dir_desc->key = dir_desc->plaintext_data->blinded_pubkey.pubkey;
dir_desc->created_ts = time(NULL);
return dir_desc;
err:
cache_dir_desc_free(dir_desc);
return NULL;
}
static size_t
cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry)
{
return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
+ strlen(entry->encoded_desc));
}
static int
cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
{
hs_cache_dir_descriptor_t *cache_entry;
tor_assert(desc);
cache_entry = lookup_v3_desc_as_dir(desc->key);
if (cache_entry != NULL) {
if (cache_entry->plaintext_data->revision_counter >=
desc->plaintext_data->revision_counter) {
log_info(LD_REND, "Descriptor revision counter in our cache is "
"greater or equal than the one we received (%d/%d). "
"Rejecting!",
(int)cache_entry->plaintext_data->revision_counter,
(int)desc->plaintext_data->revision_counter);
goto err;
}
remove_v3_desc_as_dir(cache_entry);
rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry));
cache_dir_desc_free(cache_entry);
}
store_v3_desc_as_dir(desc);
rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
return 0;
err:
return -1;
}
static int
cache_lookup_v3_as_dir(const char *query, const char **desc_out)
{
int found = 0;
ed25519_public_key_t blinded_key;
const hs_cache_dir_descriptor_t *entry;
tor_assert(query);
if (ed25519_public_from_base64(&blinded_key, query) < 0) {
log_info(LD_REND, "Unable to decode the v3 HSDir query %s.",
safe_str_client(query));
goto err;
}
entry = lookup_v3_desc_as_dir(blinded_key.pubkey);
if (entry != NULL) {
found = 1;
if (desc_out) {
*desc_out = entry->encoded_desc;
}
}
return found;
err:
return -1;
}
STATIC size_t
cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
{
size_t bytes_removed = 0;
tor_assert(global_cutoff >= 0);
if (!hs_cache_v3_dir) {
return 0;
}
DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_dir, key,
hs_cache_dir_descriptor_t *, entry) {
size_t entry_size;
time_t cutoff = global_cutoff;
if (!cutoff) {
cutoff = now - entry->plaintext_data->lifetime_sec;
}
if (entry->created_ts > cutoff) {
continue;
}
MAP_DEL_CURRENT(key);
entry_size = cache_get_dir_entry_size(entry);
bytes_removed += entry_size;
cache_dir_desc_free(entry);
rend_cache_decrement_allocation(entry_size);
{
char key_b64[BASE64_DIGEST256_LEN + 1];
digest256_to_base64(key_b64, (const char *) key);
log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache",
safe_str_client(key_b64));
}
} DIGEST256MAP_FOREACH_END;
return bytes_removed;
}
int
hs_cache_store_as_dir(const char *desc)
{
hs_cache_dir_descriptor_t *dir_desc = NULL;
tor_assert(desc);
dir_desc = cache_dir_desc_new(desc);
if (dir_desc == NULL) {
goto err;
}
switch (dir_desc->plaintext_data->version) {
case HS_VERSION_THREE:
default:
if (cache_store_v3_as_dir(dir_desc) < 0) {
goto err;
}
break;
}
return 0;
err:
cache_dir_desc_free(dir_desc);
return -1;
}
int
hs_cache_lookup_as_dir(uint32_t version, const char *query,
const char **desc_out)
{
int found;
tor_assert(query);
tor_assert(hs_desc_is_supported_version(version));
switch (version) {
case HS_VERSION_THREE:
default:
found = cache_lookup_v3_as_dir(query, desc_out);
break;
}
return found;
}
void
hs_cache_clean_as_dir(time_t now)
{
time_t cutoff;
cutoff = now - rend_cache_max_entry_lifetime();
rend_cache_clean_v2_descs_as_dir(cutoff);
cache_clean_v3_as_dir(now, 0);
}
static digest256map_t *hs_cache_v3_client;
static digest256map_t *hs_cache_client_intro_state;
#define cache_client_desc_free(val) \
FREE_AND_NULL(hs_cache_client_descriptor_t, cache_client_desc_free_, (val))
static void
cache_client_desc_free_(hs_cache_client_descriptor_t *desc)
{
if (desc == NULL) {
return;
}
hs_descriptor_free(desc->desc);
memwipe(&desc->key, 0, sizeof(desc->key));
memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc));
tor_free(desc->encoded_desc);
tor_free(desc);
}
static void
cache_client_desc_free_void(void *ptr)
{
hs_cache_client_descriptor_t *desc = ptr;
cache_client_desc_free(desc);
}
static size_t
cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry)
{
size_t size = 0;
if (entry == NULL) {
goto end;
}
size += sizeof(*entry);
if (entry->encoded_desc) {
size += strlen(entry->encoded_desc);
}
if (entry_has_decrypted_descriptor(entry)) {
size += hs_desc_obj_size(entry->desc);
}
end:
return size;
}
static void
remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc)
{
tor_assert(desc);
digest256map_remove(hs_cache_v3_client, desc->key.pubkey);
rend_cache_decrement_allocation(cache_get_client_entry_size(desc));
}
static void
store_v3_desc_as_client(hs_cache_client_descriptor_t *desc)
{
hs_cache_client_descriptor_t *cached_desc;
tor_assert(desc);
cached_desc = digest256map_get(hs_cache_v3_client, desc->key.pubkey);
if (cached_desc) {
cache_client_desc_free(cached_desc);
}
digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc);
rend_cache_increment_allocation(cache_get_client_entry_size(desc));
}
STATIC hs_cache_client_descriptor_t *
lookup_v3_desc_as_client(const uint8_t *key)
{
time_t now = approx_time();
hs_cache_client_descriptor_t *cached_desc;
tor_assert(key);
cached_desc = digest256map_get(hs_cache_v3_client, key);
if (!cached_desc) {
return NULL;
}
if (cached_client_descriptor_has_expired(now, cached_desc)) {
return NULL;
}
return cached_desc;
}
static hs_cache_client_descriptor_t *
cache_client_desc_new(const char *desc_str,
const ed25519_public_key_t *service_identity_pk,
hs_desc_decode_status_t *decode_status_out)
{
hs_desc_decode_status_t ret;
hs_descriptor_t *desc = NULL;
hs_cache_client_descriptor_t *client_desc = NULL;
tor_assert(desc_str);
tor_assert(service_identity_pk);
ret = hs_client_decode_descriptor(desc_str, service_identity_pk, &desc);
if (ret != HS_DESC_DECODE_OK &&
ret != HS_DESC_DECODE_NEED_CLIENT_AUTH &&
ret != HS_DESC_DECODE_BAD_CLIENT_AUTH) {
goto end;
}
if (ret == HS_DESC_DECODE_OK) {
tor_assert(desc);
} else {
if (BUG(desc != NULL)) {
goto end;
}
}
client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t));
ed25519_pubkey_copy(&client_desc->key, service_identity_pk);
client_desc->expiration_ts = hs_get_start_time_of_next_time_period(0);
client_desc->desc = desc;
client_desc->encoded_desc = tor_strdup(desc_str);
end:
if (decode_status_out) {
*decode_status_out = ret;
}
return client_desc;
}
static hs_cache_intro_state_t *
cache_intro_state_new(void)
{
hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state));
state->created_ts = approx_time();
return state;
}
#define cache_intro_state_free(val) \
FREE_AND_NULL(hs_cache_intro_state_t, cache_intro_state_free_, (val))
static void
cache_intro_state_free_(hs_cache_intro_state_t *state)
{
tor_free(state);
}
static void
cache_intro_state_free_void(void *state)
{
cache_intro_state_free_(state);
}
static hs_cache_client_intro_state_t *
cache_client_intro_state_new(void)
{
hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache));
cache->intro_points = digest256map_new();
return cache;
}
#define cache_client_intro_state_free(val) \
FREE_AND_NULL(hs_cache_client_intro_state_t, \
cache_client_intro_state_free_, (val))
static void
cache_client_intro_state_free_(hs_cache_client_intro_state_t *cache)
{
if (cache == NULL) {
return;
}
digest256map_free(cache->intro_points, cache_intro_state_free_void);
tor_free(cache);
}
static void
cache_client_intro_state_free_void(void *entry)
{
cache_client_intro_state_free_(entry);
}
static int
cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk,
const ed25519_public_key_t *auth_key,
hs_cache_intro_state_t **entry)
{
hs_cache_intro_state_t *state;
hs_cache_client_intro_state_t *cache;
tor_assert(service_pk);
tor_assert(auth_key);
cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
if (cache == NULL) {
goto not_found;
}
state = digest256map_get(cache->intro_points, auth_key->pubkey);
if (state == NULL) {
goto not_found;
}
if (entry) {
*entry = state;
}
return 1;
not_found:
return 0;
}
static void
cache_client_intro_state_note(hs_cache_intro_state_t *state,
rend_intro_point_failure_t failure)
{
tor_assert(state);
switch (failure) {
case INTRO_POINT_FAILURE_GENERIC:
state->error = 1;
break;
case INTRO_POINT_FAILURE_TIMEOUT:
state->timed_out = 1;
break;
case INTRO_POINT_FAILURE_UNREACHABLE:
state->unreachable_count++;
break;
default:
tor_assert_nonfatal_unreached();
return;
}
}
static void
cache_client_intro_state_add(const ed25519_public_key_t *service_pk,
const ed25519_public_key_t *auth_key,
hs_cache_intro_state_t **state)
{
hs_cache_intro_state_t *entry, *old_entry;
hs_cache_client_intro_state_t *cache;
tor_assert(service_pk);
tor_assert(auth_key);
cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
if (cache == NULL) {
cache = cache_client_intro_state_new();
digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache);
}
entry = cache_intro_state_new();
old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry);
tor_assert_nonfatal(old_entry == NULL);
tor_free(old_entry);
if (state) {
*state = entry;
}
}
static void
cache_client_intro_state_clean(time_t cutoff,
hs_cache_client_intro_state_t *cache)
{
tor_assert(cache);
DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key,
hs_cache_intro_state_t *, entry) {
if (entry->created_ts <= cutoff) {
cache_intro_state_free(entry);
MAP_DEL_CURRENT(key);
}
} DIGEST256MAP_FOREACH_END;
}
static int
cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache)
{
return digest256map_isempty(cache->intro_points);
}
static int
cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
{
hs_cache_client_descriptor_t *cache_entry;
tor_assert(client_desc);
cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
if (cache_entry != NULL) {
if (!entry_has_decrypted_descriptor(cache_entry) ||
!entry_has_decrypted_descriptor(client_desc)) {
remove_v3_desc_as_client(cache_entry);
cache_client_desc_free(cache_entry);
goto store;
}
if (cache_entry->desc->plaintext_data.revision_counter >
client_desc->desc->plaintext_data.revision_counter) {
cache_client_desc_free(client_desc);
goto done;
}
remove_v3_desc_as_client(cache_entry);
hs_client_close_intro_circuits_from_desc(cache_entry->desc);
cache_client_desc_free(cache_entry);
}
store:
store_v3_desc_as_client(client_desc);
done:
return 0;
}
static int
cached_client_descriptor_has_expired(time_t now,
const hs_cache_client_descriptor_t *cached_desc)
{
const networkstatus_t *ns =
networkstatus_get_reasonably_live_consensus(now,
usable_consensus_flavor());
if (!ns) {
return 1;
}
if (cached_desc->expiration_ts <= ns->valid_after) {
return 1;
}
return 0;
}
static size_t
cache_clean_v3_as_client(time_t now)
{
size_t bytes_removed = 0;
if (!hs_cache_v3_client) {
return 0;
}
DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
hs_cache_client_descriptor_t *, entry) {
size_t entry_size;
if (!cached_client_descriptor_has_expired(now, entry)) {
continue;
}
MAP_DEL_CURRENT(key);
entry_size = cache_get_client_entry_size(entry);
bytes_removed += entry_size;
if (entry_has_decrypted_descriptor(entry)) {
hs_client_close_intro_circuits_from_desc(entry->desc);
}
cache_client_desc_free(entry);
rend_cache_decrement_allocation(entry_size);
{
char key_b64[BASE64_DIGEST256_LEN + 1];
digest256_to_base64(key_b64, (const char *) key);
log_info(LD_REND, "Removing hidden service v3 descriptor '%s' "
"from client cache",
safe_str_client(key_b64));
}
} DIGEST256MAP_FOREACH_END;
return bytes_removed;
}
const char *
hs_cache_lookup_encoded_as_client(const ed25519_public_key_t *key)
{
hs_cache_client_descriptor_t *cached_desc = NULL;
tor_assert(key);
cached_desc = lookup_v3_desc_as_client(key->pubkey);
if (cached_desc) {
tor_assert(cached_desc->encoded_desc);
return cached_desc->encoded_desc;
}
return NULL;
}
const hs_descriptor_t *
hs_cache_lookup_as_client(const ed25519_public_key_t *key)
{
hs_cache_client_descriptor_t *cached_desc = NULL;
tor_assert(key);
cached_desc = lookup_v3_desc_as_client(key->pubkey);
if (cached_desc && entry_has_decrypted_descriptor(cached_desc)) {
return cached_desc->desc;
}
return NULL;
}
hs_desc_decode_status_t
hs_cache_store_as_client(const char *desc_str,
const ed25519_public_key_t *identity_pk)
{
hs_desc_decode_status_t ret;
hs_cache_client_descriptor_t *client_desc = NULL;
tor_assert(desc_str);
tor_assert(identity_pk);
client_desc = cache_client_desc_new(desc_str, identity_pk, &ret);
if (!client_desc) {
log_warn(LD_GENERAL, "HSDesc parsing failed!");
log_debug(LD_GENERAL, "Failed to parse HSDesc: %s.", escaped(desc_str));
goto err;
}
if (cache_store_as_client(client_desc) < 0) {
ret = HS_DESC_DECODE_GENERIC_ERROR;
goto err;
}
return ret;
err:
cache_client_desc_free(client_desc);
return ret;
}
void
hs_cache_remove_as_client(const ed25519_public_key_t *key)
{
hs_cache_client_descriptor_t *cached_desc = NULL;
tor_assert(key);
cached_desc = lookup_v3_desc_as_client(key->pubkey);
if (!cached_desc) {
return;
}
if (entry_has_decrypted_descriptor(cached_desc)) {
hs_client_close_intro_circuits_from_desc(cached_desc->desc);
}
remove_v3_desc_as_client(cached_desc);
cache_client_desc_free(cached_desc);
{
char key_b64[BASE64_DIGEST256_LEN + 1];
digest256_to_base64(key_b64, (const char *) key);
log_info(LD_REND, "Onion service v3 descriptor '%s' removed "
"from client cache",
safe_str_client(key_b64));
}
}
void
hs_cache_clean_as_client(time_t now)
{
rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
cache_clean_v3_as_client(now);
}
void
hs_cache_purge_as_client(void)
{
DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
hs_cache_client_descriptor_t *, entry) {
size_t entry_size = cache_get_client_entry_size(entry);
MAP_DEL_CURRENT(key);
cache_client_desc_free(entry);
rend_cache_decrement_allocation(entry_size);
} DIGEST256MAP_FOREACH_END;
log_info(LD_REND, "Hidden service client descriptor cache purged.");
}
void
hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk,
const ed25519_public_key_t *auth_key,
rend_intro_point_failure_t failure)
{
int found;
hs_cache_intro_state_t *entry;
tor_assert(service_pk);
tor_assert(auth_key);
found = cache_client_intro_state_lookup(service_pk, auth_key, &entry);
if (!found) {
cache_client_intro_state_add(service_pk, auth_key, &entry);
}
cache_client_intro_state_note(entry, failure);
}
const hs_cache_intro_state_t *
hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk,
const ed25519_public_key_t *auth_key)
{
hs_cache_intro_state_t *state = NULL;
cache_client_intro_state_lookup(service_pk, auth_key, &state);
return state;
}
void
hs_cache_client_intro_state_clean(time_t now)
{
time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE;
DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key,
hs_cache_client_intro_state_t *, cache) {
cache_client_intro_state_clean(cutoff, cache);
if (cache_client_intro_state_is_empty(cache)) {
cache_client_intro_state_free(cache);
MAP_DEL_CURRENT(key);
}
} DIGEST256MAP_FOREACH_END;
}
void
hs_cache_client_intro_state_purge(void)
{
DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key,
hs_cache_client_intro_state_t *, cache) {
MAP_DEL_CURRENT(key);
cache_client_intro_state_free(cache);
} DIGEST256MAP_FOREACH_END;
log_info(LD_REND, "Hidden service client introduction point state "
"cache purged.");
}
bool
hs_cache_client_new_auth_parse(const ed25519_public_key_t *service_pk)
{
bool ret = false;
hs_cache_client_descriptor_t *cached_desc = NULL;
tor_assert(service_pk);
if (!hs_cache_v3_client) {
return false;
}
cached_desc = lookup_v3_desc_as_client(service_pk->pubkey);
if (cached_desc == NULL || entry_has_decrypted_descriptor(cached_desc)) {
goto end;
}
if (hs_client_decode_descriptor(cached_desc->encoded_desc, service_pk,
&cached_desc->desc) == HS_DESC_DECODE_OK) {
ret = true;
}
end:
return ret;
}
size_t
hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
{
time_t k;
size_t bytes_removed = 0;
tor_assert(min_remove_bytes != 0);
k = rend_cache_max_entry_lifetime();
do {
time_t cutoff;
if (k < 0) {
break;
}
cutoff = now - k;
bytes_removed += rend_cache_clean_v2_descs_as_dir(cutoff);
if (bytes_removed < min_remove_bytes) {
bytes_removed += cache_clean_v3_as_dir(now, cutoff);
k -= get_options()->RendPostPeriod;
}
} while (bytes_removed < min_remove_bytes);
return bytes_removed;
}
unsigned int
hs_cache_get_max_descriptor_size(void)
{
return (unsigned) networkstatus_get_param(NULL,
"HSV3MaxDescriptorSize",
HS_DESC_MAX_LEN, 1, INT32_MAX);
}
void
hs_cache_init(void)
{
tor_assert(!hs_cache_v3_dir);
hs_cache_v3_dir = digest256map_new();
tor_assert(!hs_cache_v3_client);
hs_cache_v3_client = digest256map_new();
tor_assert(!hs_cache_client_intro_state);
hs_cache_client_intro_state = digest256map_new();
}
void
hs_cache_free_all(void)
{
digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_void);
hs_cache_v3_dir = NULL;
digest256map_free(hs_cache_v3_client, cache_client_desc_free_void);
hs_cache_v3_client = NULL;
digest256map_free(hs_cache_client_intro_state,
cache_client_intro_state_free_void);
hs_cache_client_intro_state = NULL;
}