aboutsummaryrefslogtreecommitdiff
path: root/src/feature/hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/feature/hs')
-rw-r--r--src/feature/hs/hs_cache.c979
-rw-r--r--src/feature/hs/hs_cache.h130
-rw-r--r--src/feature/hs/hs_cell.c950
-rw-r--r--src/feature/hs/hs_cell.h122
-rw-r--r--src/feature/hs/hs_circuit.c1249
-rw-r--r--src/feature/hs/hs_circuit.h75
-rw-r--r--src/feature/hs/hs_circuitmap.c585
-rw-r--r--src/feature/hs/hs_circuitmap.h112
-rw-r--r--src/feature/hs/hs_client.c1623
-rw-r--r--src/feature/hs/hs_client.h92
-rw-r--r--src/feature/hs/hs_common.c1825
-rw-r--r--src/feature/hs/hs_common.h288
-rw-r--r--src/feature/hs/hs_config.c591
-rw-r--r--src/feature/hs/hs_config.h24
-rw-r--r--src/feature/hs/hs_control.c261
-rw-r--r--src/feature/hs/hs_control.h52
-rw-r--r--src/feature/hs/hs_descriptor.c2611
-rw-r--r--src/feature/hs/hs_descriptor.h280
-rw-r--r--src/feature/hs/hs_ident.c127
-rw-r--r--src/feature/hs/hs_ident.h146
-rw-r--r--src/feature/hs/hs_intropoint.c614
-rw-r--r--src/feature/hs/hs_intropoint.h79
-rw-r--r--src/feature/hs/hs_service.c3644
-rw-r--r--src/feature/hs/hs_service.h378
-rw-r--r--src/feature/hs/hs_stats.c58
-rw-r--r--src/feature/hs/hs_stats.h14
-rw-r--r--src/feature/hs/hsdir_index_st.h24
27 files changed, 16933 insertions, 0 deletions
diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c
new file mode 100644
index 0000000000..3772e0c0ed
--- /dev/null
+++ b/src/feature/hs/hs_cache.c
@@ -0,0 +1,979 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.c
+ * \brief Handle hidden service descriptor caches.
+ **/
+
+/* For unit tests.*/
+#define HS_CACHE_PRIVATE
+
+#include "or/or.h"
+#include "or/config.h"
+#include "lib/crypt_ops/crypto_format.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/hs_ident.h"
+#include "or/hs_common.h"
+#include "or/hs_client.h"
+#include "or/hs_descriptor.h"
+#include "or/networkstatus.h"
+#include "or/rendcache.h"
+
+#include "or/hs_cache.h"
+
+#include "or/networkstatus_st.h"
+
+static int cached_client_descriptor_has_expired(time_t now,
+ const hs_cache_client_descriptor_t *cached_desc);
+
+/********************** Directory HS cache ******************/
+
+/* Directory descriptor cache. Map indexed by blinded key. */
+static digest256map_t *hs_cache_v3_dir;
+
+/* Remove a given descriptor from our cache. */
+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);
+}
+
+/* Store a given descriptor in our cache. */
+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);
+}
+
+/* Query our cache and return the entry or NULL if not found. */
+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))
+
+/* Free a directory descriptor object. */
+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);
+}
+
+/* Helper function: Use by the free all function using the digest256map
+ * interface to cache entries. */
+static void
+cache_dir_desc_free_void(void *ptr)
+{
+ cache_dir_desc_free_(ptr);
+}
+
+/* Create a new directory cache descriptor object from a encoded descriptor.
+ * On success, return the heap-allocated cache object, otherwise return NULL if
+ * we can't decode the descriptor. */
+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;
+ }
+
+ /* The blinded pubkey is the indexed key. */
+ 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;
+}
+
+/* Return the size of a cache entry in bytes. */
+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));
+}
+
+/* Try to store a valid version 3 descriptor in the directory cache. Return 0
+ * on success else a negative value is returned indicating that we have a
+ * newer version in our cache. On error, caller is responsible to free the
+ * given descriptor desc. */
+static int
+cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
+{
+ hs_cache_dir_descriptor_t *cache_entry;
+
+ tor_assert(desc);
+
+ /* Verify if we have an entry in the cache for that key and if yes, check
+ * if we should replace it? */
+ cache_entry = lookup_v3_desc_as_dir(desc->key);
+ if (cache_entry != NULL) {
+ /* Only replace descriptor if revision-counter is greater than the one
+ * in our cache */
+ 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;
+ }
+ /* We now know that the descriptor we just received is a new one so
+ * remove the entry we currently have from our cache so we can then
+ * store the new one. */
+ 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 the descriptor we just got. We are sure here that either we
+ * don't have the entry or we have a newer descriptor and the old one
+ * has been removed from the cache. */
+ store_v3_desc_as_dir(desc);
+
+ /* Update our total cache size with this entry for the OOM. This uses the
+ * old HS protocol cache subsystem for which we are tied with. */
+ rend_cache_increment_allocation(cache_get_dir_entry_size(desc));
+
+ /* XXX: Update HS statistics. We should have specific stats for v3. */
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Using the query which is the base64 encoded blinded key of a version 3
+ * descriptor, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being the
+ * encoded descriptor. If not found, 0 is returned and desc_out is untouched.
+ * On error, a negative value is returned and desc_out is untouched. */
+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);
+
+ /* Decode blinded key using the given query value. */
+ 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;
+}
+
+/* Clean the v3 cache by removing any entry that has expired using the
+ * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning
+ * process will use the lifetime found in the plaintext data section. Return
+ * the number of bytes cleaned. */
+STATIC size_t
+cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
+{
+ size_t bytes_removed = 0;
+
+ /* Code flow error if this ever happens. */
+ tor_assert(global_cutoff >= 0);
+
+ if (!hs_cache_v3_dir) { /* No cache to clean. Just return. */
+ 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 is the lifetime of the entry found in the descriptor. */
+ cutoff = now - entry->plaintext_data->lifetime_sec;
+ }
+
+ /* If the entry has been created _after_ the cutoff, not expired so
+ * continue to the next entry in our v3 cache. */
+ if (entry->created_ts > cutoff) {
+ continue;
+ }
+ /* Here, our entry has expired, remove and free. */
+ MAP_DEL_CURRENT(key);
+ entry_size = cache_get_dir_entry_size(entry);
+ bytes_removed += entry_size;
+ /* Entry is not in the cache anymore, destroy it. */
+ cache_dir_desc_free(entry);
+ /* Update our cache entry allocation size for the OOM. */
+ rend_cache_decrement_allocation(entry_size);
+ /* Logging. */
+ {
+ 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;
+}
+
+/* Given an encoded descriptor, store it in the directory cache depending on
+ * which version it is. Return a negative value on error. On success, 0 is
+ * returned. */
+int
+hs_cache_store_as_dir(const char *desc)
+{
+ hs_cache_dir_descriptor_t *dir_desc = NULL;
+
+ tor_assert(desc);
+
+ /* Create a new cache object. This can fail if the descriptor plaintext data
+ * is unparseable which in this case a log message will be triggered. */
+ dir_desc = cache_dir_desc_new(desc);
+ if (dir_desc == NULL) {
+ goto err;
+ }
+
+ /* Call the right function against the descriptor version. At this point,
+ * we are sure that the descriptor's version is supported else the
+ * decoding would have failed. */
+ 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;
+}
+
+/* Using the query, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being
+ * the encoded descriptor. If not found, 0 is returned and desc_out is
+ * untouched. On error, a negative value is returned and desc_out is
+ * untouched. */
+int
+hs_cache_lookup_as_dir(uint32_t version, const char *query,
+ const char **desc_out)
+{
+ int found;
+
+ tor_assert(query);
+ /* This should never be called with an unsupported version. */
+ 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;
+}
+
+/* Clean all directory caches using the current time now. */
+void
+hs_cache_clean_as_dir(time_t now)
+{
+ time_t cutoff;
+
+ /* Start with v2 cache cleaning. */
+ cutoff = now - rend_cache_max_entry_lifetime();
+ rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
+ * to compute the cutoff by itself using the lifetime value. */
+ cache_clean_v3_as_dir(now, 0);
+}
+
+/********************** Client-side HS cache ******************/
+
+/* Client-side HS descriptor cache. Map indexed by service identity key. */
+static digest256map_t *hs_cache_v3_client;
+
+/* Client-side introduction point state cache. Map indexed by service public
+ * identity key (onion address). It contains hs_cache_client_intro_state_t
+ * objects all related to a specific service. */
+static digest256map_t *hs_cache_client_intro_state;
+
+/* Return the size of a client cache entry in bytes. */
+static size_t
+cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry)
+{
+ return sizeof(*entry) +
+ strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc);
+}
+
+/* Remove a given descriptor from our cache. */
+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);
+ /* Update cache size with this entry for the OOM handler. */
+ rend_cache_decrement_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Store a given descriptor in our cache. */
+static void
+store_v3_desc_as_client(hs_cache_client_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc);
+ /* Update cache size with this entry for the OOM handler. */
+ rend_cache_increment_allocation(cache_get_client_entry_size(desc));
+}
+
+/* Query our cache and return the entry or NULL if not found or if expired. */
+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);
+
+ /* Do the lookup */
+ cached_desc = digest256map_get(hs_cache_v3_client, key);
+ if (!cached_desc) {
+ return NULL;
+ }
+
+ /* Don't return expired entries */
+ if (cached_client_descriptor_has_expired(now, cached_desc)) {
+ return NULL;
+ }
+
+ return cached_desc;
+}
+
+/* Parse the encoded descriptor in <b>desc_str</b> using
+ * <b>service_identity_pk<b> to decrypt it first.
+ *
+ * If everything goes well, allocate and return a new
+ * hs_cache_client_descriptor_t object. In case of error, return NULL. */
+static hs_cache_client_descriptor_t *
+cache_client_desc_new(const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk)
+{
+ hs_descriptor_t *desc = NULL;
+ hs_cache_client_descriptor_t *client_desc = NULL;
+
+ tor_assert(desc_str);
+ tor_assert(service_identity_pk);
+
+ /* Decode the descriptor we just fetched. */
+ if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) {
+ goto end;
+ }
+ tor_assert(desc);
+
+ /* All is good: make a cache object for this descriptor */
+ client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t));
+ ed25519_pubkey_copy(&client_desc->key, service_identity_pk);
+ /* Set expiration time for this cached descriptor to be the start of the next
+ * time period since that's when clients need to start using the next blinded
+ * pk of the service (and hence will need its next descriptor). */
+ 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:
+ return client_desc;
+}
+
+#define cache_client_desc_free(val) \
+ FREE_AND_NULL(hs_cache_client_descriptor_t, cache_client_desc_free_, (val))
+
+/** Free memory allocated by <b>desc</b>. */
+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);
+}
+
+/** Helper function: Use by the free all function to clear the client cache */
+static void
+cache_client_desc_free_void(void *ptr)
+{
+ hs_cache_client_descriptor_t *desc = ptr;
+ cache_client_desc_free(desc);
+}
+
+/* Return a newly allocated and initialized hs_cache_intro_state_t object. */
+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))
+
+/* Free an hs_cache_intro_state_t object. */
+static void
+cache_intro_state_free_(hs_cache_intro_state_t *state)
+{
+ tor_free(state);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_intro_state_free_void(void *state)
+{
+ cache_intro_state_free_(state);
+}
+
+/* Return a newly allocated and initialized hs_cache_client_intro_state_t
+ * object. */
+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))
+
+/* Free a cache client intro state object. */
+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);
+}
+
+/* Helper function: use by the free all function. */
+static void
+cache_client_intro_state_free_void(void *entry)
+{
+ cache_client_intro_state_free_(entry);
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, lookup the intro state object. Return 1 if
+ * found and put it in entry if not NULL. Return 0 if not found and entry is
+ * untouched. */
+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);
+
+ /* Lookup the intro state cache for this service key. */
+ cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey);
+ if (cache == NULL) {
+ goto not_found;
+ }
+
+ /* From the cache we just found for the service, lookup in the introduction
+ * points map for the given authentication key. */
+ 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;
+}
+
+/* Note the given failure in state. */
+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;
+ }
+}
+
+/* For the given service identity key service_pk and an introduction
+ * authentication key auth_key, add an entry in the client intro state cache
+ * If no entry exists for the service, it will create one. If state is non
+ * NULL, it will point to the new intro state entry. */
+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);
+
+ /* Lookup the state cache for this service 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);
+ /* This should never happened because the code flow is to lookup the entry
+ * before adding it. But, just in case, non fatal assert and free it. */
+ tor_assert_nonfatal(old_entry == NULL);
+ tor_free(old_entry);
+
+ if (state) {
+ *state = entry;
+ }
+}
+
+/* Remove every intro point state entry from cache that has been created
+ * before or at the cutoff. */
+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;
+}
+
+/* Return true iff no intro points are in this cache. */
+static int
+cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache)
+{
+ return digest256map_isempty(cache->intro_points);
+}
+
+/** Check whether <b>client_desc</b> is useful for us, and store it in the
+ * client-side HS cache if so. The client_desc is freed if we already have a
+ * fresher (higher revision counter count) in the cache. */
+static int
+cache_store_as_client(hs_cache_client_descriptor_t *client_desc)
+{
+ hs_cache_client_descriptor_t *cache_entry;
+
+ /* TODO: Heavy code duplication with cache_store_as_dir(). Consider
+ * refactoring and uniting! */
+
+ tor_assert(client_desc);
+
+ /* Check if we already have a descriptor from this HS in cache. If we do,
+ * check if this descriptor is newer than the cached one */
+ cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey);
+ if (cache_entry != NULL) {
+ /* If we have an entry in our cache that has a revision counter greater
+ * than the one we just fetched, discard the one we fetched. */
+ if (cache_entry->desc->plaintext_data.revision_counter >
+ client_desc->desc->plaintext_data.revision_counter) {
+ cache_client_desc_free(client_desc);
+ goto done;
+ }
+ /* Remove old entry. Make space for the new one! */
+ remove_v3_desc_as_client(cache_entry);
+ cache_client_desc_free(cache_entry);
+ }
+
+ /* Store descriptor in cache */
+ store_v3_desc_as_client(client_desc);
+
+ done:
+ return 0;
+}
+
+/* Return true iff the cached client descriptor at <b>cached_desc</b has
+ * expired. */
+static int
+cached_client_descriptor_has_expired(time_t now,
+ const hs_cache_client_descriptor_t *cached_desc)
+{
+ /* We use the current consensus time to see if we should expire this
+ * descriptor since we use consensus time for all other parts of the protocol
+ * as well (e.g. to build the blinded key and compute time periods). */
+ const networkstatus_t *ns = networkstatus_get_live_consensus(now);
+ /* If we don't have a recent consensus, consider this entry expired since we
+ * will want to fetch a new HS desc when we get a live consensus. */
+ if (!ns) {
+ return 1;
+ }
+
+ if (cached_desc->expiration_ts <= ns->valid_after) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* clean the client cache using now as the current time. Return the total size
+ * of removed bytes from the cache. */
+static size_t
+cache_clean_v3_as_client(time_t now)
+{
+ size_t bytes_removed = 0;
+
+ if (!hs_cache_v3_client) { /* No cache to clean. Just return. */
+ return 0;
+ }
+
+ DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key,
+ hs_cache_client_descriptor_t *, entry) {
+ size_t entry_size;
+
+ /* If the entry has not expired, continue to the next cached entry */
+ if (!cached_client_descriptor_has_expired(now, entry)) {
+ continue;
+ }
+ /* Here, our entry has expired, remove and free. */
+ MAP_DEL_CURRENT(key);
+ entry_size = cache_get_client_entry_size(entry);
+ bytes_removed += entry_size;
+ /* Entry is not in the cache anymore, destroy it. */
+ cache_client_desc_free(entry);
+ /* Update our OOM. We didn't use the remove() function because we are in
+ * a loop so we have to explicitly decrement. */
+ rend_cache_decrement_allocation(entry_size);
+ /* Logging. */
+ {
+ 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;
+}
+
+/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return
+ * its HS encoded descriptor if it's stored in our cache, or NULL if not. */
+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;
+}
+
+/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return
+ * its HS descriptor if it's stored in our cache, or NULL if not. */
+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) {
+ tor_assert(cached_desc->desc);
+ return cached_desc->desc;
+ }
+
+ return NULL;
+}
+
+/** Public API: Given an encoded descriptor, store it in the client HS
+ * cache. Return -1 on error, 0 on success .*/
+int
+hs_cache_store_as_client(const char *desc_str,
+ const ed25519_public_key_t *identity_pk)
+{
+ hs_cache_client_descriptor_t *client_desc = NULL;
+
+ tor_assert(desc_str);
+ tor_assert(identity_pk);
+
+ /* Create client cache descriptor object */
+ client_desc = cache_client_desc_new(desc_str, identity_pk);
+ if (!client_desc) {
+ log_warn(LD_GENERAL, "Failed to parse received descriptor %s.",
+ escaped(desc_str));
+ goto err;
+ }
+
+ /* Push it to the cache */
+ if (cache_store_as_client(client_desc) < 0) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ cache_client_desc_free(client_desc);
+ return -1;
+}
+
+/* Clean all client caches using the current time now. */
+void
+hs_cache_clean_as_client(time_t now)
+{
+ /* Start with v2 cache cleaning. */
+ rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
+ /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function
+ * to compute the cutoff by itself using the lifetime value. */
+ cache_clean_v3_as_client(now);
+}
+
+/* Purge the client descriptor cache. */
+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);
+ /* Update our OOM. We didn't use the remove() function because we are in
+ * a loop so we have to explicitly decrement. */
+ rend_cache_decrement_allocation(entry_size);
+ } DIGEST256MAP_FOREACH_END;
+
+ log_info(LD_REND, "Hidden service client descriptor cache purged.");
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, note the given failure in the client intro state cache. */
+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) {
+ /* Create a new entry and add it to the cache. */
+ cache_client_intro_state_add(service_pk, auth_key, &entry);
+ }
+ /* Note down the entry. */
+ cache_client_intro_state_note(entry, failure);
+}
+
+/* For a given service identity public key and an introduction authentication
+ * key, return true iff it is present in the failure cache. */
+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;
+}
+
+/* Cleanup the client introduction state cache. */
+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) {
+ /* Cleanup intro points failure. */
+ cache_client_intro_state_clean(cutoff, cache);
+
+ /* Is this cache empty for this service key? If yes, remove it from the
+ * cache. Else keep it. */
+ if (cache_client_intro_state_is_empty(cache)) {
+ cache_client_intro_state_free(cache);
+ MAP_DEL_CURRENT(key);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Purge the client introduction state cache. */
+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.");
+}
+
+/**************** Generics *********************************/
+
+/* Do a round of OOM cleanup on all directory caches. Return the amount of
+ * removed bytes. It is possible that the returned value is lower than
+ * min_remove_bytes if the caches get emptied out so the caller should be
+ * aware of this. */
+size_t
+hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
+{
+ time_t k;
+ size_t bytes_removed = 0;
+
+ /* Our OOM handler called with 0 bytes to remove is a code flow error. */
+ tor_assert(min_remove_bytes != 0);
+
+ /* The algorithm is as follow. K is the oldest expected descriptor age.
+ *
+ * 1) Deallocate all entries from v2 cache that are older than K hours.
+ * 1.1) If the amount of remove bytes has been reached, stop.
+ * 2) Deallocate all entries from v3 cache that are older than K hours
+ * 2.1) If the amount of remove bytes has been reached, stop.
+ * 3) Set K = K - RendPostPeriod and repeat process until K is < 0.
+ *
+ * This ends up being O(Kn).
+ */
+
+ /* Set K to the oldest expected age in seconds which is the maximum
+ * lifetime of a cache entry. We'll use the v2 lifetime because it's much
+ * bigger than the v3 thus leading to cleaning older descriptors. */
+ k = rend_cache_max_entry_lifetime();
+
+ do {
+ time_t cutoff;
+
+ /* If K becomes negative, it means we've empty the caches so stop and
+ * return what we were able to cleanup. */
+ if (k < 0) {
+ break;
+ }
+ /* Compute a cutoff value with K and the current time. */
+ cutoff = now - k;
+
+ /* Start by cleaning the v2 cache with that cutoff. */
+ bytes_removed += rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ if (bytes_removed < min_remove_bytes) {
+ /* We haven't remove enough bytes so clean v3 cache. */
+ bytes_removed += cache_clean_v3_as_dir(now, cutoff);
+ /* Decrement K by a post period to shorten the cutoff. */
+ k -= get_options()->RendPostPeriod;
+ }
+ } while (bytes_removed < min_remove_bytes);
+
+ return bytes_removed;
+}
+
+/* Return the maximum size of a v3 HS descriptor. */
+unsigned int
+hs_cache_get_max_descriptor_size(void)
+{
+ return (unsigned) networkstatus_get_param(NULL,
+ "HSV3MaxDescriptorSize",
+ HS_DESC_MAX_LEN, 1, INT32_MAX);
+}
+
+/* Initialize the hidden service cache subsystem. */
+void
+hs_cache_init(void)
+{
+ /* Calling this twice is very wrong code flow. */
+ 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();
+}
+
+/* Cleanup the hidden service cache subsystem. */
+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;
+}
diff --git a/src/feature/hs/hs_cache.h b/src/feature/hs/hs_cache.h
new file mode 100644
index 0000000000..1e479700fa
--- /dev/null
+++ b/src/feature/hs/hs_cache.h
@@ -0,0 +1,130 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.h
+ * \brief Header file for hs_cache.c
+ **/
+
+#ifndef TOR_HS_CACHE_H
+#define TOR_HS_CACHE_H
+
+#include <stdint.h>
+
+#include "or/hs_common.h"
+#include "or/hs_descriptor.h"
+#include "or/rendcommon.h"
+#include "or/torcert.h"
+
+struct ed25519_public_key_t;
+
+/* This is the maximum time an introduction point state object can stay in the
+ * client cache in seconds (2 mins or 120 seconds). */
+#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60)
+
+/* Introduction point state. */
+typedef struct hs_cache_intro_state_t {
+ /* When this entry was created and put in the cache. */
+ time_t created_ts;
+
+ /* Did it suffered a generic error? */
+ unsigned int error : 1;
+
+ /* Did it timed out? */
+ unsigned int timed_out : 1;
+
+ /* How many times we tried to reached it and it was unreachable. */
+ uint32_t unreachable_count;
+} hs_cache_intro_state_t;
+
+typedef struct hs_cache_client_intro_state_t {
+ /* Contains hs_cache_intro_state_t object indexed by introduction point
+ * authentication key. */
+ digest256map_t *intro_points;
+} hs_cache_client_intro_state_t;
+
+/* Descriptor representation on the directory side which is a subset of
+ * information that the HSDir can decode and serve it. */
+typedef struct hs_cache_dir_descriptor_t {
+ /* This object is indexed using the blinded pubkey located in the plaintext
+ * data which is populated only once the descriptor has been successfully
+ * decoded and validated. This simply points to that pubkey. */
+ const uint8_t *key;
+
+ /* When does this entry has been created. Used to expire entries. */
+ time_t created_ts;
+
+ /* Descriptor plaintext information. Obviously, we can't decrypt the
+ * encrypted part of the descriptor. */
+ hs_desc_plaintext_data_t *plaintext_data;
+
+ /* Encoded descriptor which is basically in text form. It's a NUL terminated
+ * string thus safe to strlen(). */
+ char *encoded_desc;
+} hs_cache_dir_descriptor_t;
+
+/* Public API */
+
+void hs_cache_init(void);
+void hs_cache_free_all(void);
+void hs_cache_clean_as_dir(time_t now);
+size_t hs_cache_handle_oom(time_t now, size_t min_remove_bytes);
+
+unsigned int hs_cache_get_max_descriptor_size(void);
+
+/* Store and Lookup function. They are version agnostic that is depending on
+ * the requested version of the descriptor, it will be re-routed to the
+ * right function. */
+int hs_cache_store_as_dir(const char *desc);
+int hs_cache_lookup_as_dir(uint32_t version, const char *query,
+ const char **desc_out);
+
+const hs_descriptor_t *
+hs_cache_lookup_as_client(const struct ed25519_public_key_t *key);
+const char *
+hs_cache_lookup_encoded_as_client(const struct ed25519_public_key_t *key);
+int hs_cache_store_as_client(const char *desc_str,
+ const struct ed25519_public_key_t *identity_pk);
+void hs_cache_clean_as_client(time_t now);
+void hs_cache_purge_as_client(void);
+
+/* Client failure cache. */
+void hs_cache_client_intro_state_note(
+ const struct ed25519_public_key_t *service_pk,
+ const struct ed25519_public_key_t *auth_key,
+ rend_intro_point_failure_t failure);
+const hs_cache_intro_state_t *hs_cache_client_intro_state_find(
+ const struct ed25519_public_key_t *service_pk,
+ const struct ed25519_public_key_t *auth_key);
+void hs_cache_client_intro_state_clean(time_t now);
+void hs_cache_client_intro_state_purge(void);
+
+#ifdef HS_CACHE_PRIVATE
+#include "lib/crypt_ops/crypto_ed25519.h"
+
+/** Represents a locally cached HS descriptor on a hidden service client. */
+typedef struct hs_cache_client_descriptor_t {
+ /* This object is indexed using the service identity public key */
+ struct ed25519_public_key_t key;
+
+ /* When will this entry expire? We expire cached client descriptors in the
+ * start of the next time period, since that's when clients need to start
+ * using the next blinded key of the service. */
+ time_t expiration_ts;
+
+ /* The cached descriptor, this object is the owner. It can't be NULL. A
+ * cache object without a valid descriptor is not possible. */
+ hs_descriptor_t *desc;
+
+ /* Encoded descriptor in string form. Can't be NULL. */
+ char *encoded_desc;
+} hs_cache_client_descriptor_t;
+
+STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
+
+STATIC hs_cache_client_descriptor_t *
+lookup_v3_desc_as_client(const uint8_t *key);
+
+#endif /* defined(HS_CACHE_PRIVATE) */
+
+#endif /* !defined(TOR_HS_CACHE_H) */
diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c
new file mode 100644
index 0000000000..f8b76c5133
--- /dev/null
+++ b/src/feature/hs/hs_cell.c
@@ -0,0 +1,950 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.c
+ * \brief Hidden service API for cell creation and handling.
+ **/
+
+#include "or/or.h"
+#include "or/config.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/rendservice.h"
+#include "or/replaycache.h"
+
+#include "or/hs_cell.h"
+#include "or/hs_ntor.h"
+
+#include "or/origin_circuit_st.h"
+
+/* Trunnel. */
+#include "trunnel/ed25519_cert.h"
+#include "trunnel/hs/cell_common.h"
+#include "trunnel/hs/cell_establish_intro.h"
+#include "trunnel/hs/cell_introduce1.h"
+#include "trunnel/hs/cell_rendezvous.h"
+
+/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is
+ * the cell content up to the ENCRYPTED section of length encoded_cell_len.
+ * The encrypted param is the start of the ENCRYPTED section of length
+ * encrypted_len. The mac_key is the key needed for the computation of the MAC
+ * derived from the ntor handshake of length mac_key_len.
+ *
+ * The length mac_out_len must be at least DIGEST256_LEN. */
+static void
+compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ const uint8_t *mac_key, size_t mac_key_len,
+ uint8_t *mac_out, size_t mac_out_len)
+{
+ size_t offset = 0;
+ size_t mac_msg_len;
+ uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(encoded_cell);
+ tor_assert(encrypted);
+ tor_assert(mac_key);
+ tor_assert(mac_out);
+ tor_assert(mac_out_len >= DIGEST256_LEN);
+
+ /* Compute the size of the message which is basically the entire cell until
+ * the MAC field of course. */
+ mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN);
+ tor_assert(mac_msg_len <= sizeof(mac_msg));
+
+ /* First, put the encoded cell in the msg. */
+ memcpy(mac_msg, encoded_cell, encoded_cell_len);
+ offset += encoded_cell_len;
+ /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which
+ * is junk at this point). */
+ memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN));
+ offset += (encrypted_len - DIGEST256_LEN);
+ tor_assert(offset == mac_msg_len);
+
+ crypto_mac_sha3_256(mac_out, mac_out_len,
+ mac_key, mac_key_len,
+ mac_msg, mac_msg_len);
+ memwipe(mac_msg, 0, sizeof(mac_msg));
+}
+
+/* From a set of keys, subcredential and the ENCRYPTED section of an
+ * INTRODUCE2 cell, return a newly allocated intro cell keys structure.
+ * Finally, the client public key is copied in client_pk. On error, return
+ * NULL. */
+static hs_ntor_intro_cell_keys_t *
+get_introduce2_key_material(const ed25519_public_key_t *auth_key,
+ const curve25519_keypair_t *enc_key,
+ const uint8_t *subcredential,
+ const uint8_t *encrypted_section,
+ curve25519_public_key_t *client_pk)
+{
+ hs_ntor_intro_cell_keys_t *keys;
+
+ tor_assert(auth_key);
+ tor_assert(enc_key);
+ tor_assert(subcredential);
+ tor_assert(encrypted_section);
+ tor_assert(client_pk);
+
+ keys = tor_malloc_zero(sizeof(*keys));
+
+ /* First bytes of the ENCRYPTED section are the client public key. */
+ memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);
+
+ if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk,
+ subcredential, keys) < 0) {
+ /* Don't rely on the caller to wipe this on error. */
+ memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
+ tor_free(keys);
+ keys = NULL;
+ }
+ return keys;
+}
+
+/* Using the given encryption key, decrypt the encrypted_section of length
+ * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated
+ * buffer containing the decrypted data. On decryption failure, NULL is
+ * returned. */
+static uint8_t *
+decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section,
+ size_t encrypted_section_len)
+{
+ uint8_t *decrypted = NULL;
+ crypto_cipher_t *cipher = NULL;
+
+ tor_assert(enc_key);
+ tor_assert(encrypted_section);
+
+ /* Decrypt ENCRYPTED section. */
+ cipher = crypto_cipher_new_with_bits((char *) enc_key,
+ CURVE25519_PUBKEY_LEN * 8);
+ tor_assert(cipher);
+
+ /* This is symmetric encryption so can't be bigger than the encrypted
+ * section length. */
+ decrypted = tor_malloc_zero(encrypted_section_len);
+ if (crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted_section,
+ encrypted_section_len) < 0) {
+ tor_free(decrypted);
+ decrypted = NULL;
+ goto done;
+ }
+
+ done:
+ crypto_cipher_free(cipher);
+ return decrypted;
+}
+
+/* Given a pointer to the decrypted data of the ENCRYPTED section of an
+ * INTRODUCE2 cell of length decrypted_len, parse and validate the cell
+ * content. Return a newly allocated cell structure or NULL on error. The
+ * circuit and service object are only used for logging purposes. */
+static trn_cell_introduce_encrypted_t *
+parse_introduce2_encrypted(const uint8_t *decrypted_data,
+ size_t decrypted_len, const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+
+ tor_assert(decrypted_data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data,
+ decrypted_len) < 0) {
+ log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of "
+ "the INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) !=
+ HS_CELL_ONION_KEY_TYPE_NTOR) {
+ log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but "
+ "expected %u on circuit %u for service %s",
+ trn_cell_introduce_encrypted_get_onion_key_type(enc_cell),
+ HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) !=
+ CURVE25519_PUBKEY_LEN) {
+ log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but "
+ "expected %d on circuit %u for service %s",
+ (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* XXX: Validate NSPEC field as well. */
+
+ return enc_cell;
+ err:
+ trn_cell_introduce_encrypted_free(enc_cell);
+ return NULL;
+}
+
+/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA
+ * encryption key. The encoded cell is put in cell_out that MUST at least be
+ * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on
+ * success else a negative value and cell_out is untouched. */
+static ssize_t
+build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+
+ tor_assert(circ_nonce);
+ tor_assert(enc_key);
+ tor_assert(cell_out);
+
+ memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE);
+
+ cell_len = rend_service_encode_establish_intro_cell((char*)cell_out,
+ RELAY_PAYLOAD_SIZE,
+ enc_key, circ_nonce);
+ return cell_len;
+}
+
+/* Parse an INTRODUCE2 cell from payload of size payload_len for the given
+ * service and circuit which are used only for logging purposes. The resulting
+ * parsed cell is put in cell_ptr_out.
+ *
+ * This function only parses prop224 INTRODUCE2 cells even when the intro point
+ * is a legacy intro point. That's because intro points don't actually care
+ * about the contents of the introduce cell. Legacy INTRODUCE cells are only
+ * used by the legacy system now.
+ *
+ * Return 0 on success else a negative value and cell_ptr_out is untouched. */
+static int
+parse_introduce2_cell(const hs_service_t *service,
+ const origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len,
+ trn_cell_introduce1_t **cell_ptr_out)
+{
+ trn_cell_introduce1_t *cell = NULL;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(cell_ptr_out);
+
+ /* Parse the cell so we can start cell validation. */
+ if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Success. */
+ *cell_ptr_out = cell;
+ return 0;
+ err:
+ return -1;
+}
+
+/* Set the onion public key onion_pk in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell,
+ const uint8_t *onion_pk)
+{
+ tor_assert(cell);
+ tor_assert(onion_pk);
+ /* There is only one possible key type for a non legacy cell. */
+ trn_cell_introduce_encrypted_set_onion_key_type(cell,
+ HS_CELL_ONION_KEY_TYPE_NTOR);
+ trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN);
+ trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN);
+ memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk,
+ trn_cell_introduce_encrypted_getlen_onion_key(cell));
+}
+
+/* Set the link specifiers in lspecs in cell, the encrypted section of an
+ * INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell,
+ const smartlist_t *lspecs)
+{
+ tor_assert(cell);
+ tor_assert(lspecs);
+ tor_assert(smartlist_len(lspecs) > 0);
+ tor_assert(smartlist_len(lspecs) <= UINT8_MAX);
+
+ uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs);
+ trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num);
+ /* We aren't duplicating the link specifiers object here which means that
+ * the ownership goes to the trn_cell_introduce_encrypted_t cell and those
+ * object will be freed when the cell is. */
+ SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls,
+ trn_cell_introduce_encrypted_add_nspecs(cell, ls));
+}
+
+/* Set padding in the enc_cell only if needed that is the total length of both
+ * sections are below the mininum required for an INTRODUCE1 cell. */
+static void
+introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell,
+ trn_cell_introduce_encrypted_t *enc_cell)
+{
+ tor_assert(cell);
+ tor_assert(enc_cell);
+ /* This is the length we expect to have once encoded of the whole cell. */
+ ssize_t full_len = trn_cell_introduce1_encoded_len(cell) +
+ trn_cell_introduce_encrypted_encoded_len(enc_cell);
+ tor_assert(full_len > 0);
+ if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) {
+ size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len;
+ trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding);
+ memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0,
+ trn_cell_introduce_encrypted_getlen_pad(enc_cell));
+ }
+}
+
+/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell
+ * and the INTRODUCE1 data.
+ *
+ * This can't fail but it is very important that the caller sets every field
+ * in data so the computation of the INTRODUCE1 keys doesn't fail. */
+static void
+introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell,
+ const trn_cell_introduce_encrypted_t *enc_cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ size_t offset = 0;
+ ssize_t encrypted_len;
+ ssize_t encoded_cell_len, encoded_enc_cell_len;
+ uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t *encrypted = NULL;
+ uint8_t mac[DIGEST256_LEN];
+ crypto_cipher_t *cipher = NULL;
+ hs_ntor_intro_cell_keys_t keys;
+
+ tor_assert(cell);
+ tor_assert(enc_cell);
+ tor_assert(data);
+
+ /* Encode the cells up to now of what we have to we can perform the MAC
+ * computation on it. */
+ encoded_cell_len = trn_cell_introduce1_encode(encoded_cell,
+ sizeof(encoded_cell), cell);
+ /* We have a much more serious issue if this isn't true. */
+ tor_assert(encoded_cell_len > 0);
+
+ encoded_enc_cell_len =
+ trn_cell_introduce_encrypted_encode(encoded_enc_cell,
+ sizeof(encoded_enc_cell), enc_cell);
+ /* We have a much more serious issue if this isn't true. */
+ tor_assert(encoded_enc_cell_len > 0);
+
+ /* Get the key material for the encryption. */
+ if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk,
+ data->client_kp,
+ data->subcredential, &keys) < 0) {
+ tor_assert_unreached();
+ }
+
+ /* Prepare cipher with the encryption key just computed. */
+ cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key,
+ sizeof(keys.enc_key) * 8);
+ tor_assert(cipher);
+
+ /* Compute the length of the ENCRYPTED section which is the CLIENT_PK,
+ * ENCRYPTED_DATA and MAC length. */
+ encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len +
+ sizeof(mac);
+ tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE);
+ encrypted = tor_malloc_zero(encrypted_len);
+
+ /* Put the CLIENT_PK first. */
+ memcpy(encrypted, data->client_kp->pubkey.public_key,
+ sizeof(data->client_kp->pubkey.public_key));
+ offset += sizeof(data->client_kp->pubkey.public_key);
+ /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) encrypted + offset,
+ (const char *) encoded_enc_cell, encoded_enc_cell_len);
+ crypto_cipher_free(cipher);
+ offset += encoded_enc_cell_len;
+ /* Compute MAC from the above and put it in the buffer. This function will
+ * make the adjustment to the encrypted_len to omit the MAC length. */
+ compute_introduce_mac(encoded_cell, encoded_cell_len,
+ encrypted, encrypted_len,
+ keys.mac_key, sizeof(keys.mac_key),
+ mac, sizeof(mac));
+ memcpy(encrypted + offset, mac, sizeof(mac));
+ offset += sizeof(mac);
+ tor_assert(offset == (size_t) encrypted_len);
+
+ /* Set the ENCRYPTED section in the cell. */
+ trn_cell_introduce1_setlen_encrypted(cell, encrypted_len);
+ memcpy(trn_cell_introduce1_getarray_encrypted(cell),
+ encrypted, encrypted_len);
+
+ /* Cleanup. */
+ memwipe(&keys, 0, sizeof(keys));
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(encrypted, 0, sizeof(encrypted_len));
+ memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell));
+ tor_free(encrypted);
+}
+
+/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means
+ * set it, encrypt it and encode it. */
+static void
+introduce1_set_encrypted(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ trn_cell_introduce_encrypted_t *enc_cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(cell);
+ tor_assert(data);
+
+ enc_cell = trn_cell_introduce_encrypted_new();
+ tor_assert(enc_cell);
+
+ /* Set extension data. None are used. */
+ ext = trn_cell_extension_new();
+ tor_assert(ext);
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce_encrypted_set_extensions(enc_cell, ext);
+
+ /* Set the rendezvous cookie. */
+ memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell),
+ data->rendezvous_cookie, REND_COOKIE_LEN);
+
+ /* Set the onion public key. */
+ introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key);
+
+ /* Set the link specifiers. */
+ introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers);
+
+ /* Set padding. */
+ introduce1_set_encrypted_padding(cell, enc_cell);
+
+ /* Encrypt and encode it in the cell. */
+ introduce1_encrypt_and_encode(cell, enc_cell, data);
+
+ /* Cleanup. */
+ trn_cell_introduce_encrypted_free(enc_cell);
+}
+
+/* Set the authentication key in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_auth_key(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ tor_assert(cell);
+ tor_assert(data);
+ /* There is only one possible type for a non legacy cell. */
+ trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519);
+ trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN);
+ trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN);
+ memcpy(trn_cell_introduce1_getarray_auth_key(cell),
+ data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell));
+}
+
+/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */
+static void
+introduce1_set_legacy_id(trn_cell_introduce1_t *cell,
+ const hs_cell_introduce1_data_t *data)
+{
+ tor_assert(cell);
+ tor_assert(data);
+
+ if (data->is_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) {
+ return;
+ }
+ memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell),
+ digest, trn_cell_introduce1_getlen_legacy_key_id(cell));
+ } else {
+ /* We have to zeroed the LEGACY_KEY_ID field. */
+ memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0,
+ trn_cell_introduce1_getlen_legacy_key_id(cell));
+ }
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point
+ * object. The encoded cell is put in cell_out that MUST at least be of the
+ * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else
+ * a negative value and cell_out is untouched. This function also supports
+ * legacy cell creation. */
+ssize_t
+hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len = -1;
+ uint16_t sig_len = ED25519_SIG_LEN;
+ trn_cell_extension_t *ext;
+ trn_cell_establish_intro_t *cell = NULL;
+
+ tor_assert(circ_nonce);
+ tor_assert(ip);
+
+ /* Quickly handle the legacy IP. */
+ if (ip->base.is_only_legacy) {
+ tor_assert(ip->legacy_key);
+ cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key,
+ cell_out);
+ tor_assert(cell_len <= RELAY_PAYLOAD_SIZE);
+ /* Success or not we are done here. */
+ goto done;
+ }
+
+ /* Set extension data. None used here. */
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ cell = trn_cell_establish_intro_new();
+ trn_cell_establish_intro_set_extensions(cell, ext);
+ /* Set signature size. Array is then allocated in the cell. We need to do
+ * this early so we can use trunnel API to get the signature length. */
+ trn_cell_establish_intro_set_sig_len(cell, sig_len);
+ trn_cell_establish_intro_setlen_sig(cell, sig_len);
+
+ /* Set AUTH_KEY_TYPE: 2 means ed25519 */
+ trn_cell_establish_intro_set_auth_key_type(cell,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519);
+
+ /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of
+ * AUTH_KEY to match */
+ {
+ uint16_t auth_key_len = ED25519_PUBKEY_LEN;
+ trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
+ trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
+ /* We do this call _after_ setting the length because it's reallocated at
+ * that point only. */
+ uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
+ memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len);
+ }
+
+ /* Calculate HANDSHAKE_AUTH field (MAC). */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_mac_offset =
+ sig_len + sizeof(cell->sig_len) +
+ trn_cell_establish_intro_getlen_handshake_mac(cell);
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0};
+ uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the MAC using the raw bytes. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+ /* Sanity check. */
+ tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset);
+
+ /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ (uint8_t *) circ_nonce, DIGEST_LEN,
+ tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset);
+ handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell);
+ memcpy(handshake_ptr, mac, sizeof(mac));
+
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Calculate the cell signature SIG. */
+ {
+ ssize_t tmp_cell_enc_len = 0;
+ ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len));
+ uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr;
+ ed25519_signature_t sig;
+
+ /* We first encode the current fields we have in the cell so we can
+ * compute the signature from the raw bytes of the cell. */
+ tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
+ sizeof(tmp_cell_enc),
+ cell);
+ if (BUG(tmp_cell_enc_len < 0)) {
+ goto done;
+ }
+
+ if (ed25519_sign_prefixed(&sig, tmp_cell_enc,
+ tmp_cell_enc_len - tmp_cell_sig_offset,
+ ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) {
+ log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell.");
+ goto done;
+ }
+ /* Copy the signature into the cell. */
+ sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
+ memcpy(sig_ptr, sig.sig, sig_len);
+
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* Encode the cell. Can't be bigger than a standard cell. */
+ cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE,
+ cell);
+
+ done:
+ trn_cell_establish_intro_free(cell);
+ return cell_len;
+}
+
+/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we
+ * are successful at parsing it, return the length of the parsed cell else a
+ * negative value on error. */
+ssize_t
+hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
+{
+ ssize_t ret;
+ trn_cell_intro_established_t *cell = NULL;
+
+ tor_assert(payload);
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. */
+ ret = trn_cell_intro_established_parse(&cell, payload, payload_len);
+ if (ret >= 0) {
+ /* On success, we do not keep the cell, we just notify the caller that it
+ * was successfully parsed. */
+ trn_cell_intro_established_free(cell);
+ }
+ return ret;
+}
+
+/* Parsse the INTRODUCE2 cell using data which contains everything we need to
+ * do so and contains the destination buffers of information we extract and
+ * compute from the cell. Return 0 on success else a negative value. The
+ * service and circ are only used for logging purposes. */
+ssize_t
+hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service)
+{
+ int ret = -1;
+ time_t elapsed;
+ uint8_t *decrypted = NULL;
+ size_t encrypted_section_len;
+ const uint8_t *encrypted_section;
+ trn_cell_introduce1_t *cell = NULL;
+ trn_cell_introduce_encrypted_t *enc_cell = NULL;
+ hs_ntor_intro_cell_keys_t *intro_keys = NULL;
+
+ tor_assert(data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ /* Parse the cell into a decoded data structure pointed by cell_ptr. */
+ if (parse_introduce2_cell(service, circ, data->payload, data->payload_len,
+ &cell) < 0) {
+ goto done;
+ }
+
+ log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u "
+ "for service %s. Decoding encrypted section...",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+
+ encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell);
+ encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell);
+
+ /* Encrypted section must at least contain the CLIENT_PK and MAC which is
+ * defined in section 3.3.2 of the specification. */
+ if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length "
+ "for service %s. Dropping cell.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Check our replay cache for this introduction point. */
+ if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
+ encrypted_section_len, &elapsed)) {
+ log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the"
+ "same ENCRYPTED section was seen %ld seconds ago. "
+ "Dropping cell.", (long int) elapsed);
+ goto done;
+ }
+
+ /* Build the key material out of the key material found in the cell. */
+ intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
+ data->subcredential,
+ encrypted_section,
+ &data->client_pk);
+ if (intro_keys == NULL) {
+ log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
+ "compute key material on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Validate MAC from the cell and our computed key material. The MAC field
+ * in the cell is at the end of the encrypted section. */
+ {
+ uint8_t mac[DIGEST256_LEN];
+ /* The MAC field is at the very end of the ENCRYPTED section. */
+ size_t mac_offset = encrypted_section_len - sizeof(mac);
+ /* Compute the MAC. Use the entire encoded payload with a length up to the
+ * ENCRYPTED section. */
+ compute_introduce_mac(data->payload,
+ data->payload_len - encrypted_section_len,
+ encrypted_section, encrypted_section_len,
+ intro_keys->mac_key, sizeof(intro_keys->mac_key),
+ mac, sizeof(mac));
+ if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) {
+ log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on "
+ "circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ }
+
+ {
+ /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
+ const uint8_t *encrypted_data =
+ encrypted_section + sizeof(data->client_pk);
+ /* It's symmetric encryption so it's correct to use the ENCRYPTED length
+ * for decryption. Computes the length of ENCRYPTED_DATA meaning removing
+ * the CLIENT_PK and MAC length. */
+ size_t encrypted_data_len =
+ encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN);
+
+ /* This decrypts the ENCRYPTED_DATA section of the cell. */
+ decrypted = decrypt_introduce2(intro_keys->enc_key,
+ encrypted_data, encrypted_data_len);
+ if (decrypted == NULL) {
+ log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an "
+ "INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Parse this blob into an encrypted cell structure so we can then extract
+ * the data we need out of it. */
+ enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len,
+ circ, service);
+ memwipe(decrypted, 0, encrypted_data_len);
+ if (enc_cell == NULL) {
+ goto done;
+ }
+ }
+
+ /* XXX: Implement client authorization checks. */
+
+ /* Extract onion key and rendezvous cookie from the cell used for the
+ * rendezvous point circuit e2e encryption. */
+ memcpy(data->onion_pk.public_key,
+ trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell),
+ CURVE25519_PUBKEY_LEN);
+ memcpy(data->rendezvous_cookie,
+ trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell),
+ sizeof(data->rendezvous_cookie));
+
+ /* Extract rendezvous link specifiers. */
+ for (size_t idx = 0;
+ idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) {
+ link_specifier_t *lspec =
+ trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx);
+ smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec));
+ }
+
+ /* Success. */
+ ret = 0;
+ log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit.");
+
+ done:
+ if (intro_keys) {
+ memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ tor_free(intro_keys);
+ }
+ tor_free(decrypted);
+ trn_cell_introduce_encrypted_free(enc_cell);
+ trn_cell_introduce1_free(cell);
+ return ret;
+}
+
+/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake
+ * info. The encoded cell is put in cell_out and the length of the data is
+ * returned. This can't fail. */
+ssize_t
+hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+ trn_cell_rendezvous1_t *cell;
+
+ tor_assert(rendezvous_cookie);
+ tor_assert(rendezvous_handshake_info);
+ tor_assert(cell_out);
+
+ cell = trn_cell_rendezvous1_new();
+ /* Set the RENDEZVOUS_COOKIE. */
+ memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell),
+ rendezvous_cookie, rendezvous_cookie_len);
+ /* Set the HANDSHAKE_INFO. */
+ trn_cell_rendezvous1_setlen_handshake_info(cell,
+ rendezvous_handshake_info_len);
+ memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell),
+ rendezvous_handshake_info, rendezvous_handshake_info_len);
+ /* Encoding. */
+ cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+ tor_assert(cell_len > 0);
+
+ trn_cell_rendezvous1_free(cell);
+ return cell_len;
+}
+
+/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in
+ * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the
+ * encoded length is returned else a negative value and the content of
+ * cell_out should be ignored. */
+ssize_t
+hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+ uint8_t *cell_out)
+{
+ ssize_t cell_len;
+ trn_cell_introduce1_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(data);
+ tor_assert(cell_out);
+
+ cell = trn_cell_introduce1_new();
+ tor_assert(cell);
+
+ /* Set extension data. None are used. */
+ ext = trn_cell_extension_new();
+ tor_assert(ext);
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce1_set_extensions(cell, ext);
+
+ /* Set the legacy ID field. */
+ introduce1_set_legacy_id(cell, data);
+
+ /* Set the authentication key. */
+ introduce1_set_auth_key(cell, data);
+
+ /* Set the encrypted section. This will set, encrypt and encode the
+ * ENCRYPTED section in the cell. After this, we'll be ready to encode. */
+ introduce1_set_encrypted(cell, data);
+
+ /* Final encoding. */
+ cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
+
+ trn_cell_introduce1_free(cell);
+ return cell_len;
+}
+
+/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The
+ * encoded cell is put in cell_out which must be of at least
+ * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the
+ * caller should clear up the content of the cell.
+ *
+ * This function can't fail. */
+ssize_t
+hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+ uint8_t *cell_out)
+{
+ tor_assert(rendezvous_cookie);
+ tor_assert(cell_out);
+
+ memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN);
+ return HS_REND_COOKIE_LEN;
+}
+
+/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len.
+ * Return the status code on success else a negative value if the cell as not
+ * decodable. */
+int
+hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ trn_cell_introduce_ack_t *cell = NULL;
+
+ tor_assert(payload);
+
+ /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a
+ * NACK is 1 byte. We can't use the legacy function for this so we have to
+ * do a special case. */
+ if (payload_len <= 1) {
+ if (payload_len == 0) {
+ ret = HS_CELL_INTRO_ACK_SUCCESS;
+ } else {
+ ret = HS_CELL_INTRO_ACK_FAILURE;
+ }
+ goto end;
+ }
+
+ if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it.");
+ goto end;
+ }
+
+ ret = trn_cell_introduce_ack_get_status(cell);
+
+ end:
+ trn_cell_introduce_ack_free(cell);
+ return ret;
+}
+
+/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On
+ * success, handshake_info contains the data in the HANDSHAKE_INFO field, and
+ * 0 is returned. On error, a negative value is returned. */
+int
+hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+ uint8_t *handshake_info, size_t handshake_info_len)
+{
+ int ret = -1;
+ trn_cell_rendezvous2_t *cell = NULL;
+
+ tor_assert(payload);
+ tor_assert(handshake_info);
+
+ if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) {
+ log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it.");
+ goto end;
+ }
+
+ /* Static size, we should never have an issue with this else we messed up
+ * our code flow. */
+ tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) ==
+ handshake_info_len);
+ memcpy(handshake_info,
+ trn_cell_rendezvous2_getconstarray_handshake_info(cell),
+ handshake_info_len);
+ ret = 0;
+
+ end:
+ trn_cell_rendezvous2_free(cell);
+ return ret;
+}
+
+/* Clear the given INTRODUCE1 data structure data. */
+void
+hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data)
+{
+ if (data == NULL) {
+ return;
+ }
+ /* Object in this list have been moved to the cell object when building it
+ * so they've been freed earlier. We do that in order to avoid duplicating
+ * them leading to more memory and CPU time being used for nothing. */
+ smartlist_free(data->link_specifiers);
+ /* The data object has no ownership of any members. */
+ memwipe(data, 0, sizeof(hs_cell_introduce1_data_t));
+}
+
diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h
new file mode 100644
index 0000000000..4a522810c6
--- /dev/null
+++ b/src/feature/hs/hs_cell.h
@@ -0,0 +1,122 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.h
+ * \brief Header file containing cell data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CELL_H
+#define TOR_HS_CELL_H
+
+#include "or/or.h"
+#include "or/hs_service.h"
+
+/* An INTRODUCE1 cell requires at least this amount of bytes (see section
+ * 3.2.2 of the specification). Below this value, the cell must be padded. */
+#define HS_CELL_INTRODUCE1_MIN_SIZE 246
+
+/* Status code of an INTRODUCE_ACK cell. */
+typedef enum {
+ HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */
+ HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */
+ HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */
+ HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */
+} hs_cell_introd_ack_status_t;
+
+/* Onion key type found in the INTRODUCE1 cell. */
+typedef enum {
+ HS_CELL_ONION_KEY_TYPE_NTOR = 1,
+} hs_cell_onion_key_type_t;
+
+/* This data structure contains data that we need to build an INTRODUCE1 cell
+ * used by the INTRODUCE1 build function. */
+typedef struct hs_cell_introduce1_data_t {
+ /* Is this a legacy introduction point? */
+ unsigned int is_legacy : 1;
+ /* (Legacy only) The encryption key for a legacy intro point. Only set if
+ * is_legacy is true. */
+ const crypto_pk_t *legacy_key;
+ /* Introduction point authentication public key. */
+ const ed25519_public_key_t *auth_pk;
+ /* Introduction point encryption public key. */
+ const curve25519_public_key_t *enc_pk;
+ /* Subcredentials of the service. */
+ const uint8_t *subcredential;
+ /* Onion public key for the ntor handshake. */
+ const curve25519_public_key_t *onion_pk;
+ /* Rendezvous cookie. */
+ const uint8_t *rendezvous_cookie;
+ /* Public key put before the encrypted data (CLIENT_PK). */
+ const curve25519_keypair_t *client_kp;
+ /* Rendezvous point link specifiers. */
+ smartlist_t *link_specifiers;
+} hs_cell_introduce1_data_t;
+
+/* This data structure contains data that we need to parse an INTRODUCE2 cell
+ * which is used by the INTRODUCE2 cell parsing function. On a successful
+ * parsing, the onion_pk and rendezvous_cookie will be populated with the
+ * computed key material from the cell data. This structure is only used during
+ * INTRO2 parsing and discarded after that. */
+typedef struct hs_cell_introduce2_data_t {
+ /*** Immutable Section: Set on structure init. ***/
+
+ /* Introduction point authentication public key. Pointer owned by the
+ introduction point object through which we received the INTRO2 cell. */
+ const ed25519_public_key_t *auth_pk;
+ /* Introduction point encryption keypair for the ntor handshake. Pointer
+ owned by the introduction point object through which we received the
+ INTRO2 cell*/
+ const curve25519_keypair_t *enc_kp;
+ /* Subcredentials of the service. Pointer owned by the descriptor that owns
+ the introduction point through which we received the INTRO2 cell. */
+ const uint8_t *subcredential;
+ /* Payload of the received encoded cell. */
+ const uint8_t *payload;
+ /* Size of the payload of the received encoded cell. */
+ size_t payload_len;
+
+ /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
+
+ /* Onion public key computed using the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t onion_pk;
+ /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
+ uint8_t rendezvous_cookie[REND_COOKIE_LEN];
+ /* Client public key from the INTRODUCE2 encrypted section. */
+ curve25519_public_key_t client_pk;
+ /* Link specifiers of the rendezvous point. Contains link_specifier_t. */
+ smartlist_t *link_specifiers;
+ /* Replay cache of the introduction point. */
+ replaycache_t *replay_cache;
+} hs_cell_introduce2_data_t;
+
+/* Build cell API. */
+ssize_t hs_cell_build_establish_intro(const char *circ_nonce,
+ const hs_service_intro_point_t *ip,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
+ size_t rendezvous_cookie_len,
+ const uint8_t *rendezvous_handshake_info,
+ size_t rendezvous_handshake_info_len,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data,
+ uint8_t *cell_out);
+ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie,
+ uint8_t *cell_out);
+
+/* Parse cell API. */
+ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
+ size_t payload_len);
+ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
+ const origin_circuit_t *circ,
+ const hs_service_t *service);
+int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len);
+int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len,
+ uint8_t *handshake_info,
+ size_t handshake_info_len);
+
+/* Util API. */
+void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data);
+
+#endif /* !defined(TOR_HS_CELL_H) */
+
diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c
new file mode 100644
index 0000000000..5a2c7e4e42
--- /dev/null
+++ b/src/feature/hs/hs_circuit.c
@@ -0,0 +1,1249 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.c
+ **/
+
+#define HS_CIRCUIT_PRIVATE
+
+#include "or/or.h"
+#include "or/circpathbias.h"
+#include "or/circuitbuild.h"
+#include "or/circuitlist.h"
+#include "or/circuituse.h"
+#include "or/config.h"
+#include "lib/crypt_ops/crypto_dh.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/nodelist.h"
+#include "or/policies.h"
+#include "or/relay.h"
+#include "or/rendservice.h"
+#include "or/rephist.h"
+#include "or/router.h"
+
+#include "or/hs_cell.h"
+#include "or/hs_circuitmap.h"
+#include "or/hs_ident.h"
+#include "or/hs_ntor.h"
+#include "or/hs_service.h"
+#include "or/hs_circuit.h"
+
+/* Trunnel. */
+#include "trunnel/ed25519_cert.h"
+#include "trunnel/hs/cell_common.h"
+#include "trunnel/hs/cell_establish_intro.h"
+
+#include "or/cpath_build_state_st.h"
+#include "or/crypt_path_st.h"
+#include "or/node_st.h"
+#include "or/origin_circuit_st.h"
+
+/* A circuit is about to become an e2e rendezvous circuit. Check
+ * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
+ * circuit purpose is properly set, otherwise return false. */
+static int
+circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
+ int is_service_side)
+{
+ if (is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_warn(LD_BUG,
+ "HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ if (!is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_BUG,
+ "Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
+ * circuit. Initialize the crypt path crypto using the output material from the
+ * ntor key exchange at <b>ntor_key_seed</b>.
+ *
+ * If <b>is_service_side</b> is set, we are the hidden service and the final
+ * hop of the rendezvous circuit is the client on the other side. */
+static crypt_path_t *
+create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
+ crypt_path_t *cpath = NULL;
+
+ /* Do the key expansion */
+ if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
+ keys, sizeof(keys)) < 0) {
+ goto err;
+ }
+
+ /* Setup the cpath */
+ cpath = tor_malloc_zero(sizeof(crypt_path_t));
+ cpath->magic = CRYPT_PATH_MAGIC;
+
+ if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
+ is_service_side, 1) < 0) {
+ tor_free(cpath);
+ goto err;
+ }
+
+ err:
+ memwipe(keys, 0, sizeof(keys));
+ return cpath;
+}
+
+/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
+ * service on the other side of the rendezvous circuit <b>circ</b>. Initialize
+ * the crypt path crypto using the body of the RENDEZVOUS1 cell at
+ * <b>rend_cell_body</b> (which must be at least DH1024_KEY_LEN+DIGEST_LEN
+ * bytes).
+ */
+static crypt_path_t *
+create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
+{
+ crypt_path_t *hop = NULL;
+ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+
+ /* first DH1024_KEY_LEN bytes are g^y from the service. Finish the dh
+ * handshake...*/
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->pending_final_cpath);
+ hop = circ->build_state->pending_final_cpath;
+
+ tor_assert(hop->rend_dh_handshake_state);
+ if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
+ (char*)rend_cell_body, DH1024_KEY_LEN,
+ keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
+ log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ goto err;
+ }
+ /* ... and set up cpath. */
+ if (circuit_init_cpath_crypto(hop,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 0, 0) < 0)
+ goto err;
+
+ /* Check whether the digest is right... */
+ if (tor_memneq(keys, rend_cell_body+DH1024_KEY_LEN, DIGEST_LEN)) {
+ log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
+ goto err;
+ }
+
+ /* clean up the crypto stuff we just made */
+ crypto_dh_free(hop->rend_dh_handshake_state);
+ hop->rend_dh_handshake_state = NULL;
+
+ goto done;
+
+ err:
+ hop = NULL;
+
+ done:
+ memwipe(keys, 0, sizeof(keys));
+ return hop;
+}
+
+/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
+ * <b>circ</b> ready for use to transfer HS relay cells. */
+static void
+finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
+ int is_service_side)
+{
+ tor_assert(circ);
+ tor_assert(hop);
+
+ /* Notify the circuit state machine that we are splicing this circuit */
+ int new_circ_purpose = is_service_side ?
+ CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
+ circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
+
+ /* All is well. Extend the circuit. */
+ hop->state = CPATH_STATE_OPEN;
+ /* Set the windows to default. */
+ hop->package_window = circuit_initial_package_window();
+ hop->deliver_window = CIRCWINDOW_START;
+
+ /* Now that this circuit has finished connecting to its destination,
+ * make sure circuit_get_open_circ_or_launch is willing to return it
+ * so we can actually use it. */
+ circ->hs_circ_has_timed_out = 0;
+
+ /* Append the hop to the cpath of this circuit */
+ onion_append_to_cpath(&circ->cpath, hop);
+
+ /* In legacy code, 'pending_final_cpath' points to the final hop we just
+ * appended to the cpath. We set the original pointer to NULL so that we
+ * don't double free it. */
+ if (circ->build_state) {
+ circ->build_state->pending_final_cpath = NULL;
+ }
+
+ /* Finally, mark circuit as ready to be used for client streams */
+ if (!is_service_side) {
+ circuit_try_attaching_streams(circ);
+ }
+}
+
+/* For a given circuit and a service introduction point object, register the
+ * intro circuit to the circuitmap. This supports legacy intro point. */
+static void
+register_intro_circ(const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ)
+{
+ tor_assert(ip);
+ tor_assert(circ);
+
+ if (ip->base.is_only_legacy) {
+ hs_circuitmap_register_intro_circ_v2_service_side(circ,
+ ip->legacy_key_digest);
+ } else {
+ hs_circuitmap_register_intro_circ_v3_service_side(circ,
+ &ip->auth_key_kp.pubkey);
+ }
+}
+
+/* Return the number of opened introduction circuit for the given circuit that
+ * is matching its identity key. */
+static unsigned int
+count_opened_desc_intro_point_circuits(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ const circuit_t *circ;
+ const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc == NULL) {
+ continue;
+ }
+ circ = TO_CIRCUIT(ocirc);
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO);
+ /* Having a circuit not for the requested service is really bad. */
+ tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk,
+ &ocirc->hs_ident->identity_pk));
+ /* Only count opened circuit and skip circuit that will be closed. */
+ if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) {
+ count++;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ return count;
+}
+
+/* From a given service, rendezvous cookie and handshake info, create a
+ * rendezvous point circuit identifier. This can't fail. */
+STATIC hs_ident_circuit_t *
+create_rp_circuit_identifier(const hs_service_t *service,
+ const uint8_t *rendezvous_cookie,
+ const curve25519_public_key_t *server_pk,
+ const hs_ntor_rend_cell_keys_t *keys)
+{
+ hs_ident_circuit_t *ident;
+ uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ tor_assert(service);
+ tor_assert(rendezvous_cookie);
+ tor_assert(server_pk);
+ tor_assert(keys);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_RENDEZVOUS);
+ /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */
+ memcpy(ident->rendezvous_cookie, rendezvous_cookie,
+ sizeof(ident->rendezvous_cookie));
+ /* Build the HANDSHAKE_INFO which looks like this:
+ * SERVER_PK [32 bytes]
+ * AUTH_INPUT_MAC [32 bytes]
+ */
+ memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN);
+ memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac,
+ DIGEST256_LEN);
+ tor_assert(sizeof(ident->rendezvous_handshake_info) ==
+ sizeof(handshake_info));
+ memcpy(ident->rendezvous_handshake_info, handshake_info,
+ sizeof(ident->rendezvous_handshake_info));
+ /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */
+ tor_assert(sizeof(ident->rendezvous_ntor_key_seed) ==
+ sizeof(keys->ntor_key_seed));
+ memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed,
+ sizeof(ident->rendezvous_ntor_key_seed));
+ return ident;
+}
+
+/* From a given service and service intro point, create an introduction point
+ * circuit identifier. This can't fail. */
+static hs_ident_circuit_t *
+create_intro_circuit_identifier(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_ident_circuit_t *ident;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ ident = hs_ident_circuit_new(&service->keys.identity_pk,
+ HS_IDENT_CIRCUIT_INTRO);
+ ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
+
+ return ident;
+}
+
+/* For a given introduction point and an introduction circuit, send the
+ * ESTABLISH_INTRO cell. The service object is used for logging. This can fail
+ * and if so, the circuit is closed and the intro point object is flagged
+ * that the circuit is not established anymore which is important for the
+ * retry mechanism. */
+static void
+send_establish_intro(const hs_service_t *service,
+ hs_service_intro_point_t *ip, origin_circuit_t *circ)
+{
+ ssize_t cell_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+
+ /* Encode establish intro cell. */
+ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce,
+ ip, payload);
+ if (cell_len < 0) {
+ log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s "
+ "on circuit %u. Closing circuit.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+
+ /* Send the cell on the circuit. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_ESTABLISH_INTRO,
+ (char *) payload, cell_len,
+ circ->cpath->prev) < 0) {
+ log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s "
+ "on circuit %u.",
+ safe_str_client(service->onion_address),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* On error, the circuit has been closed. */
+ goto done;
+ }
+
+ /* Record the attempt to use this circuit. */
+ pathbias_count_use_attempt(circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* Return a string constant describing the anonymity of service. */
+static const char *
+get_service_anonymity_string(const hs_service_t *service)
+{
+ if (service->config.is_single_onion) {
+ return "single onion";
+ } else {
+ return "hidden";
+ }
+}
+
+/* For a given service, the ntor onion key and a rendezvous cookie, launch a
+ * circuit to the rendezvous point specified by the link specifiers. On
+ * success, a circuit identifier is attached to the circuit with the needed
+ * data. This function will try to open a circuit for a maximum value of
+ * MAX_REND_FAILURES then it will give up. */
+static void
+launch_rendezvous_point_circuit(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ const hs_cell_introduce2_data_t *data)
+{
+ int circ_needs_uptime;
+ time_t now = time(NULL);
+ extend_info_t *info = NULL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(data);
+
+ circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports);
+
+ /* Get the extend info data structure for the chosen rendezvous point
+ * specified by the given link specifiers. */
+ info = hs_get_extend_info_from_lspecs(data->link_specifiers,
+ &data->onion_pk,
+ service->config.is_single_onion);
+ if (info == NULL) {
+ /* We are done here, we can't extend to the rendezvous point.
+ * If you're running an IPv6-only v3 single onion service on 0.3.2 or with
+ * 0.3.2 clients, and somehow disable the option check, it will fail here.
+ */
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Not enough info to open a circuit to a rendezvous point for "
+ "%s service %s.",
+ get_service_anonymity_string(service),
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+
+ for (int i = 0; i < MAX_REND_FAILURES; i++) {
+ int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
+ if (circ_needs_uptime) {
+ circ_flags |= CIRCLAUNCH_NEED_UPTIME;
+ }
+ /* Firewall and policies are checked when getting the extend info. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info,
+ circ_flags);
+ if (circ != NULL) {
+ /* Stop retrying, we have a circuit! */
+ break;
+ }
+ }
+ if (circ == NULL) {
+ log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s "
+ "for %s service %s",
+ safe_str_client(extend_info_describe(info)),
+ get_service_anonymity_string(service),
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
+ "for %s service %s",
+ safe_str_client(extend_info_describe(info)),
+ safe_str_client(hex_str((const char *) data->rendezvous_cookie,
+ REND_COOKIE_LEN)),
+ get_service_anonymity_string(service),
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state);
+ /* Rendezvous circuit have a specific timeout for the time spent on trying
+ * to connect to the rendezvous point. */
+ circ->build_state->expiry_time = now + MAX_REND_TIMEOUT;
+
+ /* Create circuit identifier and key material. */
+ {
+ hs_ntor_rend_cell_keys_t keys;
+ curve25519_keypair_t ephemeral_kp;
+ /* No need for extra strong, this is only for this circuit life time. This
+ * key will be used for the RENDEZVOUS1 cell that will be sent on the
+ * circuit once opened. */
+ curve25519_keypair_generate(&ephemeral_kp, 0);
+ if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey,
+ &ip->enc_key_kp,
+ &ephemeral_kp, &data->client_pk,
+ &keys) < 0) {
+ /* This should not really happened but just in case, don't make tor
+ * freak out, close the circuit and move on. */
+ log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ goto end;
+ }
+ circ->hs_ident = create_rp_circuit_identifier(service,
+ data->rendezvous_cookie,
+ &ephemeral_kp.pubkey, &keys);
+ memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp));
+ memwipe(&keys, 0, sizeof(keys));
+ tor_assert(circ->hs_ident);
+ }
+
+ end:
+ extend_info_free(info);
+}
+
+/* Return true iff the given service rendezvous circuit circ is allowed for a
+ * relaunch to the rendezvous point. */
+static int
+can_relaunch_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* XXX: Retrying under certain condition. This is related to #22455. */
+
+ /* Avoid to relaunch twice a circuit to the same rendezvous point at the
+ * same time. */
+ if (circ->hs_service_side_rend_circ_has_been_relaunched) {
+ log_info(LD_REND, "Rendezvous circuit to %s has already been retried. "
+ "Skipping retry.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)));
+ goto disallow;
+ }
+
+ /* We check failure_count >= hs_get_service_max_rend_failures()-1 below, and
+ * the -1 is because we increment the failure count for our current failure
+ * *after* this clause. */
+ int max_rend_failures = hs_get_service_max_rend_failures() - 1;
+
+ /* A failure count that has reached maximum allowed or circuit that expired,
+ * we skip relaunching. */
+ if (circ->build_state->failure_count > max_rend_failures ||
+ circ->build_state->expiry_time <= time(NULL)) {
+ log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has "
+ "failed with %d attempts and expiry time %ld. "
+ "Giving up building.",
+ safe_str_client(
+ extend_info_describe(circ->build_state->chosen_exit)),
+ circ->build_state->failure_count,
+ (long int) circ->build_state->expiry_time);
+ goto disallow;
+ }
+
+ /* Allowed to relaunch. */
+ return 1;
+ disallow:
+ return 0;
+}
+
+/* Retry the rendezvous point of circ by launching a new circuit to it. */
+static void
+retry_service_rendezvous_point(const origin_circuit_t *circ)
+{
+ int flags = 0;
+ origin_circuit_t *new_circ;
+ cpath_build_state_t *bstate;
+
+ tor_assert(circ);
+ /* This is initialized when allocating an origin circuit. */
+ tor_assert(circ->build_state);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Ease our life. */
+ bstate = circ->build_state;
+
+ log_info(LD_REND, "Retrying rendezvous point circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+
+ /* Get the current build state flags for the next circuit. */
+ flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0;
+ flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0;
+ flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0;
+
+ /* We do NOT add the onehop tunnel flag even though it might be a single
+ * onion service. The reason is that if we failed once to connect to the RP
+ * with a direct connection, we consider that chances are that we will fail
+ * again so try a 3-hop circuit and hope for the best. Because the service
+ * has no anonymity (single onion), this change of behavior won't affect
+ * security directly. */
+
+ new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
+ bstate->chosen_exit, flags);
+ if (new_circ == NULL) {
+ log_warn(LD_REND, "Failed to launch rendezvous circuit to %s",
+ safe_str_client(extend_info_describe(bstate->chosen_exit)));
+ goto done;
+ }
+
+ /* Transfer build state information to the new circuit state in part to
+ * catch any other failures. */
+ new_circ->build_state->failure_count = bstate->failure_count+1;
+ new_circ->build_state->expiry_time = bstate->expiry_time;
+ new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident);
+
+ done:
+ return;
+}
+
+/* Add all possible link specifiers in node to lspecs.
+ * legacy ID is mandatory thus MUST be present in node. If the primary address
+ * is not IPv4, log a BUG() warning, and return an empty smartlist.
+ * Includes ed25519 id and IPv6 link specifiers if present in the node. */
+static void
+get_lspecs_from_node(const node_t *node, smartlist_t *lspecs)
+{
+ link_specifier_t *ls;
+ tor_addr_port_t ap;
+
+ tor_assert(node);
+ tor_assert(lspecs);
+
+ /* Get the relay's IPv4 address. */
+ node_get_prim_orport(node, &ap);
+
+ /* We expect the node's primary address to be a valid IPv4 address.
+ * This conforms to the protocol, which requires either an IPv4 or IPv6
+ * address (or both). */
+ if (BUG(!tor_addr_is_v4(&ap.addr)) ||
+ BUG(!tor_addr_port_is_valid_ap(&ap, 0))) {
+ return;
+ }
+
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_IPV4);
+ link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ap.addr));
+ link_specifier_set_un_ipv4_port(ls, ap.port);
+ /* Four bytes IPv4 and two bytes port. */
+ link_specifier_set_ls_len(ls, sizeof(ap.addr.addr.in_addr) +
+ sizeof(ap.port));
+ smartlist_add(lspecs, ls);
+
+ /* Legacy ID is mandatory and will always be present in node. */
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_LEGACY_ID);
+ memcpy(link_specifier_getarray_un_legacy_id(ls), node->identity,
+ link_specifier_getlen_un_legacy_id(ls));
+ link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls));
+ smartlist_add(lspecs, ls);
+
+ /* ed25519 ID is only included if the node has it. */
+ if (!ed25519_public_key_is_zero(&node->ed25519_id)) {
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_ED25519_ID);
+ memcpy(link_specifier_getarray_un_ed25519_id(ls), &node->ed25519_id,
+ link_specifier_getlen_un_ed25519_id(ls));
+ link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
+ smartlist_add(lspecs, ls);
+ }
+
+ /* Check for IPv6. If so, include it as well. */
+ if (node_has_ipv6_orport(node)) {
+ ls = link_specifier_new();
+ node_get_pref_ipv6_orport(node, &ap);
+ link_specifier_set_ls_type(ls, LS_IPV6);
+ size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+ const uint8_t *in6_addr = tor_addr_to_in6_addr8(&ap.addr);
+ uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+ memcpy(ipv6_array, in6_addr, addr_len);
+ link_specifier_set_un_ipv6_port(ls, ap.port);
+ /* Sixteen bytes IPv6 and two bytes port. */
+ link_specifier_set_ls_len(ls, addr_len + sizeof(ap.port));
+ smartlist_add(lspecs, ls);
+ }
+}
+
+/* Using the given descriptor intro point ip, the node of the
+ * rendezvous point rp_node and the service's subcredential, populate the
+ * already allocated intro1_data object with the needed key material and link
+ * specifiers.
+ *
+ * If rp_node has an invalid primary address, intro1_data->link_specifiers
+ * will be an empty list. Otherwise, this function can't fail. The ip
+ * MUST be a valid object containing the needed keys and authentication
+ * method. */
+static void
+setup_introduce1_data(const hs_desc_intro_point_t *ip,
+ const node_t *rp_node,
+ const uint8_t *subcredential,
+ hs_cell_introduce1_data_t *intro1_data)
+{
+ smartlist_t *rp_lspecs;
+
+ tor_assert(ip);
+ tor_assert(rp_node);
+ tor_assert(subcredential);
+ tor_assert(intro1_data);
+
+ /* Build the link specifiers from the extend information of the rendezvous
+ * circuit that we've picked previously. */
+ rp_lspecs = smartlist_new();
+ get_lspecs_from_node(rp_node, rp_lspecs);
+
+ /* Populate the introduce1 data object. */
+ memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t));
+ if (ip->legacy.key != NULL) {
+ intro1_data->is_legacy = 1;
+ intro1_data->legacy_key = ip->legacy.key;
+ }
+ intro1_data->auth_pk = &ip->auth_key_cert->signed_key;
+ intro1_data->enc_pk = &ip->enc_key;
+ intro1_data->subcredential = subcredential;
+ intro1_data->onion_pk = node_get_curve25519_onion_key(rp_node);
+ intro1_data->link_specifiers = rp_lspecs;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* Return an introduction point circuit matching the given intro point object.
+ * NULL is returned is no such circuit can be found. */
+origin_circuit_t *
+hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip)
+{
+ tor_assert(ip);
+
+ if (ip->base.is_only_legacy) {
+ return hs_circuitmap_get_intro_circ_v2_service_side(ip->legacy_key_digest);
+ } else {
+ return hs_circuitmap_get_intro_circ_v3_service_side(
+ &ip->auth_key_kp.pubkey);
+ }
+}
+
+/* Called when we fail building a rendezvous circuit at some point other than
+ * the last hop: launches a new circuit to the same rendezvous point. This
+ * supports legacy service.
+ *
+ * We currently relaunch connections to rendezvous points if:
+ * - A rendezvous circuit timed out before connecting to RP.
+ * - The rendezvous circuit failed to connect to the RP.
+ *
+ * We avoid relaunching a connection to this rendezvous point if:
+ * - We have already tried MAX_REND_FAILURES times to connect to this RP,
+ * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT
+ * seconds, or
+ * - We've already retried this specific rendezvous circuit.
+ */
+void
+hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Check if we are allowed to relaunch to the rendezvous point of circ. */
+ if (!can_relaunch_service_rendezvous_point(circ)) {
+ goto done;
+ }
+
+ /* Flag the circuit that we are relaunching, to avoid to relaunch twice a
+ * circuit to the same rendezvous point at the same time. */
+ circ->hs_service_side_rend_circ_has_been_relaunched = 1;
+
+ /* Legacy services don't have a hidden service ident. */
+ if (circ->hs_ident) {
+ retry_service_rendezvous_point(circ);
+ } else {
+ rend_service_relaunch_rendezvous(circ);
+ }
+
+ done:
+ return;
+}
+
+/* For a given service and a service intro point, launch a circuit to the
+ * extend info ei. If the service is a single onion, a one-hop circuit will be
+ * requested. Return 0 if the circuit was successfully launched and tagged
+ * with the correct identifier. On error, a negative value is returned. */
+int
+hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei)
+{
+ /* Standard flags for introduction circuit. */
+ int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+ origin_circuit_t *circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(ei);
+
+ /* Update circuit flags in case of a single onion service that requires a
+ * direct connection. */
+ if (service->config.is_single_onion) {
+ circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ }
+
+ log_info(LD_REND, "Launching a circuit to intro point %s for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+
+ /* Note down the launch for the retry period. Even if the circuit fails to
+ * be launched, we still want to respect the retry period to avoid stress on
+ * the circuit subsystem. */
+ service->state.num_intro_circ_launched++;
+ circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+ ei, circ_flags);
+ if (circ == NULL) {
+ goto end;
+ }
+
+ /* Setup the circuit identifier and attach it to it. */
+ circ->hs_ident = create_intro_circuit_identifier(service, ip);
+ tor_assert(circ->hs_ident);
+ /* Register circuit in the global circuitmap. */
+ register_intro_circ(ip, circ);
+
+ /* Success. */
+ ret = 0;
+ end:
+ return ret;
+}
+
+/* Called when a service introduction point circuit is done building. Given
+ * the service and intro point object, this function will send the
+ * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the
+ * circuit has been repurposed to General because we already have too many
+ * opened. */
+int
+hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ)
+{
+ int ret = 0;
+ unsigned int num_intro_circ, num_needed_circ;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(desc);
+ tor_assert(circ);
+
+ /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already
+ * established introduction circuits */
+ num_intro_circ = count_opened_desc_intro_point_circuits(service, desc);
+ num_needed_circ = service->config.num_intro_points;
+ if (num_intro_circ > num_needed_circ) {
+ /* There are too many opened valid intro circuit for what the service
+ * needs so repurpose this one. */
+
+ /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just
+ * closes the circuit. I have NO idea why it does that so it hasn't been
+ * added here. I can only assume in case our ExcludeNodes list changes but
+ * in that case, all circuit are flagged unusable (config.c). --dgoulet */
+
+ log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we "
+ "have enough for service %s. Repurposing "
+ "it to general and leaving internal.",
+ safe_str_client(service->onion_address));
+ tor_assert(circ->build_state->is_internal);
+ /* Remove it from the circuitmap. */
+ hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
+ /* Cleaning up the hidden service identifier and repurpose. */
+ hs_ident_circuit_free(circ->hs_ident);
+ circ->hs_ident = NULL;
+ if (circuit_should_use_vanguards(TO_CIRCUIT(circ)->purpose))
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_HS_VANGUARDS);
+ else
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL);
+
+ /* Inform that this circuit just opened for this new purpose. */
+ circuit_has_opened(circ);
+ /* This return value indicate to the caller that the IP object should be
+ * removed from the service because it's corresponding circuit has just
+ * been repurposed. */
+ ret = 1;
+ goto done;
+ }
+
+ log_info(LD_REND, "Introduction circuit %u established for service %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call
+ * makes sure the circuit gets closed. */
+ send_establish_intro(service, ip, circ);
+
+ done:
+ return ret;
+}
+
+/* Called when a service rendezvous point circuit is done building. Given the
+ * service and the circuit, this function will send a RENDEZVOUS1 cell on the
+ * circuit using the information in the circuit identifier. If the cell can't
+ * be sent, the circuit is closed. */
+void
+hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ)
+{
+ size_t payload_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(circ->hs_ident);
+
+ /* Some useful logging. */
+ log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN),
+ safe_str_client(service->onion_address));
+ circuit_log_path(LOG_INFO, LD_REND, circ);
+
+ /* This can't fail. */
+ payload_len = hs_cell_build_rendezvous1(
+ circ->hs_ident->rendezvous_cookie,
+ sizeof(circ->hs_ident->rendezvous_cookie),
+ circ->hs_ident->rendezvous_handshake_info,
+ sizeof(circ->hs_ident->rendezvous_handshake_info),
+ payload);
+
+ /* Pad the payload with random bytes so it matches the size of a legacy cell
+ * which is normally always bigger. Also, the size of a legacy cell is
+ * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */
+ if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) {
+ crypto_rand((char *) payload + payload_len,
+ HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len);
+ payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE;
+ }
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_RENDEZVOUS1,
+ (const char *) payload, payload_len,
+ circ->cpath->prev) < 0) {
+ /* On error, circuit is closed. */
+ log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Setup end-to-end rendezvous circuit between the client and us. */
+ if (hs_circuit_setup_e2e_rend_circ(circ,
+ circ->hs_ident->rendezvous_ntor_key_seed,
+ sizeof(circ->hs_ident->rendezvous_ntor_key_seed),
+ 1) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
+ goto done;
+ }
+
+ done:
+ memwipe(payload, 0, sizeof(payload));
+}
+
+/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle
+ * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the
+ * given introduction circuit circ. The service is only used for logging
+ * purposes. Return 0 on success else a negative value. */
+int
+hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(service);
+ tor_assert(ip);
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ goto done;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. For a legacy node, it's an empty payload so as long as we
+ * have the cell, we are good. */
+ if (!ip->base.is_only_legacy &&
+ hs_cell_parse_intro_established(payload, payload_len) < 0) {
+ log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on "
+ "circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+
+ /* Switch the purpose to a fully working intro point. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO);
+ /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the
+ * circuit so update our pathbias subsystem. */
+ pathbias_mark_use_success(circ);
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* We just received an INTRODUCE2 cell on the established introduction circuit
+ * circ. Handle the INTRODUCE2 payload of size payload_len for the given
+ * circuit and service. This cell is associated with the intro point object ip
+ * and the subcredential. Return 0 on success else a negative value. */
+int
+hs_circ_handle_introduce2(const hs_service_t *service,
+ const origin_circuit_t *circ,
+ hs_service_intro_point_t *ip,
+ const uint8_t *subcredential,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ time_t elapsed;
+ hs_cell_introduce2_data_t data;
+
+ tor_assert(service);
+ tor_assert(circ);
+ tor_assert(ip);
+ tor_assert(subcredential);
+ tor_assert(payload);
+
+ /* Populate the data structure with everything we need for the cell to be
+ * parsed, decrypted and key material computed correctly. */
+ data.auth_pk = &ip->auth_key_kp.pubkey;
+ data.enc_kp = &ip->enc_key_kp;
+ data.subcredential = subcredential;
+ data.payload = payload;
+ data.payload_len = payload_len;
+ data.link_specifiers = smartlist_new();
+ data.replay_cache = ip->replay_cache;
+
+ if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
+ goto done;
+ }
+
+ /* Check whether we've seen this REND_COOKIE before to detect repeats. */
+ if (replaycache_add_test_and_elapsed(
+ service->state.replay_cache_rend_cookie,
+ data.rendezvous_cookie, sizeof(data.rendezvous_cookie),
+ &elapsed)) {
+ /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE
+ * as its previous one if its intro circ times out while in state
+ * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first
+ * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2
+ * cell), we are already trying to connect to that rend point (and may
+ * have already succeeded); drop this cell. */
+ log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE "
+ "field %ld seconds ago. Dropping cell.",
+ (long int) elapsed);
+ goto done;
+ }
+
+ /* At this point, we just confirmed that the full INTRODUCE2 cell is valid
+ * so increment our counter that we've seen one on this intro point. */
+ ip->introduce2_count++;
+
+ /* Launch rendezvous circuit with the onion key and rend cookie. */
+ launch_rendezvous_point_circuit(service, ip, &data);
+ /* Success. */
+ ret = 0;
+
+ done:
+ SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec,
+ link_specifier_free(lspec));
+ smartlist_free(data.link_specifiers);
+ memwipe(&data, 0, sizeof(data));
+ return ret;
+}
+
+/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
+ * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
+ * serve as a rendezvous end-to-end circuit between the client and the
+ * service. If <b>is_service_side</b> is set, then we are the hidden service
+ * and the other side is the client.
+ *
+ * Return 0 if the operation went well; in case of error return -1. */
+int
+hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
+ is_service_side))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
+ is_service_side);
+ if (!hop) {
+ log_warn(LD_REND, "Couldn't get v3 %s cpath!",
+ is_service_side ? "service-side" : "client-side");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, is_service_side);
+
+ return 0;
+}
+
+/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
+ * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
+ * extend the crypt path of <b>circ</b> so that the hidden service is on the
+ * other side. */
+int
+hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body)
+{
+
+ if (BUG(!circuit_purpose_is_correct_for_rend(
+ TO_CIRCUIT(circ)->purpose, 0))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
+ if (!hop) {
+ log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, 0);
+
+ return 0;
+}
+
+/* Given the introduction circuit intro_circ, the rendezvous circuit
+ * rend_circ, a descriptor intro point object ip and the service's
+ * subcredential, send an INTRODUCE1 cell on intro_circ.
+ *
+ * This will also setup the circuit identifier on rend_circ containing the key
+ * material for the handshake and e2e encryption. Return 0 on success else
+ * negative value. Because relay_send_command_from_edge() closes the circuit
+ * on error, it is possible that intro_circ is closed on error. */
+int
+hs_circ_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ,
+ const hs_desc_intro_point_t *ip,
+ const uint8_t *subcredential)
+{
+ int ret = -1;
+ ssize_t payload_len;
+ uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+ hs_cell_introduce1_data_t intro1_data;
+
+ tor_assert(intro_circ);
+ tor_assert(rend_circ);
+ tor_assert(ip);
+ tor_assert(subcredential);
+
+ /* It is undefined behavior in hs_cell_introduce1_data_clear() if intro1_data
+ * has been declared on the stack but not initialized. Here, we set it to 0.
+ */
+ memset(&intro1_data, 0, sizeof(hs_cell_introduce1_data_t));
+
+ /* This takes various objects in order to populate the introduce1 data
+ * object which is used to build the content of the cell. */
+ const node_t *exit_node = build_state_get_exit_node(rend_circ->build_state);
+ if (exit_node == NULL) {
+ log_info(LD_REND, "Unable to get rendezvous point for circuit %u. "
+ "Failing.", TO_CIRCUIT(intro_circ)->n_circ_id);
+ goto done;
+ }
+ setup_introduce1_data(ip, exit_node, subcredential, &intro1_data);
+ /* If we didn't get any link specifiers, it's because our node was
+ * bad. */
+ if (BUG(!intro1_data.link_specifiers) ||
+ !smartlist_len(intro1_data.link_specifiers)) {
+ log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on "
+ "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id);
+ goto done;
+ }
+
+ /* Final step before we encode a cell, we setup the circuit identifier which
+ * will generate both the rendezvous cookie and client keypair for this
+ * connection. Those are put in the ident. */
+ intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie;
+ intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp;
+
+ memcpy(intro_circ->hs_ident->rendezvous_cookie,
+ rend_circ->hs_ident->rendezvous_cookie,
+ sizeof(intro_circ->hs_ident->rendezvous_cookie));
+
+ /* From the introduce1 data object, this will encode the INTRODUCE1 cell
+ * into payload which is then ready to be sent as is. */
+ payload_len = hs_cell_build_introduce1(&intro1_data, payload);
+ if (BUG(payload_len < 0)) {
+ goto done;
+ }
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ),
+ RELAY_COMMAND_INTRODUCE1,
+ (const char *) payload, payload_len,
+ intro_circ->cpath->prev) < 0) {
+ /* On error, circuit is closed. */
+ log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.",
+ TO_CIRCUIT(intro_circ)->n_circ_id);
+ goto done;
+ }
+
+ /* Success. */
+ ret = 0;
+ goto done;
+
+ done:
+ hs_cell_introduce1_data_clear(&intro1_data);
+ memwipe(payload, 0, sizeof(payload));
+ return ret;
+}
+
+/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On
+ * success, 0 is returned else -1 and the circuit is marked for close. */
+int
+hs_circ_send_establish_rendezvous(origin_circuit_t *circ)
+{
+ ssize_t cell_len = 0;
+ uint8_t cell[RELAY_PAYLOAD_SIZE] = {0};
+
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+ log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u",
+ TO_CIRCUIT(circ)->n_circ_id);
+
+ /* Set timestamp_dirty, because circuit_expire_building expects it,
+ * and the rend cookie also means we've used the circ. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+ /* We've attempted to use this circuit. Probe it if we fail */
+ pathbias_count_use_attempt(circ);
+
+ /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can
+ * complete the handshake when receiving the acknowledgement. */
+ crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN);
+ /* Generate the client keypair. No need to be extra strong, not long term */
+ curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0);
+
+ cell_len =
+ hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie,
+ cell);
+ if (BUG(cell_len < 0)) {
+ goto err;
+ }
+
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
+ (const char *) cell, cell_len,
+ circ->cpath->prev) < 0) {
+ /* Circuit has been marked for close */
+ log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on "
+ "circuit %u", TO_CIRCUIT(circ)->n_circ_id);
+ memwipe(cell, 0, cell_len);
+ goto err;
+ }
+
+ memwipe(cell, 0, cell_len);
+ return 0;
+ err:
+ return -1;
+}
+
+/* We are about to close or free this <b>circ</b>. Clean it up from any
+ * related HS data structures. This function can be called multiple times
+ * safely for the same circuit. */
+void
+hs_circ_cleanup(circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* If it's a service-side intro circ, notify the HS subsystem for the intro
+ * point circuit closing so it can be dealt with cleanly. */
+ if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_INTRO) {
+ hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ));
+ }
+
+ /* Clear HS circuitmap token for this circ (if any). Very important to be
+ * done after the HS subsystem has been notified of the close else the
+ * circuit will not be found.
+ *
+ * We do this at the close if possible because from that point on, the
+ * circuit is good as dead. We can't rely on removing it in the circuit
+ * free() function because we open a race window between the close and free
+ * where we can't register a new circuit for the same intro point. */
+ if (circ->hs_token) {
+ hs_circuitmap_remove_circuit(circ);
+ }
+}
diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h
new file mode 100644
index 0000000000..425070f4ca
--- /dev/null
+++ b/src/feature/hs/hs_circuit.h
@@ -0,0 +1,75 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.h
+ * \brief Header file containing circuit data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CIRCUIT_H
+#define TOR_HS_CIRCUIT_H
+
+#include "or/or.h"
+#include "lib/crypt_ops/crypto_ed25519.h"
+
+#include "or/hs_service.h"
+
+/* Cleanup function when the circuit is closed or/and freed. */
+void hs_circ_cleanup(circuit_t *circ);
+
+/* Circuit API. */
+int hs_circ_service_intro_has_opened(hs_service_t *service,
+ hs_service_intro_point_t *ip,
+ const hs_service_descriptor_t *desc,
+ origin_circuit_t *circ);
+void hs_circ_service_rp_has_opened(const hs_service_t *service,
+ origin_circuit_t *circ);
+int hs_circ_launch_intro_point(hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ extend_info_t *ei);
+int hs_circ_launch_rendezvous_point(const hs_service_t *service,
+ const curve25519_public_key_t *onion_key,
+ const uint8_t *rendezvous_cookie);
+void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ);
+
+origin_circuit_t *hs_circ_service_get_intro_circ(
+ const hs_service_intro_point_t *ip);
+
+/* Cell API. */
+int hs_circ_handle_intro_established(const hs_service_t *service,
+ const hs_service_intro_point_t *ip,
+ origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_circ_handle_introduce2(const hs_service_t *service,
+ const origin_circuit_t *circ,
+ hs_service_intro_point_t *ip,
+ const uint8_t *subcredential,
+ const uint8_t *payload, size_t payload_len);
+int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ,
+ const hs_desc_intro_point_t *ip,
+ const uint8_t *subcredential);
+int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
+
+/* e2e circuit API. */
+
+int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+ const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ int is_service_side);
+int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body);
+
+#ifdef HS_CIRCUIT_PRIVATE
+
+STATIC hs_ident_circuit_t *
+create_rp_circuit_identifier(const hs_service_t *service,
+ const uint8_t *rendezvous_cookie,
+ const curve25519_public_key_t *server_pk,
+ const hs_ntor_rend_cell_keys_t *keys);
+
+#endif /* defined(HS_CIRCUIT_PRIVATE) */
+
+#endif /* !defined(TOR_HS_CIRCUIT_H) */
+
diff --git a/src/feature/hs/hs_circuitmap.c b/src/feature/hs/hs_circuitmap.c
new file mode 100644
index 0000000000..c4bf9fab43
--- /dev/null
+++ b/src/feature/hs/hs_circuitmap.c
@@ -0,0 +1,585 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.c
+ *
+ * \brief Hidden service circuitmap: A hash table that maps binary tokens to
+ * introduction and rendezvous circuits; it's used:
+ * (a) by relays acting as intro points and rendezvous points
+ * (b) by hidden services to find intro and rend circuits and
+ * (c) by HS clients to find rendezvous circuits.
+ **/
+
+#define HS_CIRCUITMAP_PRIVATE
+
+#include "or/or.h"
+#include "or/config.h"
+#include "or/circuitlist.h"
+#include "or/hs_circuitmap.h"
+
+#include "or/or_circuit_st.h"
+#include "or/origin_circuit_st.h"
+
+/************************** HS circuitmap code *******************************/
+
+/* This is the hidden service circuitmap. It's a hash table that maps
+ introduction and rendezvous tokens to specific circuits such that given a
+ token it's easy to find the corresponding circuit. */
+static struct hs_circuitmap_ht *the_hs_circuitmap = NULL;
+
+/* This is a helper function used by the hash table code (HT_). It returns 1 if
+ * two circuits have the same HS token. */
+static int
+hs_circuits_have_same_token(const circuit_t *first_circuit,
+ const circuit_t *second_circuit)
+{
+ const hs_token_t *first_token;
+ const hs_token_t *second_token;
+
+ tor_assert(first_circuit);
+ tor_assert(second_circuit);
+
+ first_token = first_circuit->hs_token;
+ second_token = second_circuit->hs_token;
+
+ /* Both circs must have a token */
+ if (BUG(!first_token) || BUG(!second_token)) {
+ return 0;
+ }
+
+ if (first_token->type != second_token->type) {
+ return 0;
+ }
+
+ if (first_token->token_len != second_token->token_len)
+ return 0;
+
+ return tor_memeq(first_token->token,
+ second_token->token,
+ first_token->token_len);
+}
+
+/* This is a helper function for the hash table code (HT_). It hashes a circuit
+ * HS token into an unsigned int for use as a key by the hash table routines.*/
+static inline unsigned int
+hs_circuit_hash_token(const circuit_t *circuit)
+{
+ tor_assert(circuit->hs_token);
+
+ return (unsigned) siphash24g(circuit->hs_token->token,
+ circuit->hs_token->token_len);
+}
+
+/* Register the circuitmap hash table */
+HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct
+ circuit_t, // The name of the element struct,
+ hs_circuitmap_node, // The name of HT_ENTRY member
+ hs_circuit_hash_token, hs_circuits_have_same_token)
+
+HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node,
+ hs_circuit_hash_token, hs_circuits_have_same_token,
+ 0.6, tor_reallocarray, tor_free_)
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global HS circuitmap. Used by unittests. */
+hs_circuitmap_ht *
+get_hs_circuitmap(void)
+{
+ return the_hs_circuitmap;
+}
+
+#endif /* defined(TOR_UNIT_TESTS) */
+
+/****************** HS circuitmap utility functions **************************/
+
+/** Return a new HS token of type <b>type</b> containing <b>token</b>. */
+static hs_token_t *
+hs_token_new(hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ tor_assert(token);
+
+ hs_token_t *hs_token = tor_malloc_zero(sizeof(hs_token_t));
+ hs_token->type = type;
+ hs_token->token_len = token_len;
+ hs_token->token = tor_memdup(token, token_len);
+
+ return hs_token;
+}
+
+#define hs_token_free(val) \
+ FREE_AND_NULL(hs_token_t, hs_token_free_, (val))
+
+/** Free memory allocated by this <b>hs_token</b>. */
+static void
+hs_token_free_(hs_token_t *hs_token)
+{
+ if (!hs_token) {
+ return;
+ }
+
+ tor_free(hs_token->token);
+ tor_free(hs_token);
+}
+
+/** Return the circuit from the circuitmap with token <b>search_token</b>. */
+static circuit_t *
+get_circuit_with_token(hs_token_t *search_token)
+{
+ tor_assert(the_hs_circuitmap);
+
+ /* We use a dummy circuit object for the hash table search routine. */
+ circuit_t search_circ;
+ search_circ.hs_token = search_token;
+ return HT_FIND(hs_circuitmap_ht, the_hs_circuitmap, &search_circ);
+}
+
+/* Helper function that registers <b>circ</b> with <b>token</b> on the HS
+ circuitmap. This function steals reference of <b>token</b>. */
+static void
+hs_circuitmap_register_impl(circuit_t *circ, hs_token_t *token)
+{
+ tor_assert(circ);
+ tor_assert(token);
+ tor_assert(the_hs_circuitmap);
+
+ /* If this circuit already has a token, clear it. */
+ if (circ->hs_token) {
+ hs_circuitmap_remove_circuit(circ);
+ }
+
+ /* Kill old circuits with the same token. We want new intro/rend circuits to
+ take precedence over old ones, so that HSes and clients and reestablish
+ killed circuits without changing the HS token. */
+ {
+ circuit_t *found_circ;
+ found_circ = get_circuit_with_token(token);
+ if (found_circ) {
+ hs_circuitmap_remove_circuit(found_circ);
+ if (!found_circ->marked_for_close) {
+ circuit_mark_for_close(found_circ, END_CIRC_REASON_FINISHED);
+ }
+ }
+ }
+
+ /* Register circuit and token to circuitmap. */
+ circ->hs_token = token;
+ HT_INSERT(hs_circuitmap_ht, the_hs_circuitmap, circ);
+}
+
+/** Helper function: Register <b>circ</b> of <b>type</b> on the HS
+ * circuitmap. Use the HS <b>token</b> as the key to the hash table. If
+ * <b>token</b> is not set, clear the circuit of any HS tokens. */
+static void
+hs_circuitmap_register_circuit(circuit_t *circ,
+ hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ hs_token_t *hs_token = NULL;
+
+ /* Create a new token and register it to the circuitmap */
+ tor_assert(token);
+ hs_token = hs_token_new(type, token_len, token);
+ tor_assert(hs_token);
+ hs_circuitmap_register_impl(circ, hs_token);
+}
+
+/* Helper function for hs_circuitmap_get_origin_circuit() and
+ * hs_circuitmap_get_or_circuit(). Because only circuit_t are indexed in the
+ * circuitmap, this function returns object type so the specialized functions
+ * using this helper can upcast it to the right type.
+ *
+ * Return NULL if not such circuit is found. */
+static circuit_t *
+hs_circuitmap_get_circuit_impl(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *found_circ = NULL;
+
+ tor_assert(the_hs_circuitmap);
+
+ /* Check the circuitmap if we have a circuit with this token */
+ {
+ hs_token_t *search_hs_token = hs_token_new(type, token_len, token);
+ tor_assert(search_hs_token);
+ found_circ = get_circuit_with_token(search_hs_token);
+ hs_token_free(search_hs_token);
+ }
+
+ /* Check that the circuit is useful to us */
+ if (!found_circ ||
+ found_circ->purpose != wanted_circ_purpose ||
+ found_circ->marked_for_close) {
+ return NULL;
+ }
+
+ return found_circ;
+}
+
+/* Helper function: Query circuitmap for origin circuit with <b>token</b> of
+ * size <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose
+ * equal to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked
+ * for close. Return NULL if no such circuit is found. */
+static origin_circuit_t *
+hs_circuitmap_get_origin_circuit(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *circ;
+ tor_assert(token);
+ tor_assert(CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose));
+
+ circ = hs_circuitmap_get_circuit_impl(type, token_len, token,
+ wanted_circ_purpose);
+ if (!circ) {
+ return NULL;
+ }
+
+ tor_assert(CIRCUIT_IS_ORIGIN(circ));
+ return TO_ORIGIN_CIRCUIT(circ);
+}
+
+/* Helper function: Query circuitmap for OR circuit with <b>token</b> of size
+ * <b>token_len</b> and <b>type</b>. Only returns a circuit with purpose equal
+ * to the <b>wanted_circ_purpose</b> parameter and if it is NOT marked for
+ * close. Return NULL if no such circuit is found. */
+static or_circuit_t *
+hs_circuitmap_get_or_circuit(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ circuit_t *circ;
+ tor_assert(token);
+ tor_assert(!CIRCUIT_PURPOSE_IS_ORIGIN(wanted_circ_purpose));
+
+ circ = hs_circuitmap_get_circuit_impl(type, token_len, token,
+ wanted_circ_purpose);
+ if (!circ) {
+ return NULL;
+ }
+
+ tor_assert(CIRCUIT_IS_ORCIRC(circ));
+ return TO_OR_CIRCUIT(circ);
+}
+
+/************** Public circuitmap API ****************************************/
+
+/**** Public relay-side getters: */
+
+/* Public function: Return a v3 introduction circuit to this relay with
+ * <b>auth_key</b>. Return NULL if no such circuit is found in the
+ * circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v3_relay_side(
+ const ed25519_public_key_t *auth_key)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V3_RELAY_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return v2 introduction circuit to this relay with
+ * <b>digest</b>. Return NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return rendezvous circuit to this relay with rendezvous
+ * <b>cookie</b>. Return NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie)
+{
+ return hs_circuitmap_get_or_circuit(HS_TOKEN_REND_RELAY_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_REND_POINT_WAITING);
+}
+
+/** Public relay-side setters: */
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_rend_circ_relay_side(or_circuit_t *circ,
+ const uint8_t *cookie)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_REND_RELAY_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+/* Public function: Register v2 intro circuit with key <b>digest</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v2_relay_side(or_circuit_t *circ,
+ const uint8_t *digest)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ REND_TOKEN_LEN, digest);
+}
+
+/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v3_relay_side(or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V3_RELAY_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey);
+}
+
+/**** Public servide-side getters: */
+
+/* Public function: Return v3 introduction circuit with <b>auth_key</b>
+ * originating from this hidden service. Return NULL if no such circuit is
+ * found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_intro_circ_v3_service_side(const
+ ed25519_public_key_t *auth_key)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Check first for established intro circuits */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_S_INTRO);
+ if (circ) {
+ return circ;
+ }
+
+ /* ...if nothing found, check for pending intro circs */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ return circ;
+}
+
+/* Public function: Return v2 introduction circuit originating from this hidden
+ * service with <b>digest</b>. Return NULL if no such circuit is found in the
+ * circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Check first for established intro circuits */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_S_INTRO);
+ if (circ) {
+ return circ;
+ }
+
+ /* ...if nothing found, check for pending intro circs */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ return circ;
+}
+
+/* Public function: Return rendezvous circuit originating from this hidden
+ * service with rendezvous <b>cookie</b>. Return NULL if no such circuit is
+ * found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie)
+{
+ origin_circuit_t *circ = NULL;
+
+ /* Try to check if we have a connecting circuit. */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_S_CONNECT_REND);
+ if (circ) {
+ return circ;
+ }
+
+ /* Then try for connected circuit. */
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_S_REND_JOINED);
+ return circ;
+}
+
+/* Public function: Return client-side rendezvous circuit with rendezvous
+ * <b>cookie</b>. It will look for circuits with the following purposes:
+
+ * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received
+ * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for
+ * INTRODUCE_ACK from intro point.
+ *
+ * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and
+ * introduce circuit acked. Waiting for RENDEZVOUS2 from service.
+ *
+ * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received
+ * RENDEZVOUS2 from service.
+ *
+ * d) CIRCUIT_PURPOSE_C_ESTABLISH_REND: Rend circuit open but not yet
+ * established.
+ *
+ * Return NULL if no such circuit is found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie)
+{
+ origin_circuit_t *circ = NULL;
+
+ circ = hs_circuitmap_get_established_rend_circ_client_side(cookie);
+ if (circ) {
+ return circ;
+ }
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+ return circ;
+}
+
+/* Public function: Return client-side established rendezvous circuit with
+ * rendezvous <b>cookie</b>. It will look for circuits with the following
+ * purposes:
+ *
+ * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received
+ * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for
+ * INTRODUCE_ACK from intro point.
+ *
+ * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and
+ * introduce circuit acked. Waiting for RENDEZVOUS2 from service.
+ *
+ * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received
+ * RENDEZVOUS2 from service.
+ *
+ * Return NULL if no such circuit is found in the circuitmap. */
+origin_circuit_t *
+hs_circuitmap_get_established_rend_circ_client_side(const uint8_t *cookie)
+{
+ origin_circuit_t *circ = NULL;
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_REND_READY);
+ if (circ) {
+ return circ;
+ }
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+ if (circ) {
+ return circ;
+ }
+
+ circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_C_REND_JOINED);
+ return circ;
+}
+
+/**** Public servide-side setters: */
+
+/* Public function: Register v2 intro circuit with key <b>digest</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v2_service_side(origin_circuit_t *circ,
+ const uint8_t *digest)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ REND_TOKEN_LEN, digest);
+}
+
+/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v3_service_side(origin_circuit_t *circ,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+ ED25519_PUBKEY_LEN, auth_key->pubkey);
+}
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ,
+ const uint8_t *cookie)
+{
+ hs_circuitmap_register_circuit(TO_CIRCUIT(circ),
+ HS_TOKEN_REND_SERVICE_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * client-side circuitmap. */
+void
+hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ,
+ const uint8_t *cookie)
+{
+ circuit_t *circ = TO_CIRCUIT(or_circ);
+ { /* Basic circ purpose sanity checking */
+ tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+ }
+
+ hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE,
+ REND_TOKEN_LEN, cookie);
+}
+
+/**** Misc public functions: */
+
+/** Public function: Remove this circuit from the HS circuitmap. Clear its HS
+ * token, and remove it from the hashtable. */
+void
+hs_circuitmap_remove_circuit(circuit_t *circ)
+{
+ tor_assert(the_hs_circuitmap);
+
+ if (!circ || !circ->hs_token) {
+ return;
+ }
+
+ /* Remove circ from circuitmap */
+ circuit_t *tmp;
+ tmp = HT_REMOVE(hs_circuitmap_ht, the_hs_circuitmap, circ);
+ /* ... and ensure the removal was successful. */
+ if (tmp) {
+ tor_assert(tmp == circ);
+ } else {
+ log_warn(LD_BUG, "Could not find circuit (%u) in circuitmap.",
+ circ->n_circ_id);
+ }
+
+ /* Clear token from circ */
+ hs_token_free(circ->hs_token);
+ circ->hs_token = NULL;
+}
+
+/* Public function: Initialize the global HS circuitmap. */
+void
+hs_circuitmap_init(void)
+{
+ tor_assert(!the_hs_circuitmap);
+
+ the_hs_circuitmap = tor_malloc_zero(sizeof(struct hs_circuitmap_ht));
+ HT_INIT(hs_circuitmap_ht, the_hs_circuitmap);
+}
+
+/* Public function: Free all memory allocated by the global HS circuitmap. */
+void
+hs_circuitmap_free_all(void)
+{
+ if (the_hs_circuitmap) {
+ HT_CLEAR(hs_circuitmap_ht, the_hs_circuitmap);
+ tor_free(the_hs_circuitmap);
+ }
+}
diff --git a/src/feature/hs/hs_circuitmap.h b/src/feature/hs/hs_circuitmap.h
new file mode 100644
index 0000000000..c39a37c052
--- /dev/null
+++ b/src/feature/hs/hs_circuitmap.h
@@ -0,0 +1,112 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.h
+ * \brief Header file for hs_circuitmap.c.
+ **/
+
+#ifndef TOR_HS_CIRCUITMAP_H
+#define TOR_HS_CIRCUITMAP_H
+
+typedef HT_HEAD(hs_circuitmap_ht, circuit_t) hs_circuitmap_ht;
+
+typedef struct hs_token_t hs_token_t;
+struct or_circuit_t;
+struct origin_circuit_t;
+
+/** Public HS circuitmap API: */
+
+/** Public relay-side API: */
+
+struct or_circuit_t *
+hs_circuitmap_get_intro_circ_v3_relay_side(const
+ ed25519_public_key_t *auth_key);
+struct or_circuit_t *
+hs_circuitmap_get_intro_circ_v2_relay_side(const uint8_t *digest);
+struct or_circuit_t *
+hs_circuitmap_get_rend_circ_relay_side(const uint8_t *cookie);
+
+void hs_circuitmap_register_rend_circ_relay_side(struct or_circuit_t *circ,
+ const uint8_t *cookie);
+void hs_circuitmap_register_intro_circ_v2_relay_side(struct or_circuit_t *circ,
+ const uint8_t *digest);
+void hs_circuitmap_register_intro_circ_v3_relay_side(struct or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key);
+
+/** Public service-side API: */
+
+struct origin_circuit_t *
+hs_circuitmap_get_intro_circ_v3_service_side(const
+ ed25519_public_key_t *auth_key);
+struct origin_circuit_t *
+hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest);
+struct origin_circuit_t *
+hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie);
+struct origin_circuit_t *
+hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie);
+struct origin_circuit_t *
+hs_circuitmap_get_established_rend_circ_client_side(const uint8_t *cookie);
+
+void hs_circuitmap_register_intro_circ_v2_service_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *digest);
+void hs_circuitmap_register_intro_circ_v3_service_side(
+ struct origin_circuit_t *circ,
+ const ed25519_public_key_t *auth_key);
+void hs_circuitmap_register_rend_circ_service_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *cookie);
+void hs_circuitmap_register_rend_circ_client_side(
+ struct origin_circuit_t *circ,
+ const uint8_t *cookie);
+
+void hs_circuitmap_remove_circuit(struct circuit_t *circ);
+
+void hs_circuitmap_init(void);
+void hs_circuitmap_free_all(void);
+
+#ifdef HS_CIRCUITMAP_PRIVATE
+
+/** Represents the type of HS token. */
+typedef enum {
+ /** A rendezvous cookie on a relay (128bit)*/
+ HS_TOKEN_REND_RELAY_SIDE,
+ /** A v2 introduction point pubkey on a relay (160bit) */
+ HS_TOKEN_INTRO_V2_RELAY_SIDE,
+ /** A v3 introduction point pubkey on a relay (256bit) */
+ HS_TOKEN_INTRO_V3_RELAY_SIDE,
+
+ /** A rendezvous cookie on a hidden service (128bit)*/
+ HS_TOKEN_REND_SERVICE_SIDE,
+ /** A v2 introduction point pubkey on a hidden service (160bit) */
+ HS_TOKEN_INTRO_V2_SERVICE_SIDE,
+ /** A v3 introduction point pubkey on a hidden service (256bit) */
+ HS_TOKEN_INTRO_V3_SERVICE_SIDE,
+
+ /** A rendezvous cookie on the client side (128bit) */
+ HS_TOKEN_REND_CLIENT_SIDE,
+} hs_token_type_t;
+
+/** Represents a token used in the HS protocol. Each such token maps to a
+ * specific introduction or rendezvous circuit. */
+struct hs_token_t {
+ /* Type of HS token. */
+ hs_token_type_t type;
+
+ /* The size of the token (depends on the type). */
+ size_t token_len;
+
+ /* The token itself. Memory allocated at runtime. */
+ uint8_t *token;
+};
+
+#endif /* defined(HS_CIRCUITMAP_PRIVATE) */
+
+#ifdef TOR_UNIT_TESTS
+
+hs_circuitmap_ht *get_hs_circuitmap(void);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* !defined(TOR_HS_CIRCUITMAP_H) */
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c
new file mode 100644
index 0000000000..cc461e368d
--- /dev/null
+++ b/src/feature/hs/hs_client.c
@@ -0,0 +1,1623 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.c
+ * \brief Implement next generation hidden service client functionality
+ **/
+
+#define HS_CLIENT_PRIVATE
+
+#include "or/or.h"
+#include "or/circpathbias.h"
+#include "or/circuitbuild.h"
+#include "or/circuitlist.h"
+#include "or/circuituse.h"
+#include "or/config.h"
+#include "or/connection.h"
+#include "or/connection_edge.h"
+#include "lib/crypt_ops/crypto_format.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/directory.h"
+#include "or/hs_cache.h"
+#include "or/hs_cell.h"
+#include "or/hs_circuit.h"
+#include "or/hs_circuitmap.h"
+#include "or/hs_client.h"
+#include "or/hs_control.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_ident.h"
+#include "or/hs_ntor.h"
+#include "or/networkstatus.h"
+#include "or/nodelist.h"
+#include "or/reasons.h"
+#include "or/rendclient.h"
+#include "or/router.h"
+#include "or/routerset.h"
+
+#include "or/cpath_build_state_st.h"
+#include "or/dir_connection_st.h"
+#include "or/entry_connection_st.h"
+#include "or/extend_info_st.h"
+#include "or/origin_circuit_st.h"
+
+/* Return a human-readable string for the client fetch status code. */
+static const char *
+fetch_status_to_string(hs_client_fetch_status_t status)
+{
+ switch (status) {
+ case HS_CLIENT_FETCH_ERROR:
+ return "Internal error";
+ case HS_CLIENT_FETCH_LAUNCHED:
+ return "Descriptor fetch launched";
+ case HS_CLIENT_FETCH_HAVE_DESC:
+ return "Already have descriptor";
+ case HS_CLIENT_FETCH_NO_HSDIRS:
+ return "No more HSDir available to query";
+ case HS_CLIENT_FETCH_NOT_ALLOWED:
+ return "Fetching descriptors is not allowed";
+ case HS_CLIENT_FETCH_MISSING_INFO:
+ return "Missing directory information";
+ case HS_CLIENT_FETCH_PENDING:
+ return "Pending descriptor fetch";
+ default:
+ return "(Unknown client fetch status code)";
+ }
+}
+
+/* Return true iff tor should close the SOCKS request(s) for the descriptor
+ * fetch that ended up with this given status code. */
+static int
+fetch_status_should_close_socks(hs_client_fetch_status_t status)
+{
+ switch (status) {
+ case HS_CLIENT_FETCH_NO_HSDIRS:
+ /* No more HSDir to query, we can't complete the SOCKS request(s). */
+ case HS_CLIENT_FETCH_ERROR:
+ /* The fetch triggered an internal error. */
+ case HS_CLIENT_FETCH_NOT_ALLOWED:
+ /* Client is not allowed to fetch (FetchHidServDescriptors 0). */
+ goto close;
+ case HS_CLIENT_FETCH_MISSING_INFO:
+ case HS_CLIENT_FETCH_HAVE_DESC:
+ case HS_CLIENT_FETCH_PENDING:
+ case HS_CLIENT_FETCH_LAUNCHED:
+ /* The rest doesn't require tor to close the SOCKS request(s). */
+ goto no_close;
+ }
+
+ no_close:
+ return 0;
+ close:
+ return 1;
+}
+
+/* Cancel all descriptor fetches currently in progress. */
+static void
+cancel_descriptor_fetches(void)
+{
+ smartlist_t *conns =
+ connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC);
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+ const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident;
+ if (BUG(ident == NULL)) {
+ /* A directory connection fetching a service descriptor can't have an
+ * empty hidden service identifier. */
+ continue;
+ }
+ log_debug(LD_REND, "Marking for close a directory connection fetching "
+ "a hidden service descriptor for service %s.",
+ safe_str_client(ed25519_fmt(&ident->identity_pk)));
+ connection_mark_for_close(conn);
+ } SMARTLIST_FOREACH_END(conn);
+
+ /* No ownership of the objects in this list. */
+ smartlist_free(conns);
+ log_info(LD_REND, "Hidden service client descriptor fetches cancelled.");
+}
+
+/* Get all connections that are waiting on a circuit and flag them back to
+ * waiting for a hidden service descriptor for the given service key
+ * service_identity_pk. */
+static void
+flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk)
+{
+ tor_assert(service_identity_pk);
+
+ smartlist_t *conns =
+ connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT);
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+ edge_connection_t *edge_conn;
+ if (BUG(!CONN_IS_EDGE(conn))) {
+ continue;
+ }
+ edge_conn = TO_EDGE_CONN(conn);
+ if (edge_conn->hs_ident &&
+ ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ service_identity_pk)) {
+ connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
+ conn->state = AP_CONN_STATE_RENDDESC_WAIT;
+ }
+ } SMARTLIST_FOREACH_END(conn);
+
+ smartlist_free(conns);
+}
+
+/* Remove tracked HSDir requests from our history for this hidden service
+ * identity public key. */
+static void
+purge_hid_serv_request(const ed25519_public_key_t *identity_pk)
+{
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+ ed25519_public_key_t blinded_pk;
+
+ tor_assert(identity_pk);
+
+ /* Get blinded pubkey of hidden service. It is possible that we just moved
+ * to a new time period meaning that we won't be able to purge the request
+ * from the previous time period. That is fine because they will expire at
+ * some point and we don't care about those anymore. */
+ hs_build_blinded_pubkey(identity_pk, NULL, 0,
+ hs_get_time_period_num(0), &blinded_pk);
+ if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) {
+ return;
+ }
+ /* Purge last hidden service request from cache for this blinded key. */
+ hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk);
+}
+
+/* Return true iff there is at least one pending directory descriptor request
+ * for the service identity_pk. */
+static int
+directory_request_is_pending(const ed25519_public_key_t *identity_pk)
+{
+ int ret = 0;
+ smartlist_t *conns =
+ connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC);
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+ const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident;
+ if (BUG(ident == NULL)) {
+ /* A directory connection fetching a service descriptor can't have an
+ * empty hidden service identifier. */
+ continue;
+ }
+ if (!ed25519_pubkey_eq(identity_pk, &ident->identity_pk)) {
+ continue;
+ }
+ ret = 1;
+ break;
+ } SMARTLIST_FOREACH_END(conn);
+
+ /* No ownership of the objects in this list. */
+ smartlist_free(conns);
+ return ret;
+}
+
+/* We failed to fetch a descriptor for the service with <b>identity_pk</b>
+ * because of <b>status</b>. Find all pending SOCKS connections for this
+ * service that are waiting on the descriptor and close them with
+ * <b>reason</b>. */
+static void
+close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk,
+ hs_client_fetch_status_t status,
+ int reason)
+{
+ unsigned int count = 0;
+ time_t now = approx_time();
+ smartlist_t *conns =
+ connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT);
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+ entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn);
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
+
+ /* Only consider the entry connections that matches the service for which
+ * we tried to get the descriptor */
+ if (!edge_conn->hs_ident ||
+ !ed25519_pubkey_eq(identity_pk,
+ &edge_conn->hs_ident->identity_pk)) {
+ continue;
+ }
+ assert_connection_ok(base_conn, now);
+ /* Unattach the entry connection which will close for the reason. */
+ connection_mark_unattached_ap(entry_conn, reason);
+ count++;
+ } SMARTLIST_FOREACH_END(base_conn);
+
+ if (count > 0) {
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ hs_build_address(identity_pk, HS_VERSION_THREE, onion_address);
+ log_notice(LD_REND, "Closed %u streams for service %s.onion "
+ "for reason %s. Fetch status: %s.",
+ count, safe_str_client(onion_address),
+ stream_end_reason_to_string(reason),
+ fetch_status_to_string(status));
+ }
+
+ /* No ownership of the object(s) in this list. */
+ smartlist_free(conns);
+}
+
+/* Find all pending SOCKS connection waiting for a descriptor and retry them
+ * all. This is called when the directory information changed. */
+static void
+retry_all_socks_conn_waiting_for_desc(void)
+{
+ smartlist_t *conns =
+ connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT);
+
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+ hs_client_fetch_status_t status;
+ const edge_connection_t *edge_conn =
+ ENTRY_TO_EDGE_CONN(TO_ENTRY_CONN(base_conn));
+
+ /* Ignore non HS or non v3 connection. */
+ if (edge_conn->hs_ident == NULL) {
+ continue;
+ }
+ /* In this loop, we will possibly try to fetch a descriptor for the
+ * pending connections because we just got more directory information.
+ * However, the refetch process can cleanup all SOCKS request so the same
+ * service if an internal error happens. Thus, we can end up with closed
+ * connections in our list. */
+ if (base_conn->marked_for_close) {
+ continue;
+ }
+
+ /* XXX: There is an optimization we could do which is that for a service
+ * key, we could check if we can fetch and remember that decision. */
+
+ /* Order a refetch in case it works this time. */
+ status = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+ if (BUG(status == HS_CLIENT_FETCH_HAVE_DESC)) {
+ /* This case is unique because it can NOT happen in theory. Once we get
+ * a new descriptor, the HS client subsystem is notified immediately and
+ * the connections waiting for it are handled which means the state will
+ * change from renddesc wait state. Log this and continue to next
+ * connection. */
+ continue;
+ }
+ /* In the case of an error, either all SOCKS connections have been
+ * closed or we are still missing directory information. Leave the
+ * connection in renddesc wait state so when we get more info, we'll be
+ * able to try it again. */
+ } SMARTLIST_FOREACH_END(base_conn);
+
+ /* We don't have ownership of those objects. */
+ smartlist_free(conns);
+}
+
+/* A v3 HS circuit successfully connected to the hidden service. Update the
+ * stream state at <b>hs_conn_ident</b> appropriately. */
+static void
+note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+{
+ tor_assert(hs_conn_ident);
+
+ /* Remove from the hid serv cache all requests for that service so we can
+ * query the HSDir again later on for various reasons. */
+ purge_hid_serv_request(&hs_conn_ident->identity_pk);
+
+ /* The v2 subsystem cleans up the intro point time out flag at this stage.
+ * We don't try to do it here because we still need to keep intact the intro
+ * point state for future connections. Even though we are able to connect to
+ * the service, doesn't mean we should reset the timed out intro points.
+ *
+ * It is not possible to have successfully connected to an intro point
+ * present in our cache that was on error or timed out. Every entry in that
+ * cache have a 2 minutes lifetime so ultimately the intro point(s) state
+ * will be reset and thus possible to be retried. */
+}
+
+/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its
+ * descriptor by launching a dir connection to <b>hsdir</b>. Return a
+ * hs_client_fetch_status_t status code depending on how it went. */
+static hs_client_fetch_status_t
+directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk,
+ const routerstatus_t *hsdir)
+{
+ uint64_t current_time_period = hs_get_time_period_num(0);
+ ed25519_public_key_t blinded_pubkey;
+ char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+ hs_ident_dir_conn_t hs_conn_dir_ident;
+ int retval;
+
+ tor_assert(hsdir);
+ tor_assert(onion_identity_pk);
+
+ /* Get blinded pubkey */
+ hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+ current_time_period, &blinded_pubkey);
+ /* ...and base64 it. */
+ retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+ if (BUG(retval < 0)) {
+ return HS_CLIENT_FETCH_ERROR;
+ }
+
+ /* Copy onion pk to a dir_ident so that we attach it to the dir conn */
+ hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey,
+ &hs_conn_dir_ident);
+
+ /* Setup directory request */
+ directory_request_t *req =
+ directory_request_new(DIR_PURPOSE_FETCH_HSDESC);
+ directory_request_set_routerstatus(req, hsdir);
+ directory_request_set_indirection(req, DIRIND_ANONYMOUS);
+ directory_request_set_resource(req, base64_blinded_pubkey);
+ directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident);
+ directory_initiate_request(req);
+ directory_request_free(req);
+
+ log_info(LD_REND, "Descriptor fetch request for service %s with blinded "
+ "key %s to directory %s",
+ safe_str_client(ed25519_fmt(onion_identity_pk)),
+ safe_str_client(base64_blinded_pubkey),
+ safe_str_client(routerstatus_describe(hsdir)));
+
+ /* Fire a REQUESTED event on the control port. */
+ hs_control_desc_event_requested(onion_identity_pk, base64_blinded_pubkey,
+ hsdir);
+
+ /* Cleanup memory. */
+ memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey));
+ memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey));
+ memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident));
+
+ return HS_CLIENT_FETCH_LAUNCHED;
+}
+
+/** Return the HSDir we should use to fetch the descriptor of the hidden
+ * service with identity key <b>onion_identity_pk</b>. */
+STATIC routerstatus_t *
+pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk)
+{
+ int retval;
+ char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
+ uint64_t current_time_period = hs_get_time_period_num(0);
+ smartlist_t *responsible_hsdirs = NULL;
+ ed25519_public_key_t blinded_pubkey;
+ routerstatus_t *hsdir_rs = NULL;
+
+ tor_assert(onion_identity_pk);
+
+ /* Get blinded pubkey of hidden service */
+ hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
+ current_time_period, &blinded_pubkey);
+ /* ...and base64 it. */
+ retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
+ if (BUG(retval < 0)) {
+ return NULL;
+ }
+
+ /* Get responsible hsdirs of service for this time period */
+ responsible_hsdirs = smartlist_new();
+
+ hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period,
+ 0, 1, responsible_hsdirs);
+
+ log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.",
+ smartlist_len(responsible_hsdirs));
+
+ /* Pick an HSDir from the responsible ones. The ownership of
+ * responsible_hsdirs is given to this function so no need to free it. */
+ hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey);
+
+ return hsdir_rs;
+}
+
+/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>.
+ *
+ * On success, HS_CLIENT_FETCH_LAUNCHED is returned. Otherwise, an error from
+ * hs_client_fetch_status_t is returned. */
+MOCK_IMPL(STATIC hs_client_fetch_status_t,
+fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk))
+{
+ routerstatus_t *hsdir_rs =NULL;
+
+ tor_assert(onion_identity_pk);
+
+ hsdir_rs = pick_hsdir_v3(onion_identity_pk);
+ if (!hsdir_rs) {
+ log_info(LD_REND, "Couldn't pick a v3 hsdir.");
+ return HS_CLIENT_FETCH_NO_HSDIRS;
+ }
+
+ return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs);
+}
+
+/* Make sure that the given v3 origin circuit circ is a valid correct
+ * introduction circuit. This will BUG() on any problems and hard assert if
+ * the anonymity of the circuit is not ok. Return 0 on success else -1 where
+ * the circuit should be mark for closed immediately. */
+static int
+intro_circ_is_ok(const origin_circuit_t *circ)
+{
+ int ret = 0;
+
+ tor_assert(circ);
+
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) {
+ ret = -1;
+ }
+ if (BUG(circ->hs_ident == NULL)) {
+ ret = -1;
+ }
+ if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) {
+ ret = -1;
+ }
+
+ /* This can stop the tor daemon but we want that since if we don't have
+ * anonymity on this circuit, something went really wrong. */
+ assert_circ_anonymity_ok(circ, get_options());
+ return ret;
+}
+
+/* Find a descriptor intro point object that matches the given ident in the
+ * given descriptor desc. Return NULL if not found. */
+static const hs_desc_intro_point_t *
+find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident,
+ const hs_descriptor_t *desc)
+{
+ const hs_desc_intro_point_t *intro_point = NULL;
+
+ tor_assert(ident);
+ tor_assert(desc);
+
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ if (ed25519_pubkey_eq(&ident->intro_auth_pk,
+ &ip->auth_key_cert->signed_key)) {
+ intro_point = ip;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ip);
+
+ return intro_point;
+}
+
+/* Find a descriptor intro point object from the descriptor object desc that
+ * matches the given legacy identity digest in legacy_id. Return NULL if not
+ * found. */
+static hs_desc_intro_point_t *
+find_desc_intro_point_by_legacy_id(const char *legacy_id,
+ const hs_descriptor_t *desc)
+{
+ hs_desc_intro_point_t *ret_ip = NULL;
+
+ tor_assert(legacy_id);
+ tor_assert(desc);
+
+ /* We will go over every intro point and try to find which one is linked to
+ * that circuit. Those lists are small so it's not that expensive. */
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ hs_desc_intro_point_t *, ip) {
+ SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+ const hs_desc_link_specifier_t *, lspec) {
+ /* Not all tor node have an ed25519 identity key so we still rely on the
+ * legacy identity digest. */
+ if (lspec->type != LS_LEGACY_ID) {
+ continue;
+ }
+ if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) {
+ break;
+ }
+ /* Found it. */
+ ret_ip = ip;
+ goto end;
+ } SMARTLIST_FOREACH_END(lspec);
+ } SMARTLIST_FOREACH_END(ip);
+
+ end:
+ return ret_ip;
+}
+
+/* Send an INTRODUCE1 cell along the intro circuit and populate the rend
+ * circuit identifier with the needed key material for the e2e encryption.
+ * Return 0 on success, -1 if there is a transient error such that an action
+ * has been taken to recover and -2 if there is a permanent error indicating
+ * that both circuits were closed. */
+static int
+send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ)
+{
+ int status;
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ const ed25519_public_key_t *service_identity_pk = NULL;
+ const hs_desc_intro_point_t *ip;
+
+ tor_assert(rend_circ);
+ if (intro_circ_is_ok(intro_circ) < 0) {
+ goto perm_err;
+ }
+
+ service_identity_pk = &intro_circ->hs_ident->identity_pk;
+ /* For logging purposes. There will be a time where the hs_ident will have a
+ * version number but for now there is none because it's all v3. */
+ hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address);
+
+ log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u",
+ safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id);
+
+ /* 1) Get descriptor from our cache. */
+ const hs_descriptor_t *desc =
+ hs_cache_lookup_as_client(service_identity_pk);
+ if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk,
+ desc)) {
+ log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.",
+ safe_str_client(onion_address),
+ (desc) ? "didn't have usable intro points" :
+ "didn't have a descriptor");
+ hs_client_refetch_hsdesc(service_identity_pk);
+ /* We just triggered a refetch, make sure every connections are back
+ * waiting for that descriptor. */
+ flag_all_conn_wait_desc(service_identity_pk);
+ /* We just asked for a refetch so this is a transient error. */
+ goto tran_err;
+ }
+
+ /* We need to find which intro point in the descriptor we are connected to
+ * on intro_circ. */
+ ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
+ if (BUG(ip == NULL)) {
+ /* If we can find a descriptor from this introduction circuit ident, we
+ * must have a valid intro point object. Permanent error. */
+ goto perm_err;
+ }
+
+ /* Send the INTRODUCE1 cell. */
+ if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
+ desc->subcredential) < 0) {
+ /* Unable to send the cell, the intro circuit has been marked for close so
+ * this is a permanent error. */
+ tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close);
+ goto perm_err;
+ }
+
+ /* Cell has been sent successfully. Copy the introduction point
+ * authentication and encryption key in the rendezvous circuit identifier so
+ * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */
+ memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key,
+ sizeof(rend_circ->hs_ident->intro_enc_pk));
+ ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk,
+ &intro_circ->hs_ident->intro_auth_pk);
+
+ /* Now, we wait for an ACK or NAK on this circuit. */
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */
+ TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(intro_circ);
+
+ /* Success. */
+ status = 0;
+ goto end;
+
+ perm_err:
+ /* Permanent error: it is possible that the intro circuit was closed prior
+ * because we weren't able to send the cell. Make sure we don't double close
+ * it which would result in a warning. */
+ if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL);
+ }
+ circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL);
+ status = -2;
+ goto end;
+
+ tran_err:
+ status = -1;
+
+ end:
+ memwipe(onion_address, 0, sizeof(onion_address));
+ return status;
+}
+
+/* Using the introduction circuit circ, setup the authentication key of the
+ * intro point this circuit has extended to. */
+static void
+setup_intro_circ_auth_key(origin_circuit_t *circ)
+{
+ const hs_descriptor_t *desc;
+ const hs_desc_intro_point_t *ip;
+
+ tor_assert(circ);
+
+ desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ /* Opening intro circuit without the descriptor is no good... */
+ goto end;
+ }
+
+ /* We will go over every intro point and try to find which one is linked to
+ * that circuit. Those lists are small so it's not that expensive. */
+ ip = find_desc_intro_point_by_legacy_id(
+ circ->build_state->chosen_exit->identity_digest, desc);
+ if (ip) {
+ /* We got it, copy its authentication key to the identifier. */
+ ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+ &ip->auth_key_cert->signed_key);
+ goto end;
+ }
+
+ /* Reaching this point means we didn't find any intro point for this circuit
+ * which is not suppose to happen. */
+ tor_assert_nonfatal_unreached();
+
+ end:
+ return;
+}
+
+/* Called when an introduction circuit has opened. */
+static void
+client_intro_circ_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
+ log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+
+ /* This is an introduction circuit so we'll attach the correct
+ * authentication key to the circuit identifier so it can be identified
+ * properly later on. */
+ setup_intro_circ_auth_key(circ);
+
+ connection_ap_attach_pending(1);
+}
+
+/* Called when a rendezvous circuit has opened. */
+static void
+client_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+
+ const extend_info_t *rp_ei = circ->build_state->chosen_exit;
+
+ /* Check that we didn't accidentally choose a node that does not understand
+ * the v3 rendezvous protocol */
+ if (rp_ei) {
+ const node_t *rp_node = node_get_by_id(rp_ei->identity_digest);
+ if (rp_node) {
+ if (BUG(!node_supports_v3_rendezvous_point(rp_node))) {
+ return;
+ }
+ }
+ }
+
+ log_info(LD_REND, "Rendezvous circuit has opened to %s.",
+ safe_str_client(extend_info_describe(rp_ei)));
+
+ /* Ignore returned value, nothing we can really do. On failure, the circuit
+ * will be marked for close. */
+ hs_circ_send_establish_rendezvous(circ);
+
+ /* Register rend circuit in circuitmap if it's still alive. */
+ if (!TO_CIRCUIT(circ)->marked_for_close) {
+ hs_circuitmap_register_rend_circ_client_side(circ,
+ circ->hs_ident->rendezvous_cookie);
+ }
+}
+
+/* This is an helper function that convert a descriptor intro point object ip
+ * to a newly allocated extend_info_t object fully initialized. Return NULL if
+ * we can't convert it for which chances are that we are missing or malformed
+ * link specifiers. */
+STATIC extend_info_t *
+desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip)
+{
+ extend_info_t *ei;
+ smartlist_t *lspecs = smartlist_new();
+
+ tor_assert(ip);
+
+ /* We first encode the descriptor link specifiers into the binary
+ * representation which is a trunnel object. */
+ SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
+ const hs_desc_link_specifier_t *, desc_lspec) {
+ link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec);
+ smartlist_add(lspecs, lspec);
+ } SMARTLIST_FOREACH_END(desc_lspec);
+
+ /* Explicitly put the direct connection option to 0 because this is client
+ * side and there is no such thing as a non anonymous client. */
+ ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0);
+
+ SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls));
+ smartlist_free(lspecs);
+ return ei;
+}
+
+/* Return true iff the intro point ip for the service service_pk is usable.
+ * This function checks if the intro point is in the client intro state cache
+ * and checks at the failures. It is considered usable if:
+ * - No error happened (INTRO_POINT_FAILURE_GENERIC)
+ * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT)
+ * - The unreachable count is lower than
+ * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE)
+ */
+static int
+intro_point_is_usable(const ed25519_public_key_t *service_pk,
+ const hs_desc_intro_point_t *ip)
+{
+ const hs_cache_intro_state_t *state;
+
+ tor_assert(service_pk);
+ tor_assert(ip);
+
+ state = hs_cache_client_intro_state_find(service_pk,
+ &ip->auth_key_cert->signed_key);
+ if (state == NULL) {
+ /* This means we've never encountered any problem thus usable. */
+ goto usable;
+ }
+ if (state->error) {
+ log_info(LD_REND, "Intro point with auth key %s had an error. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+ if (state->timed_out) {
+ log_info(LD_REND, "Intro point with auth key %s timed out. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+ if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) {
+ log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)));
+ goto not_usable;
+ }
+
+ usable:
+ return 1;
+ not_usable:
+ return 0;
+}
+
+/* Using a descriptor desc, return a newly allocated extend_info_t object of a
+ * randomly picked introduction point from its list. Return NULL if none are
+ * usable. */
+STATIC extend_info_t *
+client_get_random_intro(const ed25519_public_key_t *service_pk)
+{
+ extend_info_t *ei = NULL, *ei_excluded = NULL;
+ smartlist_t *usable_ips = NULL;
+ const hs_descriptor_t *desc;
+ const hs_desc_encrypted_data_t *enc_data;
+ const or_options_t *options = get_options();
+ /* Calculate the onion address for logging purposes */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ tor_assert(service_pk);
+
+ desc = hs_cache_lookup_as_client(service_pk);
+ /* Assume the service is v3 if the descriptor is missing. This is ok,
+ * because we only use the address in log messages */
+ hs_build_address(service_pk,
+ desc ? desc->plaintext_data.version : HS_VERSION_THREE,
+ onion_address);
+ if (desc == NULL || !hs_client_any_intro_points_usable(service_pk,
+ desc)) {
+ log_info(LD_REND, "Unable to randomly select an introduction point "
+ "for service %s because descriptor %s. We can't connect.",
+ safe_str_client(onion_address),
+ (desc) ? "doesn't have any usable intro points"
+ : "is missing (assuming v3 onion address)");
+ goto end;
+ }
+
+ enc_data = &desc->encrypted_data;
+ usable_ips = smartlist_new();
+ smartlist_add_all(usable_ips, enc_data->intro_points);
+ while (smartlist_len(usable_ips) != 0) {
+ int idx;
+ const hs_desc_intro_point_t *ip;
+
+ /* Pick a random intro point and immediately remove it from the usable
+ * list so we don't pick it again if we have to iterate more. */
+ idx = crypto_rand_int(smartlist_len(usable_ips));
+ ip = smartlist_get(usable_ips, idx);
+ smartlist_del(usable_ips, idx);
+
+ /* We need to make sure we have a usable intro points which is in a good
+ * state in our cache. */
+ if (!intro_point_is_usable(service_pk, ip)) {
+ continue;
+ }
+
+ /* Generate an extend info object from the intro point object. */
+ ei = desc_intro_point_to_extend_info(ip);
+ if (ei == NULL) {
+ /* We can get here for instance if the intro point is a private address
+ * and we aren't allowed to extend to those. */
+ log_info(LD_REND, "Unable to select introduction point with auth key %s "
+ "for service %s, because we could not extend to it.",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)),
+ safe_str_client(onion_address));
+ continue;
+ }
+
+ /* Test the pick against ExcludeNodes. */
+ if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) {
+ /* If this pick is in the ExcludeNodes list, we keep its reference so if
+ * we ever end up not being able to pick anything else and StrictNodes is
+ * unset, we'll use it. */
+ if (ei_excluded) {
+ /* If something was already here free it. After the loop is gone we
+ * will examine the last excluded intro point, and that's fine since
+ * that's random anyway */
+ extend_info_free(ei_excluded);
+ }
+ ei_excluded = ei;
+ continue;
+ }
+
+ /* Good pick! Let's go with this. */
+ goto end;
+ }
+
+ /* Reaching this point means a couple of things. Either we can't use any of
+ * the intro point listed because the IP address can't be extended to or it
+ * is listed in the ExcludeNodes list. In the later case, if StrictNodes is
+ * set, we are forced to not use anything. */
+ ei = ei_excluded;
+ if (options->StrictNodes) {
+ log_warn(LD_REND, "Every introduction point for service %s is in the "
+ "ExcludeNodes set and StrictNodes is set. We can't connect.",
+ safe_str_client(onion_address));
+ extend_info_free(ei);
+ ei = NULL;
+ } else {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service "
+ "%s is unusable or we can't extend to it. We can't connect.",
+ safe_str_client(onion_address));
+ }
+
+ end:
+ smartlist_free(usable_ips);
+ memwipe(onion_address, 0, sizeof(onion_address));
+ return ei;
+}
+
+/* For this introduction circuit, we'll look at if we have any usable
+ * introduction point left for this service. If so, we'll use the circuit to
+ * re-extend to a new intro point. Else, we'll close the circuit and its
+ * corresponding rendezvous circuit. Return 0 if we are re-extending else -1
+ * if we are closing the circuits.
+ *
+ * This is called when getting an INTRODUCE_ACK cell with a NACK. */
+static int
+close_or_reextend_intro_circ(origin_circuit_t *intro_circ)
+{
+ int ret = -1;
+ const hs_descriptor_t *desc;
+ origin_circuit_t *rend_circ;
+
+ tor_assert(intro_circ);
+
+ desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ /* We can't continue without a descriptor. */
+ goto close;
+ }
+ /* We still have the descriptor, great! Let's try to see if we can
+ * re-extend by looking up if there are any usable intro points. */
+ if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk,
+ desc)) {
+ goto close;
+ }
+ /* Try to re-extend now. */
+ if (hs_client_reextend_intro_circuit(intro_circ) < 0) {
+ goto close;
+ }
+ /* Success on re-extending. Don't return an error. */
+ ret = 0;
+ goto end;
+
+ close:
+ /* Change the intro circuit purpose before so we don't report an intro point
+ * failure again triggering an extra descriptor fetch. The circuit can
+ * already be closed on failure to re-extend. */
+ if (!TO_CIRCUIT(intro_circ)->marked_for_close) {
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+ }
+ /* Close the related rendezvous circuit. */
+ rend_circ = hs_circuitmap_get_rend_circ_client_side(
+ intro_circ->hs_ident->rendezvous_cookie);
+ /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was
+ * inflight so we can't expect one every time. */
+ if (rend_circ) {
+ circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED);
+ }
+
+ end:
+ return ret;
+}
+
+/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate
+ * actions for the rendezvous point and finally close intro_circ. */
+static void
+handle_introduce_ack_success(origin_circuit_t *intro_circ)
+{
+ origin_circuit_t *rend_circ = NULL;
+
+ tor_assert(intro_circ);
+
+ log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous");
+
+ /* Get the rendezvous circuit for this rendezvous cookie. */
+ uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie;
+ rend_circ =
+ hs_circuitmap_get_established_rend_circ_client_side(rendezvous_cookie);
+ if (rend_circ == NULL) {
+ log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping");
+ goto end;
+ }
+
+ assert_circ_anonymity_ok(rend_circ, get_options());
+
+ /* It is possible to get a RENDEZVOUS2 cell before the INTRODUCE_ACK which
+ * means that the circuit will be joined and already transmitting data. In
+ * that case, simply skip the purpose change and close the intro circuit
+ * like it should be. */
+ if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) {
+ goto end;
+ }
+ circuit_change_purpose(TO_CIRCUIT(rend_circ),
+ CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the
+ * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */
+ TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL);
+
+ end:
+ /* We don't need the intro circuit anymore. It did what it had to do! */
+ circuit_change_purpose(TO_CIRCUIT(intro_circ),
+ CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
+ circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
+
+ /* XXX: Close pending intro circuits we might have in parallel. */
+ return;
+}
+
+/* Called when we get an INTRODUCE_ACK failure status code. Depending on our
+ * failure cache status, either close the circuit or re-extend to a new
+ * introduction point. */
+static void
+handle_introduce_ack_bad(origin_circuit_t *circ, int status)
+{
+ tor_assert(circ);
+
+ log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u",
+ safe_str_client(extend_info_describe(circ->build_state->chosen_exit)),
+ status);
+
+ /* It's a NAK. The introduction point didn't relay our request. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
+
+ /* Note down this failure in the intro point failure cache. Depending on how
+ * many times we've tried this intro point, close it or reextend. */
+ hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk,
+ &circ->hs_ident->intro_auth_pk,
+ INTRO_POINT_FAILURE_GENERIC);
+}
+
+/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded
+ * cell is in payload of length payload_len. Return 0 on success else a
+ * negative value. The circuit is either close or reuse to re-extend to a new
+ * introduction point. */
+static int
+handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int status, ret = -1;
+
+ tor_assert(circ);
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->chosen_exit);
+ assert_circ_anonymity_ok(circ, get_options());
+ tor_assert(payload);
+
+ status = hs_cell_parse_introduce_ack(payload, payload_len);
+ switch (status) {
+ case HS_CELL_INTRO_ACK_SUCCESS:
+ ret = 0;
+ handle_introduce_ack_success(circ);
+ goto end;
+ case HS_CELL_INTRO_ACK_FAILURE:
+ case HS_CELL_INTRO_ACK_BADFMT:
+ case HS_CELL_INTRO_ACK_NORELAY:
+ handle_introduce_ack_bad(circ, status);
+ /* We are going to see if we have to close the circuits (IP and RP) or we
+ * can re-extend to a new intro point. */
+ ret = close_or_reextend_intro_circ(circ);
+ break;
+ default:
+ log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s",
+ status,
+ safe_str_client(extend_info_describe(circ->build_state->chosen_exit)));
+ break;
+ }
+
+ end:
+ return ret;
+}
+
+/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The
+ * encoded cell is in payload of length payload_len. Return 0 on success or a
+ * negative value on error. On error, the circuit is marked for close. */
+STATIC int
+handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+ curve25519_public_key_t server_pk;
+ uint8_t auth_mac[DIGEST256_LEN] = {0};
+ uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0};
+ hs_ntor_rend_cell_keys_t keys;
+ const hs_ident_circuit_t *ident;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Make things easier. */
+ ident = circ->hs_ident;
+ tor_assert(ident);
+
+ if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info,
+ sizeof(handshake_info)) < 0) {
+ goto err;
+ }
+ /* Get from the handshake info the SERVER_PK and AUTH_MAC. */
+ memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN);
+ memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac));
+
+ /* Generate the handshake info. */
+ if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk,
+ &ident->rendezvous_client_kp,
+ &ident->intro_enc_pk, &server_pk,
+ &keys) < 0) {
+ log_info(LD_REND, "Unable to compute the rendezvous keys.");
+ goto err;
+ }
+
+ /* Critical check, make sure that the MAC matches what we got with what we
+ * computed just above. */
+ if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) {
+ log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell.");
+ goto err;
+ }
+
+ /* Setup the e2e encryption on the circuit and finalize its state. */
+ if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed,
+ sizeof(keys.ntor_key_seed), 0) < 0) {
+ log_info(LD_REND, "Unable to setup the e2e encryption.");
+ goto err;
+ }
+ /* Success. Hidden service connection finalized! */
+ ret = 0;
+ goto end;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ end:
+ memwipe(&keys, 0, sizeof(keys));
+ return ret;
+}
+
+/* Return true iff the client can fetch a descriptor for this service public
+ * identity key and status_out if not NULL is untouched. If the client can
+ * _not_ fetch the descriptor and if status_out is not NULL, it is set with
+ * the fetch status code. */
+static unsigned int
+can_client_refetch_desc(const ed25519_public_key_t *identity_pk,
+ hs_client_fetch_status_t *status_out)
+{
+ hs_client_fetch_status_t status;
+
+ tor_assert(identity_pk);
+
+ /* Are we configured to fetch descriptors? */
+ if (!get_options()->FetchHidServDescriptors) {
+ log_warn(LD_REND, "We received an onion address for a hidden service "
+ "descriptor but we are configured to not fetch.");
+ status = HS_CLIENT_FETCH_NOT_ALLOWED;
+ goto cannot;
+ }
+
+ /* Without a live consensus we can't do any client actions. It is needed to
+ * compute the hashring for a service. */
+ if (!networkstatus_get_live_consensus(approx_time())) {
+ log_info(LD_REND, "Can't fetch descriptor for service %s because we "
+ "are missing a live consensus. Stalling connection.",
+ safe_str_client(ed25519_fmt(identity_pk)));
+ status = HS_CLIENT_FETCH_MISSING_INFO;
+ goto cannot;
+ }
+
+ if (!router_have_minimum_dir_info()) {
+ log_info(LD_REND, "Can't fetch descriptor for service %s because we "
+ "dont have enough descriptors. Stalling connection.",
+ safe_str_client(ed25519_fmt(identity_pk)));
+ status = HS_CLIENT_FETCH_MISSING_INFO;
+ goto cannot;
+ }
+
+ /* Check if fetching a desc for this HS is useful to us right now */
+ {
+ const hs_descriptor_t *cached_desc = NULL;
+ cached_desc = hs_cache_lookup_as_client(identity_pk);
+ if (cached_desc && hs_client_any_intro_points_usable(identity_pk,
+ cached_desc)) {
+ log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor "
+ "but we already have a usable descriptor.");
+ status = HS_CLIENT_FETCH_HAVE_DESC;
+ goto cannot;
+ }
+ }
+
+ /* Don't try to refetch while we have a pending request for it. */
+ if (directory_request_is_pending(identity_pk)) {
+ log_info(LD_REND, "Already a pending directory request. Waiting on it.");
+ status = HS_CLIENT_FETCH_PENDING;
+ goto cannot;
+ }
+
+ /* Yes, client can fetch! */
+ return 1;
+ cannot:
+ if (status_out) {
+ *status_out = status;
+ }
+ return 0;
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/** A circuit just finished connecting to a hidden service that the stream
+ * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
+void
+hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
+{
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+
+ if (BUG(conn->rend_data && conn->hs_ident)) {
+ log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
+ "Prioritizing hs_ident");
+ }
+
+ if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
+ note_connection_attempt_succeeded(conn->hs_ident);
+ return;
+ } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
+ rend_client_note_connection_attempt_ended(conn->rend_data);
+ return;
+ }
+}
+
+/* With the given encoded descriptor in desc_str and the service key in
+ * service_identity_pk, decode the descriptor and set the desc pointer with a
+ * newly allocated descriptor object.
+ *
+ * Return 0 on success else a negative value and desc is set to NULL. */
+int
+hs_client_decode_descriptor(const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk,
+ hs_descriptor_t **desc)
+{
+ int ret;
+ uint8_t subcredential[DIGEST256_LEN];
+ ed25519_public_key_t blinded_pubkey;
+
+ tor_assert(desc_str);
+ tor_assert(service_identity_pk);
+ tor_assert(desc);
+
+ /* Create subcredential for this HS so that we can decrypt */
+ {
+ uint64_t current_time_period = hs_get_time_period_num(0);
+ hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
+ &blinded_pubkey);
+ hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential);
+ }
+
+ /* Parse descriptor */
+ ret = hs_desc_decode_descriptor(desc_str, subcredential, desc);
+ memwipe(subcredential, 0, sizeof(subcredential));
+ if (ret < 0) {
+ log_warn(LD_GENERAL, "Could not parse received descriptor as client.");
+ if (get_options()->SafeLogging_ == SAFELOG_SCRUB_NONE) {
+ log_warn(LD_GENERAL, "%s", escaped(desc_str));
+ }
+ goto err;
+ }
+
+ /* Make sure the descriptor signing key cross certifies with the computed
+ * blinded key. Without this validation, anyone knowing the subcredential
+ * and onion address can forge a descriptor. */
+ tor_cert_t *cert = (*desc)->plaintext_data.signing_key_cert;
+ if (tor_cert_checksig(cert,
+ &blinded_pubkey, approx_time()) < 0) {
+ log_warn(LD_GENERAL, "Descriptor signing key certificate signature "
+ "doesn't validate with computed blinded key: %s",
+ tor_cert_describe_signature_status(cert));
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/* Return true iff there are at least one usable intro point in the service
+ * descriptor desc. */
+int
+hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+ const hs_descriptor_t *desc)
+{
+ tor_assert(service_pk);
+ tor_assert(desc);
+
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ if (intro_point_is_usable(service_pk, ip)) {
+ goto usable;
+ }
+ } SMARTLIST_FOREACH_END(ip);
+
+ return 0;
+ usable:
+ return 1;
+}
+
+/** Launch a connection to a hidden service directory to fetch a hidden
+ * service descriptor using <b>identity_pk</b> to get the necessary keys.
+ *
+ * A hs_client_fetch_status_t code is returned. */
+int
+hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk)
+{
+ hs_client_fetch_status_t status;
+
+ tor_assert(identity_pk);
+
+ if (!can_client_refetch_desc(identity_pk, &status)) {
+ return status;
+ }
+
+ /* Try to fetch the desc and if we encounter an unrecoverable error, mark
+ * the desc as unavailable for now. */
+ status = fetch_v3_desc(identity_pk);
+ if (fetch_status_should_close_socks(status)) {
+ close_all_socks_conns_waiting_for_desc(identity_pk, status,
+ END_STREAM_REASON_RESOLVEFAILED);
+ /* Remove HSDir fetch attempts so that we can retry later if the user
+ * wants us to regardless of if we closed any connections. */
+ purge_hid_serv_request(identity_pk);
+ }
+ return status;
+}
+
+/* This is called when we are trying to attach an AP connection to these
+ * hidden service circuits from connection_ap_handshake_attach_circuit().
+ * Return 0 on success, -1 for a transient error that is actions were
+ * triggered to recover or -2 for a permenent error where both circuits will
+ * marked for close.
+ *
+ * The following supports every hidden service version. */
+int
+hs_client_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ)
+{
+ return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) :
+ rend_client_send_introduction(intro_circ,
+ rend_circ);
+}
+
+/* Called when the client circuit circ has been established. It can be either
+ * an introduction or rendezvous circuit. This function handles all hidden
+ * service versions. */
+void
+hs_client_circuit_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ if (circ->hs_ident) {
+ client_intro_circ_has_opened(circ);
+ } else {
+ rend_client_introcirc_has_opened(circ);
+ }
+ break;
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ if (circ->hs_ident) {
+ client_rendezvous_circ_has_opened(circ);
+ } else {
+ rend_client_rendcirc_has_opened(circ);
+ }
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ }
+}
+
+/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of
+ * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a
+ * negative value and the circuit marked for close. */
+int
+hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ tor_assert(circ);
+ tor_assert(payload);
+
+ (void) payload_len;
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) {
+ log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not "
+ "expecting one. Closing circuit.");
+ goto err;
+ }
+
+ log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is "
+ "now ready for rendezvous.");
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY);
+
+ /* Set timestamp_dirty, because circuit_expire_building expects it to
+ * specify when a circuit entered the _C_REND_READY state. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+
+ /* From a path bias point of view, this circuit is now successfully used.
+ * Waiting any longer opens us up to attacks from malicious hidden services.
+ * They could induce the client to attempt to connect to their hidden
+ * service and never reply to the client's rend requests */
+ pathbias_mark_use_success(circ);
+
+ /* If we already have the introduction circuit built, make sure we send
+ * the INTRODUCE cell _now_ */
+ connection_ap_attach_pending(1);
+
+ return 0;
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* This is called when a descriptor has arrived following a fetch request and
+ * has been stored in the client cache. Every entry connection that matches
+ * the service identity key in the ident will get attached to the hidden
+ * service circuit. */
+void
+hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident)
+{
+ time_t now = time(NULL);
+ smartlist_t *conns = NULL;
+
+ tor_assert(ident);
+
+ conns = connection_list_by_type_state(CONN_TYPE_AP,
+ AP_CONN_STATE_RENDDESC_WAIT);
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
+ const hs_descriptor_t *desc;
+ entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn);
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
+
+ /* Only consider the entry connections that matches the service for which
+ * we just fetched its descriptor. */
+ if (!edge_conn->hs_ident ||
+ !ed25519_pubkey_eq(&ident->identity_pk,
+ &edge_conn->hs_ident->identity_pk)) {
+ continue;
+ }
+ assert_connection_ok(base_conn, now);
+
+ /* We were just called because we stored the descriptor for this service
+ * so not finding a descriptor means we have a bigger problem. */
+ desc = hs_cache_lookup_as_client(&ident->identity_pk);
+ if (BUG(desc == NULL)) {
+ goto end;
+ }
+
+ if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) {
+ log_info(LD_REND, "Hidden service descriptor is unusable. "
+ "Closing streams.");
+ connection_mark_unattached_ap(entry_conn,
+ END_STREAM_REASON_RESOLVEFAILED);
+ /* We are unable to use the descriptor so remove the directory request
+ * from the cache so the next connection can try again. */
+ note_connection_attempt_succeeded(edge_conn->hs_ident);
+ goto end;
+ }
+
+ log_info(LD_REND, "Descriptor has arrived. Launching circuits.");
+
+ /* Because the connection can now proceed to opening circuit and
+ * ultimately connect to the service, reset those timestamp so the
+ * connection is considered "fresh" and can continue without being closed
+ * too early. */
+ base_conn->timestamp_created = now;
+ base_conn->timestamp_last_read_allowed = now;
+ base_conn->timestamp_last_write_allowed = now;
+ /* Change connection's state into waiting for a circuit. */
+ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+
+ connection_ap_mark_as_pending_circuit(entry_conn);
+ } SMARTLIST_FOREACH_END(base_conn);
+
+ end:
+ /* We don't have ownership of the objects in this list. */
+ smartlist_free(conns);
+}
+
+/* Return a newly allocated extend_info_t for a randomly chosen introduction
+ * point for the given edge connection identifier ident. Return NULL if we
+ * can't pick any usable introduction points. */
+extend_info_t *
+hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn)
+{
+ tor_assert(edge_conn);
+
+ return (edge_conn->hs_ident) ?
+ client_get_random_intro(&edge_conn->hs_ident->identity_pk) :
+ rend_client_get_random_intro(edge_conn->rend_data);
+}
+/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ.
+ * Return 0 on success else a negative value is returned. The circuit will be
+ * closed or reuse to extend again to another intro point. */
+int
+hs_client_receive_introduce_ack(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+ log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ goto end;
+ }
+
+ ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) :
+ rend_client_introduction_acked(circ, payload,
+ payload_len);
+ /* For path bias: This circuit was used successfully. NACK or ACK counts. */
+ pathbias_mark_use_success(circ);
+
+ end:
+ return ret;
+}
+
+/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return
+ * 0 on success else a negative value is returned. The circuit will be closed
+ * on error. */
+int
+hs_client_receive_rendezvous2(origin_circuit_t *circ,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Circuit can possibly be in both state because we could receive a
+ * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. "
+ "Closing circuit.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ goto end;
+ }
+
+ log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.",
+ TO_CIRCUIT(circ)->n_circ_id);
+
+ ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) :
+ rend_client_receive_rendezvous(circ, payload,
+ payload_len);
+ end:
+ return ret;
+}
+
+/* Extend the introduction circuit circ to another valid introduction point
+ * for the hidden service it is trying to connect to, or mark it and launch a
+ * new circuit if we can't extend it. Return 0 on success or possible
+ * success. Return -1 and mark the introduction circuit for close on permanent
+ * failure.
+ *
+ * On failure, the caller is responsible for marking the associated rendezvous
+ * circuit for close. */
+int
+hs_client_reextend_intro_circuit(origin_circuit_t *circ)
+{
+ int ret = -1;
+ extend_info_t *ei;
+
+ tor_assert(circ);
+
+ ei = (circ->hs_ident) ?
+ client_get_random_intro(&circ->hs_ident->identity_pk) :
+ rend_client_get_random_intro(circ->rend_data);
+ if (ei == NULL) {
+ log_warn(LD_REND, "No usable introduction points left. Closing.");
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ goto end;
+ }
+
+ if (circ->remaining_relay_early_cells) {
+ log_info(LD_REND, "Re-extending circ %u, this time to %s.",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(extend_info_describe(ei)));
+ ret = circuit_extend_to_new_exit(circ, ei);
+ if (ret == 0) {
+ /* We were able to extend so update the timestamp so we avoid expiring
+ * this circuit too early. The intro circuit is short live so the
+ * linkability issue is minimized, we just need the circuit to hold a
+ * bit longer so we can introduce. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+ }
+ } else {
+ log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).",
+ (unsigned int) TO_CIRCUIT(circ)->n_circ_id);
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ /* connection_ap_handshake_attach_circuit will launch a new intro circ. */
+ ret = 0;
+ }
+
+ end:
+ extend_info_free(ei);
+ return ret;
+}
+
+/* Release all the storage held by the client subsystem. */
+void
+hs_client_free_all(void)
+{
+ /* Purge the hidden service request cache. */
+ hs_purge_last_hid_serv_requests();
+}
+
+/* Purge all potentially remotely-detectable state held in the hidden
+ * service client code. Called on SIGNAL NEWNYM. */
+void
+hs_client_purge_state(void)
+{
+ /* v2 subsystem. */
+ rend_client_purge_state();
+
+ /* Cancel all descriptor fetches. Do this first so once done we are sure
+ * that our descriptor cache won't modified. */
+ cancel_descriptor_fetches();
+ /* Purge the introduction point state cache. */
+ hs_cache_client_intro_state_purge();
+ /* Purge the descriptor cache. */
+ hs_cache_purge_as_client();
+ /* Purge the last hidden service request cache. */
+ hs_purge_last_hid_serv_requests();
+
+ log_info(LD_REND, "Hidden service client state has been purged.");
+}
+
+/* Called when our directory information has changed. */
+void
+hs_client_dir_info_changed(void)
+{
+ /* We have possibly reached the minimum directory information or new
+ * consensus so retry all pending SOCKS connection in
+ * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */
+ retry_all_socks_conn_waiting_for_desc();
+}
diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h
new file mode 100644
index 0000000000..8083910747
--- /dev/null
+++ b/src/feature/hs/hs_client.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.h
+ * \brief Header file containing client data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CLIENT_H
+#define TOR_HS_CLIENT_H
+
+#include "lib/crypt_ops/crypto_ed25519.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_ident.h"
+
+/* Status code of a descriptor fetch request. */
+typedef enum {
+ /* Something internally went wrong. */
+ HS_CLIENT_FETCH_ERROR = -1,
+ /* The fetch request has been launched successfully. */
+ HS_CLIENT_FETCH_LAUNCHED = 0,
+ /* We already have a usable descriptor. No fetch. */
+ HS_CLIENT_FETCH_HAVE_DESC = 1,
+ /* No more HSDir available to query. */
+ HS_CLIENT_FETCH_NO_HSDIRS = 2,
+ /* The fetch request is not allowed. */
+ HS_CLIENT_FETCH_NOT_ALLOWED = 3,
+ /* We are missing information to be able to launch a request. */
+ HS_CLIENT_FETCH_MISSING_INFO = 4,
+ /* There is a pending fetch for the requested service. */
+ HS_CLIENT_FETCH_PENDING = 5,
+} hs_client_fetch_status_t;
+
+void hs_client_note_connection_attempt_succeeded(
+ const edge_connection_t *conn);
+
+int hs_client_decode_descriptor(
+ const char *desc_str,
+ const ed25519_public_key_t *service_identity_pk,
+ hs_descriptor_t **desc);
+int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
+ const hs_descriptor_t *desc);
+int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk);
+void hs_client_dir_info_changed(void);
+
+int hs_client_send_introduce1(origin_circuit_t *intro_circ,
+ origin_circuit_t *rend_circ);
+
+void hs_client_circuit_has_opened(origin_circuit_t *circ);
+
+int hs_client_receive_rendezvous_acked(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_client_receive_introduce_ack(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_client_receive_rendezvous2(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+
+void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident);
+
+extend_info_t *hs_client_get_random_intro_from_edge(
+ const edge_connection_t *edge_conn);
+
+int hs_client_reextend_intro_circuit(origin_circuit_t *circ);
+
+void hs_client_purge_state(void);
+
+void hs_client_free_all(void);
+
+#ifdef HS_CLIENT_PRIVATE
+
+STATIC routerstatus_t *
+pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk);
+
+STATIC extend_info_t *
+client_get_random_intro(const ed25519_public_key_t *service_pk);
+
+STATIC extend_info_t *
+desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip);
+
+STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len);
+
+MOCK_DECL(STATIC hs_client_fetch_status_t,
+ fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk));
+
+#endif /* defined(HS_CLIENT_PRIVATE) */
+
+#endif /* !defined(TOR_HS_CLIENT_H) */
+
diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c
new file mode 100644
index 0000000000..d91f45a639
--- /dev/null
+++ b/src/feature/hs/hs_common.c
@@ -0,0 +1,1825 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.c
+ * \brief Contains code shared between different HS protocol version as well
+ * as useful data structures and accessors used by other subsystems.
+ * The rendcommon.c should only contains code relating to the v2
+ * protocol.
+ **/
+
+#define HS_COMMON_PRIVATE
+
+#include "or/or.h"
+
+#include "or/config.h"
+#include "or/circuitbuild.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/networkstatus.h"
+#include "or/nodelist.h"
+#include "or/hs_cache.h"
+#include "or/hs_common.h"
+#include "or/hs_client.h"
+#include "or/hs_ident.h"
+#include "or/hs_service.h"
+#include "or/hs_circuitmap.h"
+#include "or/policies.h"
+#include "or/rendcommon.h"
+#include "or/rendservice.h"
+#include "or/routerset.h"
+#include "or/router.h"
+#include "or/shared_random_client.h"
+#include "or/dirauth/shared_random_state.h"
+
+#include "or/edge_connection_st.h"
+#include "or/networkstatus_st.h"
+#include "or/node_st.h"
+#include "or/origin_circuit_st.h"
+#include "or/routerstatus_st.h"
+
+/* Trunnel */
+#include "trunnel/ed25519_cert.h"
+
+/* Ed25519 Basepoint value. Taken from section 5 of
+ * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
+static const char *str_ed25519_basepoint =
+ "(15112221349535400772501151409588531511"
+ "454012693041857206046113283949847762202, "
+ "463168356949264781694283940034751631413"
+ "07993866256225615783033603165251855960)";
+
+#ifdef HAVE_SYS_UN_H
+
+/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
+ * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
+ * else return -ENOSYS if AF_UNIX is not supported (see function in the
+ * #else statement below). */
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ tor_assert(ports);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ smartlist_add(ports, p);
+ return 0;
+}
+
+/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
+ * on success else return -ENOSYS if AF_UNIX is not supported (see function
+ * in the #else statement below). */
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ tor_assert(conn);
+ tor_assert(p);
+ tor_assert(p->is_unix_addr);
+
+ conn->base_.socket_family = AF_UNIX;
+ tor_addr_make_unspec(&conn->base_.addr);
+ conn->base_.port = 1;
+ conn->base_.address = tor_strdup(p->unix_addr);
+ return 0;
+}
+
+#else /* !(defined(HAVE_SYS_UN_H)) */
+
+static int
+set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
+{
+ (void) conn;
+ (void) p;
+ return -ENOSYS;
+}
+
+static int
+add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
+{
+ (void) ports;
+ (void) p;
+ return -ENOSYS;
+}
+
+#endif /* defined(HAVE_SYS_UN_H) */
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * current hsdir_index. */
+static int
+compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index.fetch, DIGEST256_LEN);
+}
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * next hsdir_index. */
+static int
+compare_digest_to_store_first_hsdir_index(const void *_key,
+ const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index.store_first, DIGEST256_LEN);
+}
+
+/* Helper function: The key is a digest that we compare to a node_t object
+ * next hsdir_index. */
+static int
+compare_digest_to_store_second_hsdir_index(const void *_key,
+ const void **_member)
+{
+ const char *key = _key;
+ const node_t *node = *_member;
+ return tor_memcmp(key, node->hsdir_index.store_second, DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects current hsdir_index. */
+static int
+compare_node_fetch_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index.fetch,
+ node2->hsdir_index.fetch,
+ DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects next hsdir_index. */
+static int
+compare_node_store_first_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index.store_first,
+ node2->hsdir_index.store_first,
+ DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects next hsdir_index. */
+static int
+compare_node_store_second_hsdir_index(const void **a, const void **b)
+{
+ const node_t *node1= *a;
+ const node_t *node2 = *b;
+ return tor_memcmp(node1->hsdir_index.store_second,
+ node2->hsdir_index.store_second,
+ DIGEST256_LEN);
+}
+
+/* Allocate and return a string containing the path to filename in directory.
+ * This function will never return NULL. The caller must free this path. */
+char *
+hs_path_from_filename(const char *directory, const char *filename)
+{
+ char *file_path = NULL;
+
+ tor_assert(directory);
+ tor_assert(filename);
+
+ tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename);
+ return file_path;
+}
+
+/* Make sure that the directory for <b>service</b> is private, using the config
+ * <b>username</b>.
+ * If <b>create</b> is true:
+ * - if the directory exists, change permissions if needed,
+ * - if the directory does not exist, create it with the correct permissions.
+ * If <b>create</b> is false:
+ * - if the directory exists, check permissions,
+ * - if the directory does not exist, check if we think we can create it.
+ * Return 0 on success, -1 on failure. */
+int
+hs_check_service_private_dir(const char *username, const char *path,
+ unsigned int dir_group_readable,
+ unsigned int create)
+{
+ cpd_check_t check_opts = CPD_NONE;
+
+ tor_assert(path);
+
+ if (create) {
+ check_opts |= CPD_CREATE;
+ } else {
+ check_opts |= CPD_CHECK_MODE_ONLY;
+ check_opts |= CPD_CHECK;
+ }
+ if (dir_group_readable) {
+ check_opts |= CPD_GROUP_READ;
+ }
+ /* Check/create directory */
+ if (check_private_dir(path, check_opts, username) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Default, minimum, and maximum values for the maximum rendezvous failures
+ * consensus parameter. */
+#define MAX_REND_FAILURES_DEFAULT 2
+#define MAX_REND_FAILURES_MIN 1
+#define MAX_REND_FAILURES_MAX 10
+
+/** How many times will a hidden service operator attempt to connect to
+ * a requested rendezvous point before giving up? */
+int
+hs_get_service_max_rend_failures(void)
+{
+ return networkstatus_get_param(NULL, "hs_service_max_rdv_failures",
+ MAX_REND_FAILURES_DEFAULT,
+ MAX_REND_FAILURES_MIN,
+ MAX_REND_FAILURES_MAX);
+}
+
+/** Get the default HS time period length in minutes from the consensus. */
+STATIC uint64_t
+get_time_period_length(void)
+{
+ /* If we are on a test network, make the time period smaller than normal so
+ that we actually see it rotate. Specifically, make it the same length as
+ an SRV protocol run. */
+ if (get_options()->TestingTorNetwork) {
+ unsigned run_duration = sr_state_get_protocol_run_duration();
+ /* An SRV run should take more than a minute (it's 24 rounds) */
+ tor_assert_nonfatal(run_duration > 60);
+ /* Turn it from seconds to minutes before returning: */
+ return sr_state_get_protocol_run_duration() / 60;
+ }
+
+ int32_t time_period_length = networkstatus_get_param(NULL, "hsdir_interval",
+ HS_TIME_PERIOD_LENGTH_DEFAULT,
+ HS_TIME_PERIOD_LENGTH_MIN,
+ HS_TIME_PERIOD_LENGTH_MAX);
+ /* Make sure it's a positive value. */
+ tor_assert(time_period_length >= 0);
+ /* uint64_t will always be able to contain a int32_t */
+ return (uint64_t) time_period_length;
+}
+
+/** Get the HS time period number at time <b>now</b>. If <b>now</b> is not set,
+ * we try to get the time ourselves from a live consensus. */
+uint64_t
+hs_get_time_period_num(time_t now)
+{
+ uint64_t time_period_num;
+ time_t current_time;
+
+ /* If no time is specified, set current time based on consensus time, and
+ * only fall back to system time if that fails. */
+ if (now != 0) {
+ current_time = now;
+ } else {
+ networkstatus_t *ns = networkstatus_get_live_consensus(approx_time());
+ current_time = ns ? ns->valid_after : approx_time();
+ }
+
+ /* Start by calculating minutes since the epoch */
+ uint64_t time_period_length = get_time_period_length();
+ uint64_t minutes_since_epoch = current_time / 60;
+
+ /* Apply the rotation offset as specified by prop224 (section
+ * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV
+ * publication */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ time_period_rotation_offset /= 60; /* go from seconds to minutes */
+ tor_assert(minutes_since_epoch > time_period_rotation_offset);
+ minutes_since_epoch -= time_period_rotation_offset;
+
+ /* Calculate the time period */
+ time_period_num = minutes_since_epoch / time_period_length;
+ return time_period_num;
+}
+
+/** Get the number of the _upcoming_ HS time period, given that the current
+ * time is <b>now</b>. If <b>now</b> is not set, we try to get the time from a
+ * live consensus. */
+uint64_t
+hs_get_next_time_period_num(time_t now)
+{
+ return hs_get_time_period_num(now) + 1;
+}
+
+/* Get the number of the _previous_ HS time period, given that the current time
+ * is <b>now</b>. If <b>now</b> is not set, we try to get the time from a live
+ * consensus. */
+uint64_t
+hs_get_previous_time_period_num(time_t now)
+{
+ return hs_get_time_period_num(now) - 1;
+}
+
+/* Return the start time of the upcoming time period based on <b>now</b>. If
+ <b>now</b> is not set, we try to get the time ourselves from a live
+ consensus. */
+time_t
+hs_get_start_time_of_next_time_period(time_t now)
+{
+ uint64_t time_period_length = get_time_period_length();
+
+ /* Get start time of next time period */
+ uint64_t next_time_period_num = hs_get_next_time_period_num(now);
+ uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length;
+
+ /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */
+ unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
+ return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset);
+}
+
+/* Create a new rend_data_t for a specific given <b>version</b>.
+ * Return a pointer to the newly allocated data structure. */
+static rend_data_t *
+rend_data_alloc(uint32_t version)
+{
+ rend_data_t *rend_data = NULL;
+
+ switch (version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2 = tor_malloc_zero(sizeof(*v2));
+ v2->base_.version = HS_VERSION_TWO;
+ v2->base_.hsdirs_fp = smartlist_new();
+ rend_data = &v2->base_;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return rend_data;
+}
+
+/** Free all storage associated with <b>data</b> */
+void
+rend_data_free_(rend_data_t *data)
+{
+ if (!data) {
+ return;
+ }
+ /* By using our allocation function, this should always be set. */
+ tor_assert(data->hsdirs_fp);
+ /* Cleanup the HSDir identity digest. */
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d));
+ smartlist_free(data->hsdirs_fp);
+ /* Depending on the version, cleanup. */
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(data);
+ tor_free(v2_data);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Allocate and return a deep copy of <b>data</b>. */
+rend_data_t *
+rend_data_dup(const rend_data_t *data)
+{
+ rend_data_t *data_dup = NULL;
+ smartlist_t *hsdirs_fp = smartlist_new();
+
+ tor_assert(data);
+ tor_assert(data->hsdirs_fp);
+
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp,
+ smartlist_add(hsdirs_fp, tor_memdup(fp, DIGEST_LEN)));
+
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = tor_memdup(TO_REND_DATA_V2(data),
+ sizeof(*v2_data));
+ data_dup = &v2_data->base_;
+ data_dup->hsdirs_fp = hsdirs_fp;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return data_dup;
+}
+
+/* Compute the descriptor ID for each HS descriptor replica and save them. A
+ * valid onion address must be present in the <b>rend_data</b>.
+ *
+ * Return 0 on success else -1. */
+static int
+compute_desc_id(rend_data_t *rend_data)
+{
+ int ret = 0;
+ unsigned replica;
+ time_t now = time(NULL);
+
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ /* Compute descriptor ID for each replicas. */
+ for (replica = 0; replica < ARRAY_LENGTH(v2_data->descriptor_id);
+ replica++) {
+ ret = rend_compute_v2_desc_id(v2_data->descriptor_id[replica],
+ v2_data->onion_address,
+ v2_data->descriptor_cookie,
+ now, replica);
+ if (ret < 0) {
+ goto end;
+ }
+ }
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ end:
+ return ret;
+}
+
+/* Allocate and initialize a rend_data_t object for a service using the
+ * provided arguments. All arguments are optional (can be NULL), except from
+ * <b>onion_address</b> which MUST be set. The <b>pk_digest</b> is the hash of
+ * the service private key. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation this service is configured with.
+ *
+ * Return a valid rend_data_t pointer. This only returns a version 2 object of
+ * rend_data_t. */
+rend_data_t *
+rend_data_service_create(const char *onion_address, const char *pk_digest,
+ const uint8_t *cookie, rend_auth_type_t auth_type)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL);
+
+ if (pk_digest) {
+ memcpy(v2->rend_pk_digest, pk_digest, sizeof(v2->rend_pk_digest));
+ }
+ if (cookie) {
+ memcpy(rend_data->rend_cookie, cookie, sizeof(rend_data->rend_cookie));
+ }
+
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ v2->auth_type = auth_type;
+
+ return rend_data;
+}
+
+/* Allocate and initialize a rend_data_t object for a client request using the
+ * given arguments. Either an onion address or a descriptor ID is needed. Both
+ * can be given but in this case only the onion address will be used to make
+ * the descriptor fetch. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation the service is configured with.
+ *
+ * Return a valid rend_data_t pointer or NULL on error meaning the
+ * descriptor IDs couldn't be computed from the given data. */
+rend_data_t *
+rend_data_client_create(const char *onion_address, const char *desc_id,
+ const char *cookie, rend_auth_type_t auth_type)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL || desc_id != NULL);
+
+ if (cookie) {
+ memcpy(v2->descriptor_cookie, cookie, sizeof(v2->descriptor_cookie));
+ }
+ if (desc_id) {
+ memcpy(v2->desc_id_fetch, desc_id, sizeof(v2->desc_id_fetch));
+ }
+ if (onion_address) {
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ if (compute_desc_id(rend_data) < 0) {
+ goto error;
+ }
+ }
+
+ v2->auth_type = auth_type;
+
+ return rend_data;
+
+ error:
+ rend_data_free(rend_data);
+ return NULL;
+}
+
+/* Return the onion address from the rend data. Depending on the version,
+ * the size of the address can vary but it's always NUL terminated. */
+const char *
+rend_data_get_address(const rend_data_t *rend_data)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ return TO_REND_DATA_V2(rend_data)->onion_address;
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the descriptor ID for a specific replica number from the rend
+ * data. The returned data is a binary digest and depending on the version its
+ * size can vary. The size of the descriptor ID is put in <b>len_out</b> if
+ * non NULL. */
+const char *
+rend_data_get_desc_id(const rend_data_t *rend_data, uint8_t replica,
+ size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ tor_assert(replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS);
+ if (len_out) {
+ *len_out = DIGEST_LEN;
+ }
+ return TO_REND_DATA_V2(rend_data)->descriptor_id[replica];
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the public key digest using the given <b>rend_data</b>. The size of
+ * the digest is put in <b>len_out</b> (if set) which can differ depending on
+ * the version. */
+const uint8_t *
+rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ const rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ if (len_out) {
+ *len_out = sizeof(v2_data->rend_pk_digest);
+ }
+ return (const uint8_t *) v2_data->rend_pk_digest;
+ }
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Using the given time period number, compute the disaster shared random
+ * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */
+static void
+compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(srv_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+
+ /* Start setting up payload:
+ * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */
+ crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX,
+ HS_SRV_DISASTER_PREFIX_LEN);
+
+ /* Setup INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/** Due to the high cost of computing the disaster SRV and that potentially we
+ * would have to do it thousands of times in a row, we always cache the
+ * computer disaster SRV (and its corresponding time period num) in case we
+ * want to reuse it soon after. We need to cache two SRVs, one for each active
+ * time period.
+ */
+static uint8_t cached_disaster_srv[2][DIGEST256_LEN];
+static uint64_t cached_time_period_nums[2] = {0};
+
+/** Compute the disaster SRV value for this <b>time_period_num</b> and put it
+ * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches
+ * to see if we have already computed it. */
+STATIC void
+get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
+{
+ if (time_period_num == cached_time_period_nums[0]) {
+ memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN);
+ return;
+ } else if (time_period_num == cached_time_period_nums[1]) {
+ memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN);
+ return;
+ } else {
+ int replace_idx;
+ // Replace the lower period number.
+ if (cached_time_period_nums[0] <= cached_time_period_nums[1]) {
+ replace_idx = 0;
+ } else {
+ replace_idx = 1;
+ }
+ cached_time_period_nums[replace_idx] = time_period_num;
+ compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]);
+ memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN);
+ return;
+ }
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/** Get the first cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_first_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[0];
+}
+
+/** Get the second cached disaster SRV. Only used by unittests. */
+STATIC uint8_t *
+get_second_cached_disaster_srv(void)
+{
+ return cached_disaster_srv[1];
+}
+
+#endif /* defined(TOR_UNIT_TESTS) */
+
+/* When creating a blinded key, we need a parameter which construction is as
+ * follow: H(pubkey | [secret] | ed25519-basepoint | nonce).
+ *
+ * The nonce has a pre-defined format which uses the time period number
+ * period_num and the start of the period in second start_time_period.
+ *
+ * The secret of size secret_len is optional meaning that it can be NULL and
+ * thus will be ignored for the param construction.
+ *
+ * The result is put in param_out. */
+static void
+build_blinded_key_param(const ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t period_num, uint64_t period_length,
+ uint8_t *param_out)
+{
+ size_t offset = 0;
+ const char blind_str[] = "Derive temporary signing key";
+ uint8_t nonce[HS_KEYBLIND_NONCE_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(pubkey);
+ tor_assert(param_out);
+
+ /* Create the nonce N. The construction is as follow:
+ * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */
+ memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN);
+ offset += HS_KEYBLIND_NONCE_PREFIX_LEN;
+ set_uint64(nonce + offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(nonce + offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == HS_KEYBLIND_NONCE_LEN);
+
+ /* Generate the parameter h and the construction is as follow:
+ * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str));
+ crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN);
+ /* Optional secret. */
+ if (secret) {
+ crypto_digest_add_bytes(digest, (char *) secret, secret_len);
+ }
+ crypto_digest_add_bytes(digest, str_ed25519_basepoint,
+ strlen(str_ed25519_basepoint));
+ crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce));
+
+ /* Extract digest and put it in the param. */
+ crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ memwipe(nonce, 0, sizeof(nonce));
+}
+
+/* Using an ed25519 public key and version to build the checksum of an
+ * address. Put in checksum_out. Format is:
+ * SHA3-256(".onion checksum" || PUBKEY || VERSION)
+ *
+ * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */
+static void
+build_hs_checksum(const ed25519_public_key_t *key, uint8_t version,
+ uint8_t *checksum_out)
+{
+ size_t offset = 0;
+ char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN];
+
+ /* Build checksum data. */
+ memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX,
+ HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN);
+ offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN;
+ memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ set_uint8(data + offset, version);
+ offset += sizeof(version);
+ tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN);
+
+ /* Hash the data payload to create the checksum. */
+ crypto_digest256((char *) checksum_out, data, sizeof(data),
+ DIGEST_SHA3_256);
+}
+
+/* Using an ed25519 public key, checksum and version to build the binary
+ * representation of a service address. Put in addr_out. Format is:
+ * addr_out = PUBKEY || CHECKSUM || VERSION
+ *
+ * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */
+static void
+build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum,
+ uint8_t version, char *addr_out)
+{
+ size_t offset = 0;
+
+ tor_assert(key);
+ tor_assert(checksum);
+
+ memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ set_uint8(addr_out + offset, version);
+ offset += sizeof(uint8_t);
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Helper for hs_parse_address(): Using a binary representation of a service
+ * address, parse its content into the key_out, checksum_out and version_out.
+ * Any out variable can be NULL in case the caller would want only one field.
+ * checksum_out MUST at least be 2 bytes long. address must be at least
+ * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */
+static void
+hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ size_t offset = 0;
+
+ tor_assert(address);
+
+ if (key_out) {
+ /* First is the key. */
+ memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN);
+ }
+ offset += ED25519_PUBKEY_LEN;
+ if (checksum_out) {
+ /* Followed by a 2 bytes checksum. */
+ memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ }
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ if (version_out) {
+ /* Finally, version value is 1 byte. */
+ *version_out = get_uint8(address + offset);
+ }
+ offset += sizeof(uint8_t);
+ /* Extra safety. */
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Using the given identity public key and a blinded public key, compute the
+ * subcredential and put it in subcred_out (must be of size DIGEST256_LEN).
+ * This can't fail. */
+void
+hs_get_subcredential(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ uint8_t *subcred_out)
+{
+ uint8_t credential[DIGEST256_LEN];
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(blinded_pk);
+ tor_assert(subcred_out);
+
+ /* First, build the credential. Construction is as follow:
+ * credential = H("credential" | public-identity-key) */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX,
+ HS_CREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ /* Now, compute the subcredential. Construction is as follow:
+ * subcredential = H("subcredential" | credential | blinded-public-key). */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX,
+ HS_SUBCREDENTIAL_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) credential,
+ sizeof(credential));
+ crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+
+ memwipe(credential, 0, sizeof(credential));
+}
+
+/* From the given list of hidden service ports, find the ones that much the
+ * given edge connection conn, pick one at random and use it to set the
+ * connection address. Return 0 on success or -1 if none. */
+int
+hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
+{
+ rend_service_port_config_t *chosen_port;
+ unsigned int warn_once = 0;
+ smartlist_t *matching_ports;
+
+ tor_assert(ports);
+ tor_assert(conn);
+
+ matching_ports = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (TO_CONN(conn)->port != p->virtual_port) {
+ continue;
+ }
+ if (!(p->is_unix_addr)) {
+ smartlist_add(matching_ports, p);
+ } else {
+ if (add_unix_port(matching_ports, p)) {
+ if (!warn_once) {
+ /* Unix port not supported so warn only once. */
+ log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d "
+ "which is unsupported on this platform. "
+ "Ignoring it.",
+ TO_CONN(conn)->port);
+ }
+ warn_once++;
+ }
+ }
+ } SMARTLIST_FOREACH_END(p);
+
+ chosen_port = smartlist_choose(matching_ports);
+ smartlist_free(matching_ports);
+ if (chosen_port) {
+ if (!(chosen_port->is_unix_addr)) {
+ /* Get a non-AF_UNIX connection ready for connection_exit_connect() */
+ tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr);
+ TO_CONN(conn)->port = chosen_port->real_port;
+ } else {
+ if (set_unix_port(conn, chosen_port)) {
+ /* Simply impossible to end up here else we were able to add a Unix
+ * port without AF_UNIX support... ? */
+ tor_assert(0);
+ }
+ }
+ }
+ return (chosen_port) ? 0 : -1;
+}
+
+/* Using a base32 representation of a service address, parse its content into
+ * the key_out, checksum_out and version_out. Any out variable can be NULL in
+ * case the caller would want only one field. checksum_out MUST at least be 2
+ * bytes long.
+ *
+ * Return 0 if parsing went well; return -1 in case of error. */
+int
+hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ char decoded[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(address);
+
+ /* Obvious length check. */
+ if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
+ log_warn(LD_REND, "Service address %s has an invalid length. "
+ "Expected %lu but got %lu.",
+ escaped_safe_str(address),
+ (unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
+ (unsigned long) strlen(address));
+ goto invalid;
+ }
+
+ /* Decode address so we can extract needed fields. */
+ if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) {
+ log_warn(LD_REND, "Service address %s can't be decoded.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Parse the decoded address into the fields we need. */
+ hs_parse_address_impl(decoded, key_out, checksum_out, version_out);
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Validate a given onion address. The length, the base32 decoding and
+ * checksum are validated. Return 1 if valid else 0. */
+int
+hs_address_is_valid(const char *address)
+{
+ uint8_t version;
+ uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED];
+ uint8_t target_checksum[DIGEST256_LEN];
+ ed25519_public_key_t service_pubkey;
+
+ /* Parse the decoded address into the fields we need. */
+ if (hs_parse_address(address, &service_pubkey, checksum, &version) < 0) {
+ goto invalid;
+ }
+
+ /* Get the checksum it's suppose to be and compare it with what we have
+ * encoded in the address. */
+ build_hs_checksum(&service_pubkey, version, target_checksum);
+ if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) {
+ log_warn(LD_REND, "Service address %s invalid checksum.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Validate that this pubkey does not have a torsion component. We need to do
+ * this on the prop224 client-side so that attackers can't give equivalent
+ * forms of an onion address to users. */
+ if (ed25519_validate_pubkey(&service_pubkey) < 0) {
+ log_warn(LD_REND, "Service address %s has bad pubkey .",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Valid address. */
+ return 1;
+ invalid:
+ return 0;
+}
+
+/* Build a service address using an ed25519 public key and a given version.
+ * The returned address is base32 encoded and put in addr_out. The caller MUST
+ * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long.
+ *
+ * Format is as follow:
+ * base32(PUBKEY || CHECKSUM || VERSION)
+ * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION)
+ * */
+void
+hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out)
+{
+ uint8_t checksum[DIGEST256_LEN];
+ char address[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(key);
+ tor_assert(addr_out);
+
+ /* Get the checksum of the address. */
+ build_hs_checksum(key, version, checksum);
+ /* Get the binary address representation. */
+ build_hs_address(key, checksum, version, address);
+
+ /* Encode the address. addr_out will be NUL terminated after this. */
+ base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address,
+ sizeof(address));
+ /* Validate what we just built. */
+ tor_assert(hs_address_is_valid(addr_out));
+}
+
+/* Return a newly allocated copy of lspec. */
+link_specifier_t *
+hs_link_specifier_dup(const link_specifier_t *lspec)
+{
+ link_specifier_t *result = link_specifier_new();
+ memcpy(result, lspec, sizeof(*result));
+ /* The unrecognized field is a dynamic array so make sure to copy its
+ * content and not the pointer. */
+ link_specifier_setlen_un_unrecognized(
+ result, link_specifier_getlen_un_unrecognized(lspec));
+ if (link_specifier_getlen_un_unrecognized(result)) {
+ memcpy(link_specifier_getarray_un_unrecognized(result),
+ link_specifier_getconstarray_un_unrecognized(lspec),
+ link_specifier_getlen_un_unrecognized(result));
+ }
+ return result;
+}
+
+/* From a given ed25519 public key pk and an optional secret, compute a
+ * blinded public key and put it in blinded_pk_out. This is only useful to
+ * the client side because the client only has access to the identity public
+ * key of the service. */
+void
+hs_build_blinded_pubkey(const ed25519_public_key_t *pk,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_public_key_t *blinded_pk_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(pk);
+ tor_assert(blinded_pk_out);
+ tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN));
+
+ build_blinded_key_param(pk, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_public_blind(blinded_pk_out, pk, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* From a given ed25519 keypair kp and an optional secret, compute a blinded
+ * keypair for the current time period and put it in blinded_kp_out. This is
+ * only useful by the service side because the client doesn't have access to
+ * the identity secret key. */
+void
+hs_build_blinded_keypair(const ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ ed25519_keypair_t *blinded_kp_out)
+{
+ /* Our blinding key API requires a 32 bytes parameter. */
+ uint8_t param[DIGEST256_LEN];
+
+ tor_assert(kp);
+ tor_assert(blinded_kp_out);
+ /* Extra safety. A zeroed key is bad. */
+ tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN));
+ tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN));
+
+ build_blinded_key_param(&kp->pubkey, secret, secret_len,
+ time_period_num, get_time_period_length(), param);
+ ed25519_keypair_blind(blinded_kp_out, kp, param);
+
+ memwipe(param, 0, sizeof(param));
+}
+
+/* Return true if we are currently in the time segment between a new time
+ * period and a new SRV (in the real network that happens between 12:00 and
+ * 00:00 UTC). Here is a diagram showing exactly when this returns true:
+ *
+ * +------------------------------------------------------------------+
+ * | |
+ * | 00:00 12:00 00:00 12:00 00:00 12:00 |
+ * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 |
+ * | |
+ * | $==========|-----------$===========|-----------$===========| |
+ * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ |
+ * | |
+ * +------------------------------------------------------------------+
+ */
+MOCK_IMPL(int,
+hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now))
+{
+ time_t valid_after;
+ time_t srv_start_time, tp_start_time;
+
+ if (!consensus) {
+ consensus = networkstatus_get_live_consensus(now);
+ if (!consensus) {
+ return 0;
+ }
+ }
+
+ /* Get start time of next TP and of current SRV protocol run, and check if we
+ * are between them. */
+ valid_after = consensus->valid_after;
+ srv_start_time =
+ sr_state_get_start_time_of_current_protocol_run(valid_after);
+ tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time);
+
+ if (valid_after >= srv_start_time && valid_after < tp_start_time) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Return 1 if any virtual port in ports needs a circuit with good uptime.
+ * Else return 0. */
+int
+hs_service_requires_uptime_circ(const smartlist_t *ports)
+{
+ tor_assert(ports);
+
+ SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
+ if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
+ p->virtual_port)) {
+ return 1;
+ }
+ } SMARTLIST_FOREACH_END(p);
+ return 0;
+}
+
+/* Build hs_index which is used to find the responsible hsdirs. This index
+ * value is used to select the responsible HSDir where their hsdir_index is
+ * closest to this value.
+ * SHA3-256("store-at-idx" | blinded_public_key |
+ * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(blinded_pk);
+ tor_assert(hs_index_out);
+
+ /* Build hs_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+
+ /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */
+ {
+ uint64_t period_length = get_time_period_length();
+ char buf[sizeof(uint64_t)*3];
+ size_t offset = 0;
+ set_uint64(buf, tor_htonll(replica));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(buf));
+
+ crypto_digest_add_bytes(digest, buf, sizeof(buf));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Build hsdir_index which is used to find the responsible hsdirs. This is the
+ * index value that is compare to the hs_index when selecting an HSDir.
+ * SHA3-256("node-idx" | node_identity |
+ * shared_random_value | INT_8(period_length) | INT_8(period_num) )
+ *
+ * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */
+void
+hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
+ const uint8_t *srv_value, uint64_t period_num,
+ uint8_t *hsdir_index_out)
+{
+ crypto_digest_t *digest;
+
+ tor_assert(identity_pk);
+ tor_assert(srv_value);
+ tor_assert(hsdir_index_out);
+
+ /* Build hsdir_index. See construction at top of function comment. */
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN);
+ crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
+ ED25519_PUBKEY_LEN);
+ crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN);
+
+ {
+ uint64_t time_period_length = get_time_period_length();
+ char period_stuff[sizeof(uint64_t)*2];
+ size_t offset = 0;
+ set_uint64(period_stuff, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == sizeof(period_stuff));
+
+ crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
+ }
+
+ crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN);
+ crypto_digest_free(digest);
+}
+
+/* Return a newly allocated buffer containing the current shared random value
+ * or if not present, a disaster value is computed using the given time period
+ * number. If a consensus is provided in <b>ns</b>, use it to get the SRV
+ * value. This function can't fail. */
+uint8_t *
+hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *current_srv = sr_get_current(ns);
+
+ if (current_srv) {
+ memcpy(sr_value, current_srv->value, sizeof(current_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return a newly allocated buffer containing the previous shared random
+ * value or if not present, a disaster value is computed using the given time
+ * period number. This function can't fail. */
+uint8_t *
+hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *previous_srv = sr_get_previous(ns);
+
+ if (previous_srv) {
+ memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value));
+ } else {
+ /* Disaster mode. */
+ get_disaster_srv(time_period_num, sr_value);
+ }
+ return sr_value;
+}
+
+/* Return the number of replicas defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_n_replicas(void)
+{
+ /* The [1,16] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_n_replicas",
+ HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16);
+}
+
+/* Return the spread fetch value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_fetch(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_fetch",
+ HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128);
+}
+
+/* Return the spread store value defined by a consensus parameter or the
+ * default value. */
+int32_t
+hs_get_hsdir_spread_store(void)
+{
+ /* The [1,128] range is a specification requirement. */
+ return networkstatus_get_param(NULL, "hsdir_spread_store",
+ HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128);
+}
+
+/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
+ * Return 0 if everything is as expected, else return -1. */
+static int
+node_has_hsdir_index(const node_t *node)
+{
+ tor_assert(node_supports_v3_hsdir(node));
+
+ /* A node can't have an HSDir index without a descriptor since we need desc
+ * to get its ed25519 key. for_direct_connect should be zero, since we
+ * always use the consensus-indexed node's keys to build the hash ring, even
+ * if some of the consensus-indexed nodes are also bridges. */
+ if (!node_has_preferred_descriptor(node, 0)) {
+ return 0;
+ }
+
+ /* At this point, since the node has a desc, this node must also have an
+ * hsdir index. If not, something went wrong, so BUG out. */
+ if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.fetch,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+ if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.store_first,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+ if (BUG(tor_mem_is_zero((const char*)node->hsdir_index.store_second,
+ DIGEST256_LEN))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* For a given blinded key and time period number, get the responsible HSDir
+ * and put their routerstatus_t object in the responsible_dirs list. If
+ * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t
+ * is used. If 'for_fetching' is true, the spread fetch consensus parameter is
+ * used else the spread store is used which is only for upload. This function
+ * can't fail but it is possible that the responsible_dirs list contains fewer
+ * nodes than expected.
+ *
+ * This function goes over the latest consensus routerstatus list and sorts it
+ * by their node_t hsdir_index then does a binary search to find the closest
+ * node. All of this makes it a bit CPU intensive so use it wisely. */
+void
+hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num, int use_second_hsdir_index,
+ int for_fetching, smartlist_t *responsible_dirs)
+{
+ smartlist_t *sorted_nodes;
+ /* The compare function used for the smartlist bsearch. We have two
+ * different depending on is_next_period. */
+ int (*cmp_fct)(const void *, const void **);
+
+ tor_assert(blinded_pk);
+ tor_assert(responsible_dirs);
+
+ sorted_nodes = smartlist_new();
+
+ /* Make sure we actually have a live consensus */
+ networkstatus_t *c = networkstatus_get_live_consensus(approx_time());
+ if (!c || smartlist_len(c->routerstatus_list) == 0) {
+ log_warn(LD_REND, "No live consensus so we can't get the responsible "
+ "hidden service directories.");
+ goto done;
+ }
+
+ /* Ensure the nodelist is fresh, since it contains the HSDir indices. */
+ nodelist_ensure_freshness(c);
+
+ /* Add every node_t that support HSDir v3 for which we do have a valid
+ * hsdir_index already computed for them for this consensus. */
+ {
+ SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) {
+ /* Even though this node_t object won't be modified and should be const,
+ * we can't add const object in a smartlist_t. */
+ node_t *n = node_get_mutable_by_id(rs->identity_digest);
+ tor_assert(n);
+ if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
+ if (!node_has_hsdir_index(n)) {
+ log_info(LD_GENERAL, "Node %s was found without hsdir index.",
+ node_describe(n));
+ continue;
+ }
+ smartlist_add(sorted_nodes, n);
+ }
+ } SMARTLIST_FOREACH_END(rs);
+ }
+ if (smartlist_len(sorted_nodes) == 0) {
+ log_warn(LD_REND, "No nodes found to be HSDir or supporting v3.");
+ goto done;
+ }
+
+ /* First thing we have to do is sort all node_t by hsdir_index. The
+ * is_next_period tells us if we want the current or the next one. Set the
+ * bsearch compare function also while we are at it. */
+ if (for_fetching) {
+ smartlist_sort(sorted_nodes, compare_node_fetch_hsdir_index);
+ cmp_fct = compare_digest_to_fetch_hsdir_index;
+ } else if (use_second_hsdir_index) {
+ smartlist_sort(sorted_nodes, compare_node_store_second_hsdir_index);
+ cmp_fct = compare_digest_to_store_second_hsdir_index;
+ } else {
+ smartlist_sort(sorted_nodes, compare_node_store_first_hsdir_index);
+ cmp_fct = compare_digest_to_store_first_hsdir_index;
+ }
+
+ /* For all replicas, we'll select a set of HSDirs using the consensus
+ * parameters and the sorted list. The replica starting at value 1 is
+ * defined by the specification. */
+ for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) {
+ int idx, start, found, n_added = 0;
+ uint8_t hs_index[DIGEST256_LEN] = {0};
+ /* Number of node to add to the responsible dirs list depends on if we are
+ * trying to fetch or store. A client always fetches. */
+ int n_to_add = (for_fetching) ? hs_get_hsdir_spread_fetch() :
+ hs_get_hsdir_spread_store();
+
+ /* Get the index that we should use to select the node. */
+ hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index);
+ /* The compare function pointer has been set correctly earlier. */
+ start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct,
+ &found);
+ /* Getting the length of the list if no member is greater than the key we
+ * are looking for so start at the first element. */
+ if (idx == smartlist_len(sorted_nodes)) {
+ start = idx = 0;
+ }
+ while (n_added < n_to_add) {
+ const node_t *node = smartlist_get(sorted_nodes, idx);
+ /* If the node has already been selected which is possible between
+ * replicas, the specification says to skip over. */
+ if (!smartlist_contains(responsible_dirs, node->rs)) {
+ smartlist_add(responsible_dirs, node->rs);
+ ++n_added;
+ }
+ if (++idx == smartlist_len(sorted_nodes)) {
+ /* Wrap if we've reached the end of the list. */
+ idx = 0;
+ }
+ if (idx == start) {
+ /* We've gone over the whole list, stop and avoid infinite loop. */
+ break;
+ }
+ }
+ }
+
+ done:
+ smartlist_free(sorted_nodes);
+}
+
+/*********************** HSDir request tracking ***************************/
+
+/** Return the period for which a hidden service directory cannot be queried
+ * for the same descriptor ID again, taking TestingTorNetwork into account. */
+time_t
+hs_hsdir_requery_period(const or_options_t *options)
+{
+ tor_assert(options);
+
+ if (options->TestingTorNetwork) {
+ return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING;
+ } else {
+ return REND_HID_SERV_DIR_REQUERY_PERIOD;
+ }
+}
+
+/** Tracks requests for fetching hidden service descriptors. It's used by
+ * hidden service clients, to avoid querying HSDirs that have already failed
+ * giving back a descriptor. The same data structure is used to track both v2
+ * and v3 HS descriptor requests.
+ *
+ * The string map is a key/value store that contains the last request times to
+ * hidden service directories for certain queries. Specifically:
+ *
+ * key = base32(hsdir_identity) + base32(hs_identity)
+ * value = time_t of last request for that hs_identity to that HSDir
+ *
+ * where 'hsdir_identity' is the identity digest of the HSDir node, and
+ * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519
+ * blinded public key of the HS in the v3 case. */
+static strmap_t *last_hid_serv_requests_ = NULL;
+
+/** Returns last_hid_serv_requests_, initializing it to a new strmap if
+ * necessary. */
+STATIC strmap_t *
+get_last_hid_serv_requests(void)
+{
+ if (!last_hid_serv_requests_)
+ last_hid_serv_requests_ = strmap_new();
+ return last_hid_serv_requests_;
+}
+
+/** Look up the last request time to hidden service directory <b>hs_dir</b>
+ * for descriptor request key <b>req_key_str</b> which is the descriptor ID
+ * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero,
+ * assign the current time <b>now</b> and return that. Otherwise, return the
+ * most recent request time, or 0 if no such request has been sent before. */
+time_t
+hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+ const char *req_key_str,
+ time_t now, int set)
+{
+ char hsdir_id_base32[BASE32_DIGEST_LEN + 1];
+ char *hsdir_desc_comb_id = NULL;
+ time_t *last_request_ptr;
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+ /* Create the key */
+ base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32),
+ hs_dir->identity_digest, DIGEST_LEN);
+ tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str);
+
+ if (set) {
+ time_t *oldptr;
+ last_request_ptr = tor_malloc_zero(sizeof(time_t));
+ *last_request_ptr = now;
+ oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id,
+ last_request_ptr);
+ tor_free(oldptr);
+ } else {
+ last_request_ptr = strmap_get(last_hid_serv_requests,
+ hsdir_desc_comb_id);
+ }
+
+ tor_free(hsdir_desc_comb_id);
+ return (last_request_ptr) ? *last_request_ptr : 0;
+}
+
+/** Clean the history of request times to hidden service directories, so that
+ * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD
+ * seconds any more. */
+void
+hs_clean_last_hid_serv_requests(time_t now)
+{
+ strmap_iter_t *iter;
+ time_t cutoff = now - hs_hsdir_requery_period(get_options());
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+ for (iter = strmap_iter_init(last_hid_serv_requests);
+ !strmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ time_t *ent;
+ strmap_iter_get(iter, &key, &val);
+ ent = (time_t *) val;
+ if (*ent < cutoff) {
+ iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+ tor_free(ent);
+ } else {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ }
+ }
+}
+
+/** Remove all requests related to the descriptor request key string
+ * <b>req_key_str</b> from the history of times of requests to hidden service
+ * directories.
+ *
+ * This is called from rend_client_note_connection_attempt_ended(), which
+ * must be idempotent, so any future changes to this function must leave it
+ * idempotent too. */
+void
+hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str)
+{
+ strmap_iter_t *iter;
+ strmap_t *last_hid_serv_requests = get_last_hid_serv_requests();
+
+ for (iter = strmap_iter_init(last_hid_serv_requests);
+ !strmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ strmap_iter_get(iter, &key, &val);
+
+ /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of
+ * semantic, see #23305. */
+
+ /* This strmap contains variable-sized elements so this is a basic length
+ * check on the strings we are about to compare. The key is variable sized
+ * since it's composed as follows:
+ * key = base32(hsdir_identity) + base32(req_key_str)
+ * where 'req_key_str' is the descriptor ID of the HS in the v2 case, or
+ * the ed25519 blinded public key of the HS in the v3 case. */
+ if (strlen(key) < REND_DESC_ID_V2_LEN_BASE32 + strlen(req_key_str)) {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ continue;
+ }
+
+ /* Check if the tracked request matches our request key */
+ if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str,
+ strlen(req_key_str))) {
+ iter = strmap_iter_next_rmv(last_hid_serv_requests, iter);
+ tor_free(val);
+ } else {
+ iter = strmap_iter_next(last_hid_serv_requests, iter);
+ }
+ }
+}
+
+/** Purge the history of request times to hidden service directories,
+ * so that future lookups of an HS descriptor will not fail because we
+ * accessed all of the HSDir relays responsible for the descriptor
+ * recently. */
+void
+hs_purge_last_hid_serv_requests(void)
+{
+ /* Don't create the table if it doesn't exist yet (and it may very
+ * well not exist if the user hasn't accessed any HSes)... */
+ strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_;
+ /* ... and let get_last_hid_serv_requests re-create it for us if
+ * necessary. */
+ last_hid_serv_requests_ = NULL;
+
+ if (old_last_hid_serv_requests != NULL) {
+ log_info(LD_REND, "Purging client last-HS-desc-request-time table");
+ strmap_free(old_last_hid_serv_requests, tor_free_);
+ }
+}
+
+/***********************************************************************/
+
+/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the
+ * one that we should use to fetch a descriptor right now. Take into account
+ * previous failed attempts at fetching this descriptor from HSDirs using the
+ * string identifier <b>req_key_str</b>.
+ *
+ * Steals ownership of <b>responsible_dirs</b>.
+ *
+ * Return the routerstatus of the chosen HSDir if successful, otherwise return
+ * NULL if no HSDirs are worth trying right now. */
+routerstatus_t *
+hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str)
+{
+ smartlist_t *usable_responsible_dirs = smartlist_new();
+ const or_options_t *options = get_options();
+ routerstatus_t *hs_dir;
+ time_t now = time(NULL);
+ int excluded_some;
+
+ tor_assert(req_key_str);
+
+ /* Clean outdated request history first. */
+ hs_clean_last_hid_serv_requests(now);
+
+ /* Only select those hidden service directories to which we did not send a
+ * request recently and for which we have a router descriptor here.
+ *
+ * Use for_direct_connect==0 even if we will be connecting to the node
+ * directly, since we always use the key information in the
+ * consensus-indexed node descriptors for building the index.
+ **/
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) {
+ time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0);
+ const node_t *node = node_get_by_id(dir->identity_digest);
+ if (last + hs_hsdir_requery_period(options) >= now ||
+ !node || !node_has_preferred_descriptor(node, 0)) {
+ SMARTLIST_DEL_CURRENT(responsible_dirs, dir);
+ continue;
+ }
+ if (!routerset_contains_node(options->ExcludeNodes, node)) {
+ smartlist_add(usable_responsible_dirs, dir);
+ }
+ } SMARTLIST_FOREACH_END(dir);
+
+ excluded_some =
+ smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs);
+
+ hs_dir = smartlist_choose(usable_responsible_dirs);
+ if (!hs_dir && !options->StrictNodes) {
+ hs_dir = smartlist_choose(responsible_dirs);
+ }
+
+ smartlist_free(responsible_dirs);
+ smartlist_free(usable_responsible_dirs);
+ if (!hs_dir) {
+ log_info(LD_REND, "Could not pick one of the responsible hidden "
+ "service directories, because we requested them all "
+ "recently without success.");
+ if (options->StrictNodes && excluded_some) {
+ log_warn(LD_REND, "Could not pick a hidden service directory for the "
+ "requested hidden service: they are all either down or "
+ "excluded, and StrictNodes is set.");
+ }
+ } else {
+ /* Remember that we are requesting a descriptor from this hidden service
+ * directory now. */
+ hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1);
+ }
+
+ return hs_dir;
+}
+
+/* From a list of link specifier, an onion key and if we are requesting a
+ * direct connection (ex: single onion service), return a newly allocated
+ * extend_info_t object. This function always returns an extend info with
+ * an IPv4 address, or NULL.
+ *
+ * It performs the following checks:
+ * if either IPv4 or legacy ID is missing, return NULL.
+ * if direct_conn, and we can't reach the IPv4 address, return NULL.
+ */
+extend_info_t *
+hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+ const curve25519_public_key_t *onion_key,
+ int direct_conn)
+{
+ int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+ char legacy_id[DIGEST_LEN] = {0};
+ uint16_t port_v4 = 0;
+ tor_addr_t addr_v4;
+ ed25519_public_key_t ed25519_pk;
+ extend_info_t *info = NULL;
+
+ tor_assert(lspecs);
+
+ SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) {
+ switch (link_specifier_get_ls_type(ls)) {
+ case LS_IPV4:
+ /* Skip if we already seen a v4. */
+ if (have_v4) continue;
+ tor_addr_from_ipv4h(&addr_v4,
+ link_specifier_get_un_ipv4_addr(ls));
+ port_v4 = link_specifier_get_un_ipv4_port(ls);
+ have_v4 = 1;
+ break;
+ case LS_LEGACY_ID:
+ /* Make sure we do have enough bytes for the legacy ID. */
+ if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
+ break;
+ }
+ memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
+ sizeof(legacy_id));
+ have_legacy_id = 1;
+ break;
+ case LS_ED25519_ID:
+ memcpy(ed25519_pk.pubkey,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ ED25519_PUBKEY_LEN);
+ have_ed25519_id = 1;
+ break;
+ default:
+ /* Ignore unknown. */
+ break;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* Legacy ID is mandatory, and we require IPv4. */
+ if (!have_v4 || !have_legacy_id) {
+ goto done;
+ }
+
+ /* We know we have IPv4, because we just checked. */
+ if (!direct_conn) {
+ /* All clients can extend to any IPv4 via a 3-hop path. */
+ goto validate;
+ } else if (direct_conn &&
+ fascist_firewall_allows_address_addr(&addr_v4, port_v4,
+ FIREWALL_OR_CONNECTION,
+ 0, 0)) {
+ /* Direct connection and we can reach it in IPv4 so go for it. */
+ goto validate;
+
+ /* We will add support for falling back to a 3-hop path in a later
+ * release. */
+ } else {
+ /* If we can't reach IPv4, return NULL. */
+ goto done;
+ }
+
+ /* We will add support for IPv6 in a later release. */
+
+ validate:
+ /* We'll validate now that the address we've picked isn't a private one. If
+ * it is, are we allowing to extend to private address? */
+ if (!extend_info_addr_is_allowed(&addr_v4)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Requested address is private and we are not allowed to extend to "
+ "it: %s:%u", fmt_addr(&addr_v4), port_v4);
+ goto done;
+ }
+
+ /* We do have everything for which we think we can connect successfully. */
+ info = extend_info_new(NULL, legacy_id,
+ (have_ed25519_id) ? &ed25519_pk : NULL, NULL,
+ onion_key, &addr_v4, port_v4);
+ done:
+ return info;
+}
+
+/***********************************************************************/
+
+/* Initialize the entire HS subsytem. This is called in tor_init() before any
+ * torrc options are loaded. Only for >= v3. */
+void
+hs_init(void)
+{
+ hs_circuitmap_init();
+ hs_service_init();
+ hs_cache_init();
+}
+
+/* Release and cleanup all memory of the HS subsystem (all version). This is
+ * called by tor_free_all(). */
+void
+hs_free_all(void)
+{
+ hs_circuitmap_free_all();
+ hs_service_free_all();
+ hs_cache_free_all();
+ hs_client_free_all();
+}
+
+/* For the given origin circuit circ, decrement the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_dec_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams--;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams--;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
+
+/* For the given origin circuit circ, increment the number of rendezvous
+ * stream counter. This handles every hidden service version. */
+void
+hs_inc_rdv_stream_counter(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ if (circ->rend_data) {
+ circ->rend_data->nr_streams++;
+ } else if (circ->hs_ident) {
+ circ->hs_ident->num_rdv_streams++;
+ } else {
+ /* Should not be called if this circuit is not for hidden service. */
+ tor_assert_nonfatal_unreached();
+ }
+}
diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h
new file mode 100644
index 0000000000..24f5f3a20f
--- /dev/null
+++ b/src/feature/hs/hs_common.h
@@ -0,0 +1,288 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.h
+ * \brief Header file containing common data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_COMMON_H
+#define TOR_HS_COMMON_H
+
+#include "or/or.h"
+#include "lib/defs/x25519_sizes.h"
+
+struct curve25519_public_key_t;
+struct ed25519_public_key_t;
+struct ed25519_keypair_t;
+
+/* Trunnel */
+#include "trunnel/ed25519_cert.h"
+
+/* Protocol version 2. Use this instead of hardcoding "2" in the code base,
+ * this adds a clearer semantic to the value when used. */
+#define HS_VERSION_TWO 2
+/* Version 3 of the protocol (prop224). */
+#define HS_VERSION_THREE 3
+/* Earliest and latest version we support. */
+#define HS_VERSION_MIN HS_VERSION_TWO
+#define HS_VERSION_MAX HS_VERSION_THREE
+
+/** Try to maintain this many intro points per service by default. */
+#define NUM_INTRO_POINTS_DEFAULT 3
+/** Maximum number of intro points per generic and version 2 service. */
+#define NUM_INTRO_POINTS_MAX 10
+/** Number of extra intro points we launch if our set of intro nodes is empty.
+ * See proposal 155, section 4. */
+#define NUM_INTRO_POINTS_EXTRA 2
+
+/** If we can't build our intro circuits, don't retry for this long. */
+#define INTRO_CIRC_RETRY_PERIOD (60*5)
+/** Don't try to build more than this many circuits before giving up for a
+ * while.*/
+#define MAX_INTRO_CIRCS_PER_PERIOD 10
+/** How many times will a hidden service operator attempt to connect to a
+ * requested rendezvous point before giving up? */
+#define MAX_REND_FAILURES 1
+/** How many seconds should we spend trying to connect to a requested
+ * rendezvous point before giving up? */
+#define MAX_REND_TIMEOUT 30
+
+/* String prefix for the signature of ESTABLISH_INTRO */
+#define ESTABLISH_INTRO_SIG_PREFIX "Tor establish-intro cell v1"
+
+/* The default HS time period length */
+#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
+
+/* Prefix of the onion address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
+/* Length of the checksum prefix minus the NUL terminated byte. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \
+ (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1)
+/* Length of the resulting checksum of the address. The construction of this
+ * checksum looks like:
+ * CHECKSUM = ".onion checksum" || PUBKEY || VERSION
+ * where VERSION is 1 byte. This is pre-hashing. */
+#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \
+ (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t))
+/* The amount of bytes we use from the address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2
+/* Length of the binary encoded service address which is of course before the
+ * base32 encoding. Construction is:
+ * PUBKEY || CHECKSUM || VERSION
+ * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */
+#define HS_SERVICE_ADDR_LEN \
+ (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t))
+/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the
+ * length ends up to 56 bytes (not counting the terminated NUL byte.) */
+#define HS_SERVICE_ADDR_LEN_BASE32 \
+ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5))
+
+/* The default HS time period length */
+#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
+/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
+/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
+#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
+
+/* Keyblinding parameter construction is as follow:
+ * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */
+#define HS_KEYBLIND_NONCE_PREFIX "key-blind"
+#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1)
+#define HS_KEYBLIND_NONCE_LEN \
+ (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t))
+
+/* Credential and subcredential prefix value. */
+#define HS_CREDENTIAL_PREFIX "credential"
+#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1)
+#define HS_SUBCREDENTIAL_PREFIX "subcredential"
+#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1)
+
+/* Node hidden service stored at index prefix value. */
+#define HS_INDEX_PREFIX "store-at-idx"
+#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1)
+
+/* Node hidden service directory index prefix value. */
+#define HSDIR_INDEX_PREFIX "node-idx"
+#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1)
+
+/* Prefix of the shared random value disaster mode. */
+#define HS_SRV_DISASTER_PREFIX "shared-random-disaster"
+#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1)
+
+/* Default value of number of hsdir replicas (hsdir_n_replicas). */
+#define HS_DEFAULT_HSDIR_N_REPLICAS 2
+/* Default value of hsdir spread store (hsdir_spread_store). */
+#define HS_DEFAULT_HSDIR_SPREAD_STORE 4
+/* Default value of hsdir spread fetch (hsdir_spread_fetch). */
+#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3
+
+/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is
+ * bigger than the 84 bytes needed for version 3 so we need to pad up to that
+ * length so it is indistinguishable between versions. */
+#define HS_LEGACY_RENDEZVOUS_CELL_SIZE \
+ (REND_COOKIE_LEN + DH1024_KEY_LEN + DIGEST_LEN)
+
+/* Type of authentication key used by an introduction point. */
+typedef enum {
+ HS_AUTH_KEY_TYPE_LEGACY = 1,
+ HS_AUTH_KEY_TYPE_ED25519 = 2,
+} hs_auth_key_type_t;
+
+/* Return value when adding an ephemeral service through the ADD_ONION
+ * control port command. Both v2 and v3 share these. */
+typedef enum {
+ RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */
+ RSAE_BADVIRTPORT = -4, /**< Invalid VIRTPORT/TARGET(s) */
+ RSAE_ADDREXISTS = -3, /**< Onion address collision */
+ RSAE_BADPRIVKEY = -2, /**< Invalid public key */
+ RSAE_INTERNAL = -1, /**< Internal error */
+ RSAE_OKAY = 0 /**< Service added as expected */
+} hs_service_add_ephemeral_status_t;
+
+/* Represents the mapping from a virtual port of a rendezvous service to a
+ * real port on some IP. */
+typedef struct rend_service_port_config_t {
+ /* The incoming HS virtual port we're mapping */
+ uint16_t virtual_port;
+ /* Is this an AF_UNIX port? */
+ unsigned int is_unix_addr:1;
+ /* The outgoing TCP port to use, if !is_unix_addr */
+ uint16_t real_port;
+ /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
+ tor_addr_t real_addr;
+ /* The socket path to connect to, if is_unix_addr */
+ char unix_addr[FLEXIBLE_ARRAY_MEMBER];
+} rend_service_port_config_t;
+
+void hs_init(void);
+void hs_free_all(void);
+
+void hs_cleanup_circ(circuit_t *circ);
+
+int hs_check_service_private_dir(const char *username, const char *path,
+ unsigned int dir_group_readable,
+ unsigned int create);
+int hs_get_service_max_rend_failures(void);
+
+char *hs_path_from_filename(const char *directory, const char *filename);
+void hs_build_address(const struct ed25519_public_key_t *key, uint8_t version,
+ char *addr_out);
+int hs_address_is_valid(const char *address);
+int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out);
+
+void hs_build_blinded_pubkey(const struct ed25519_public_key_t *pubkey,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ struct ed25519_public_key_t *pubkey_out);
+void hs_build_blinded_keypair(const struct ed25519_keypair_t *kp,
+ const uint8_t *secret, size_t secret_len,
+ uint64_t time_period_num,
+ struct ed25519_keypair_t *kp_out);
+int hs_service_requires_uptime_circ(const smartlist_t *ports);
+
+void rend_data_free_(rend_data_t *data);
+#define rend_data_free(data) \
+ FREE_AND_NULL(rend_data_t, rend_data_free_, (data))
+rend_data_t *rend_data_dup(const rend_data_t *data);
+rend_data_t *rend_data_client_create(const char *onion_address,
+ const char *desc_id,
+ const char *cookie,
+ rend_auth_type_t auth_type);
+rend_data_t *rend_data_service_create(const char *onion_address,
+ const char *pk_digest,
+ const uint8_t *cookie,
+ rend_auth_type_t auth_type);
+const char *rend_data_get_address(const rend_data_t *rend_data);
+const char *rend_data_get_desc_id(const rend_data_t *rend_data,
+ uint8_t replica, size_t *len_out);
+const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
+ size_t *len_out);
+
+routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
+
+void hs_get_subcredential(const struct ed25519_public_key_t *identity_pk,
+ const struct ed25519_public_key_t *blinded_pk,
+ uint8_t *subcred_out);
+
+uint64_t hs_get_previous_time_period_num(time_t now);
+uint64_t hs_get_time_period_num(time_t now);
+uint64_t hs_get_next_time_period_num(time_t now);
+time_t hs_get_start_time_of_next_time_period(time_t now);
+
+link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec);
+
+MOCK_DECL(int, hs_in_period_between_tp_and_srv,
+ (const networkstatus_t *consensus, time_t now));
+
+uint8_t *hs_get_current_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+uint8_t *hs_get_previous_srv(uint64_t time_period_num,
+ const networkstatus_t *ns);
+
+void hs_build_hsdir_index(const struct ed25519_public_key_t *identity_pk,
+ const uint8_t *srv, uint64_t period_num,
+ uint8_t *hsdir_index_out);
+void hs_build_hs_index(uint64_t replica,
+ const struct ed25519_public_key_t *blinded_pk,
+ uint64_t period_num, uint8_t *hs_index_out);
+
+int32_t hs_get_hsdir_n_replicas(void);
+int32_t hs_get_hsdir_spread_fetch(void);
+int32_t hs_get_hsdir_spread_store(void);
+
+void hs_get_responsible_hsdirs(const struct ed25519_public_key_t *blinded_pk,
+ uint64_t time_period_num,
+ int use_second_hsdir_index,
+ int for_fetching, smartlist_t *responsible_dirs);
+routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs,
+ const char *req_key_str);
+
+time_t hs_hsdir_requery_period(const or_options_t *options);
+time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir,
+ const char *desc_id_base32,
+ time_t now, int set);
+void hs_clean_last_hid_serv_requests(time_t now);
+void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id);
+void hs_purge_last_hid_serv_requests(void);
+
+int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
+
+void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
+void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
+
+extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
+ const struct curve25519_public_key_t *onion_key,
+ int direct_conn);
+
+#ifdef HS_COMMON_PRIVATE
+
+STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
+
+/** The period for which a hidden service directory cannot be queried for
+ * the same descriptor ID again. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60)
+/** Test networks generate a new consensus every 5 or 10 seconds.
+ * So allow them to requery HSDirs much faster. */
+#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5)
+
+#ifdef TOR_UNIT_TESTS
+
+STATIC strmap_t *get_last_hid_serv_requests(void);
+STATIC uint64_t get_time_period_length(void);
+
+STATIC uint8_t *get_first_cached_disaster_srv(void);
+STATIC uint8_t *get_second_cached_disaster_srv(void);
+
+#endif /* defined(TOR_UNIT_TESTS) */
+
+#endif /* defined(HS_COMMON_PRIVATE) */
+
+#endif /* !defined(TOR_HS_COMMON_H) */
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
new file mode 100644
index 0000000000..cb55faa9d5
--- /dev/null
+++ b/src/feature/hs/hs_config.c
@@ -0,0 +1,591 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.c
+ * \brief Implement hidden service configuration subsystem.
+ *
+ * \details
+ *
+ * This file has basically one main entry point: hs_config_service_all(). It
+ * takes the torrc options and configure hidden service from it. In validate
+ * mode, nothing is added to the global service list or keys are not generated
+ * nor loaded.
+ *
+ * A service is configured in two steps. It is first created using the tor
+ * options and then put in a staging list. It will stay there until
+ * hs_service_load_all_keys() is called. That function is responsible to
+ * load/generate the keys for the service in the staging list and if
+ * successful, transfert the service to the main global service list where
+ * at that point it is ready to be used.
+ *
+ * Configuration functions are per-version and there is a main generic one for
+ * every option that is common to all version (config_generic_service).
+ **/
+
+#define HS_CONFIG_PRIVATE
+
+#include "or/hs_common.h"
+#include "or/hs_config.h"
+#include "or/hs_service.h"
+#include "or/rendservice.h"
+#include "lib/encoding/confline.h"
+#include "or/or_options_st.h"
+
+/* Using the given list of services, stage them into our global state. Every
+ * service version are handled. This function can remove entries in the given
+ * service_list.
+ *
+ * Staging a service means that we take all services in service_list and we
+ * put them in the staging list (global) which acts as a temporary list that
+ * is used by the service loading key process. In other words, staging a
+ * service puts it in a list to be considered when loading the keys and then
+ * moved to the main global list. */
+static void
+stage_services(smartlist_t *service_list)
+{
+ tor_assert(service_list);
+
+ /* This is v2 specific. Trigger service pruning which will make sure the
+ * just configured services end up in the main global list. It should only
+ * be done in non validation mode because v2 subsystem handles service
+ * object differently. */
+ rend_service_prune_list();
+
+ /* Cleanup v2 service from the list, we don't need those object anymore
+ * because we validated them all against the others and we want to stage
+ * only >= v3 service. And remember, v2 has a different object type which is
+ * shadow copied from an hs_service_t type. */
+ SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) {
+ if (s->config.version == HS_VERSION_TWO) {
+ SMARTLIST_DEL_CURRENT(service_list, s);
+ hs_service_free(s);
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ /* This is >= v3 specific. Using the newly configured service list, stage
+ * them into our global state. Every object ownership is lost after. */
+ hs_service_stage_services(service_list);
+}
+
+/* Validate the given service against all service in the given list. If the
+ * service is ephemeral, this function ignores it. Services with the same
+ * directory path aren't allowed and will return an error. If a duplicate is
+ * found, 1 is returned else 0 if none found. */
+static int
+service_is_duplicate_in_list(const smartlist_t *service_list,
+ const hs_service_t *service)
+{
+ int ret = 0;
+
+ tor_assert(service_list);
+ tor_assert(service);
+
+ /* Ephemeral service don't have a directory configured so no need to check
+ * for a service in the list having the same path. */
+ if (service->config.is_ephemeral) {
+ goto end;
+ }
+
+ /* XXX: Validate if we have any service that has the given service dir path.
+ * This has two problems:
+ *
+ * a) It's O(n^2), but the same comment from the bottom of
+ * rend_config_services() should apply.
+ *
+ * b) We only compare directory paths as strings, so we can't
+ * detect two distinct paths that specify the same directory
+ * (which can arise from symlinks, case-insensitivity, bind
+ * mounts, etc.).
+ *
+ * It also can't detect that two separate Tor instances are trying
+ * to use the same HiddenServiceDir; for that, we would need a
+ * lock file. But this is enough to detect a simple mistake that
+ * at least one person has actually made. */
+ SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) {
+ if (!strcmp(s->config.directory_path, service->config.directory_path)) {
+ log_warn(LD_REND, "Another hidden service is already configured "
+ "for directory %s",
+ escaped(service->config.directory_path));
+ ret = 1;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ end:
+ return ret;
+}
+
+/* Helper function: Given an configuration option name, its value, a minimum
+ * min and a maxium max, parse the value as a uint64_t. On success, ok is set
+ * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be
+ * ignored. This function logs both on error and success. */
+static uint64_t
+helper_parse_uint64(const char *opt, const char *value, uint64_t min,
+ uint64_t max, int *ok)
+{
+ uint64_t ret = 0;
+
+ tor_assert(opt);
+ tor_assert(value);
+ tor_assert(ok);
+
+ *ok = 0;
+ ret = tor_parse_uint64(value, 10, min, max, ok, NULL);
+ if (!*ok) {
+ log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64
+ ", not %s.",
+ opt, min, max, value);
+ goto err;
+ }
+ log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret);
+ err:
+ return ret;
+}
+
+/* Return true iff the given options starting at line_ for a hidden service
+ * contains at least one invalid option. Each hidden service option don't
+ * apply to all versions so this function can find out. The line_ MUST start
+ * right after the HiddenServiceDir line of this service.
+ *
+ * This is mainly for usability so we can inform the user of any invalid
+ * option for the hidden service version instead of silently ignoring. */
+static int
+config_has_invalid_options(const config_line_t *line_,
+ const hs_service_t *service)
+{
+ int ret = 0;
+ const char **optlist;
+ const config_line_t *line;
+
+ tor_assert(service);
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+
+ /* List of options that a v3 service doesn't support thus must exclude from
+ * its configuration. */
+ const char *opts_exclude_v3[] = {
+ "HiddenServiceAuthorizeClient",
+ NULL /* End marker. */
+ };
+
+ /* Defining the size explicitly allows us to take advantage of the compiler
+ * which warns us if we ever bump the max version but forget to grow this
+ * array. The plus one is because we have a version 0 :). */
+ struct {
+ const char **list;
+ } exclude_lists[HS_VERSION_MAX + 1] = {
+ { NULL }, /* v0. */
+ { NULL }, /* v1. */
+ { NULL }, /* v2 */
+ { opts_exclude_v3 }, /* v3. */
+ };
+
+ optlist = exclude_lists[service->config.version].list;
+ if (optlist == NULL) {
+ /* No exclude options to look at for this version. */
+ goto end;
+ }
+ for (int i = 0; optlist[i]; i++) {
+ const char *opt = optlist[i];
+ for (line = line_; line; line = line->next) {
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ goto end;
+ }
+ if (!strcasecmp(line->key, opt)) {
+ log_warn(LD_CONFIG, "Hidden service option %s is incompatible with "
+ "version %" PRIu32 " of service in %s",
+ opt, service->config.version,
+ service->config.directory_path);
+ ret = 1;
+ /* Continue the loop so we can find all possible options. */
+ continue;
+ }
+ }
+ }
+ end:
+ return ret;
+}
+
+/* Validate service configuration. This is used when loading the configuration
+ * and once we've setup a service object, it's config object is passed to this
+ * function for further validation. This does not validate service key
+ * material. Return 0 if valid else -1 if invalid. */
+static int
+config_validate_service(const hs_service_config_t *config)
+{
+ tor_assert(config);
+
+ /* Amount of ports validation. */
+ if (!config->ports || smartlist_len(config->ports) == 0) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
+ escaped(config->directory_path));
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Configuration funcion for a version 3 service. The line_ must be pointing
+ * to the directive directly after a HiddenServiceDir. That way, when hitting
+ * the next HiddenServiceDir line or reaching the end of the list of lines, we
+ * know that we have to stop looking for more options. The given service
+ * object must be already allocated and passed through
+ * config_generic_service() prior to calling this function.
+ *
+ * Return 0 on success else a negative value. */
+static int
+config_service_v3(const config_line_t *line_,
+ hs_service_config_t *config)
+{
+ int have_num_ip = 0;
+ const char *dup_opt_seen = NULL;
+ const config_line_t *line;
+
+ tor_assert(config);
+
+ for (line = line_; line; line = line->next) {
+ int ok = 0;
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ break;
+ }
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ config->num_intro_points =
+ (unsigned int) helper_parse_uint64(line->key, line->value,
+ NUM_INTRO_POINTS_DEFAULT,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ &ok);
+ if (!ok || have_num_ip) {
+ if (have_num_ip)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_num_ip = 1;
+ continue;
+ }
+ }
+
+ /* We do not load the key material for the service at this stage. This is
+ * done later once tor can confirm that it is in a running state. */
+
+ /* We are about to return a fully configured service so do one last pass of
+ * validation at it. */
+ if (config_validate_service(config) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given options in line_ and options. This is
+ * called for any service regardless of its version which means that all
+ * directives in this function are generic to any service version. This
+ * function will also check the validity of the service directory path.
+ *
+ * The line_ must be pointing to the directive directly after a
+ * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or
+ * reaching the end of the list of lines, we know that we have to stop looking
+ * for more options.
+ *
+ * Return 0 on success else -1. */
+static int
+config_generic_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_t *service)
+{
+ int dir_seen = 0;
+ const config_line_t *line;
+ hs_service_config_t *config;
+ /* If this is set, we've seen a duplicate of this option. Keep the string
+ * so we can log the directive. */
+ const char *dup_opt_seen = NULL;
+ /* These variables will tell us if we ever have duplicate. */
+ int have_version = 0, have_allow_unknown_ports = 0;
+ int have_dir_group_read = 0, have_max_streams = 0;
+ int have_max_streams_close = 0;
+
+ tor_assert(line_);
+ tor_assert(options);
+ tor_assert(service);
+
+ /* Makes thing easier. */
+ config = &service->config;
+
+ /* The first line starts with HiddenServiceDir so we consider what's next is
+ * the configuration of the service. */
+ for (line = line_; line ; line = line->next) {
+ int ok = 0;
+
+ /* This indicate that we have a new service to configure. */
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* This function only configures one service at a time so if we've
+ * already seen one, stop right now. */
+ if (dir_seen) {
+ break;
+ }
+ /* Ok, we've seen one and we are about to configure it. */
+ dir_seen = 1;
+ config->directory_path = tor_strdup(line->value);
+ log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...",
+ escaped(config->directory_path));
+ continue;
+ }
+ if (BUG(!dir_seen)) {
+ goto err;
+ }
+ /* Version of the service. */
+ if (!strcasecmp(line->key, "HiddenServiceVersion")) {
+ service->config.version =
+ (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN,
+ HS_VERSION_MAX, &ok);
+ if (!ok || have_version) {
+ if (have_version)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_version = 1;
+ continue;
+ }
+ /* Virtual port. */
+ if (!strcasecmp(line->key, "HiddenServicePort")) {
+ char *err_msg = NULL;
+ /* XXX: Can we rename this? */
+ rend_service_port_config_t *portcfg =
+ rend_service_parse_port_config(line->value, " ", &err_msg);
+ if (!portcfg) {
+ if (err_msg) {
+ log_warn(LD_CONFIG, "%s", err_msg);
+ }
+ tor_free(err_msg);
+ goto err;
+ }
+ tor_assert(!err_msg);
+ smartlist_add(config->ports, portcfg);
+ log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
+ line->value, escaped(config->directory_path));
+ continue;
+ }
+ /* Do we allow unknown ports. */
+ if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
+ config->allow_unknown_ports =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_allow_unknown_ports) {
+ if (have_allow_unknown_ports)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_allow_unknown_ports = 1;
+ continue;
+ }
+ /* Directory group readable. */
+ if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) {
+ config->dir_group_readable =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_dir_group_read) {
+ if (have_dir_group_read)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_dir_group_read = 1;
+ continue;
+ }
+ /* Maximum streams per circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
+ config->max_streams_per_rdv_circuit =
+ helper_parse_uint64(line->key, line->value, 0,
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok);
+ if (!ok || have_max_streams) {
+ if (have_max_streams)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams = 1;
+ continue;
+ }
+ /* Maximum amount of streams before we close the circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
+ config->max_streams_close_circuit =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_max_streams_close) {
+ if (have_max_streams_close)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams_close = 1;
+ continue;
+ }
+ }
+
+ /* Check if we are configured in non anonymous mode meaning every service
+ * becomes a single onion service. */
+ if (rend_service_non_anonymous_mode_enabled(options)) {
+ config->is_single_onion = 1;
+ /* We will add support for IPv6-only v3 single onion services in a future
+ * Tor version. This won't catch "ReachableAddresses reject *4", but that
+ * option doesn't work anyway. */
+ if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) {
+ log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not "
+ "supported. Set HiddenServiceSingleHopMode 0 and "
+ "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1.");
+ goto err;
+ }
+ }
+
+ /* Success */
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given line and options. This function will
+ * call the corresponding configuration function for a specific service
+ * version and validate the service against the other ones. On success, add
+ * the service to the given list and return 0. On error, nothing is added to
+ * the list and a negative value is returned. */
+static int
+config_service(const config_line_t *line, const or_options_t *options,
+ smartlist_t *service_list)
+{
+ int ret;
+ hs_service_t *service = NULL;
+
+ tor_assert(line);
+ tor_assert(options);
+ tor_assert(service_list);
+
+ /* We have a new hidden service. */
+ service = hs_service_new(options);
+ /* We'll configure that service as a generic one and then pass it to a
+ * specific function according to the configured version number. */
+ if (config_generic_service(line, options, service) < 0) {
+ goto err;
+ }
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+ /* Before we configure the service on a per-version basis, we'll make
+ * sure that this set of options for a service are valid that is for
+ * instance an option only for v2 is not used for v3. */
+ if (config_has_invalid_options(line->next, service)) {
+ goto err;
+ }
+ /* Check permission on service directory that was just parsed. And this must
+ * be done regardless of the service version. Do not ask for the directory
+ * to be created, this is done when the keys are loaded because we could be
+ * in validation mode right now. */
+ if (hs_check_service_private_dir(options->User,
+ service->config.directory_path,
+ service->config.dir_group_readable,
+ 0) < 0) {
+ goto err;
+ }
+ /* Different functions are in charge of specific options for a version. We
+ * start just after the service directory line so once we hit another
+ * directory line, the function knows that it has to stop parsing. */
+ switch (service->config.version) {
+ case HS_VERSION_TWO:
+ ret = rend_config_service(line->next, options, &service->config);
+ break;
+ case HS_VERSION_THREE:
+ ret = config_service_v3(line->next, &service->config);
+ break;
+ default:
+ /* We do validate before if we support the parsed version. */
+ tor_assert_nonfatal_unreached();
+ goto err;
+ }
+ if (ret < 0) {
+ goto err;
+ }
+ /* We'll check if this service can be kept depending on the others
+ * configured previously. */
+ if (service_is_duplicate_in_list(service_list, service)) {
+ goto err;
+ }
+ /* Passes, add it to the given list. */
+ smartlist_add(service_list, service);
+ return 0;
+
+ err:
+ hs_service_free(service);
+ return -1;
+}
+
+/* From a set of <b>options</b>, setup every hidden service found. Return 0 on
+ * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and
+ * return as normal, but don't actually change the configured services. */
+int
+hs_config_service_all(const or_options_t *options, int validate_only)
+{
+ int dir_option_seen = 0, ret = -1;
+ const config_line_t *line;
+ smartlist_t *new_service_list = NULL;
+
+ tor_assert(options);
+
+ /* Newly configured service are put in that list which is then used for
+ * validation and staging for >= v3. */
+ new_service_list = smartlist_new();
+
+ for (line = options->RendConfigLines; line; line = line->next) {
+ /* Ignore all directives that aren't the start of a service. */
+ if (strcasecmp(line->key, "HiddenServiceDir")) {
+ if (!dir_option_seen) {
+ log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
+ line->key);
+ goto err;
+ }
+ continue;
+ }
+ /* Flag that we've seen a directory directive and we'll use it to make
+ * sure that the torrc options ordering is actually valid. */
+ dir_option_seen = 1;
+
+ /* Try to configure this service now. On success, it will be added to the
+ * list and validated against the service in that same list. */
+ if (config_service(line, options, new_service_list) < 0) {
+ goto err;
+ }
+ }
+
+ /* In non validation mode, we'll stage those services we just successfully
+ * configured. Service ownership is transferred from the list to the global
+ * state. If any service is invalid, it will be removed from the list and
+ * freed. All versions are handled in that function. */
+ if (!validate_only) {
+ stage_services(new_service_list);
+ } else {
+ /* We've just validated that we were able to build a clean working list of
+ * services. We don't need those objects anymore. */
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s,
+ hs_service_free(s));
+ /* For the v2 subsystem, the configuration function adds the service
+ * object to the staging list and it is transferred in the main list
+ * through the prunning process. In validation mode, we thus have to purge
+ * the staging list so it's not kept in memory as valid service. */
+ rend_service_free_staging_list();
+ }
+
+ /* Success. Note that the service list has no ownership of its content. */
+ ret = 0;
+ goto end;
+
+ err:
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s));
+
+ end:
+ smartlist_free(new_service_list);
+ /* Tor main should call the free all function on error. */
+ return ret;
+}
diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h
new file mode 100644
index 0000000000..461d58d384
--- /dev/null
+++ b/src/feature/hs/hs_config.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.h
+ * \brief Header file containing configuration ABI/API for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CONFIG_H
+#define TOR_HS_CONFIG_H
+
+#include "or/or.h"
+
+/* Max value for HiddenServiceMaxStreams */
+#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535
+/* Maximum number of intro points per version 3 services. */
+#define HS_CONFIG_V3_MAX_INTRO_POINTS 20
+
+/* API */
+
+int hs_config_service_all(const or_options_t *options, int validate_only);
+
+#endif /* !defined(TOR_HS_CONFIG_H) */
+
diff --git a/src/feature/hs/hs_control.c b/src/feature/hs/hs_control.c
new file mode 100644
index 0000000000..be456e8da4
--- /dev/null
+++ b/src/feature/hs/hs_control.c
@@ -0,0 +1,261 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_control.c
+ * \brief Contains control port event related code.
+ **/
+
+#include "or/or.h"
+#include "or/control.h"
+#include "lib/crypt_ops/crypto_format.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/hs_common.h"
+#include "or/hs_control.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_service.h"
+#include "or/nodelist.h"
+
+#include "or/node_st.h"
+#include "or/routerstatus_st.h"
+
+/* Send on the control port the "HS_DESC REQUESTED [...]" event.
+ *
+ * The onion_pk is the onion service public key, base64_blinded_pk is the
+ * base64 encoded blinded key for the service and hsdir_rs is the routerstatus
+ * object of the HSDir that this request is for. */
+void
+hs_control_desc_event_requested(const ed25519_public_key_t *onion_pk,
+ const char *base64_blinded_pk,
+ const routerstatus_t *hsdir_rs)
+{
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ const uint8_t *hsdir_index;
+ const node_t *hsdir_node;
+
+ tor_assert(onion_pk);
+ tor_assert(base64_blinded_pk);
+ tor_assert(hsdir_rs);
+
+ hs_build_address(onion_pk, HS_VERSION_THREE, onion_address);
+
+ /* Get the node from the routerstatus object to get the HSDir index used for
+ * this request. We can't have a routerstatus entry without a node and we
+ * can't pick a node without an hsdir_index. */
+ hsdir_node = node_get_by_id(hsdir_rs->identity_digest);
+ tor_assert(hsdir_node);
+ /* This is a fetch event. */
+ hsdir_index = hsdir_node->hsdir_index.fetch;
+
+ /* Trigger the event. */
+ control_event_hs_descriptor_requested(onion_address, REND_NO_AUTH,
+ hsdir_rs->identity_digest,
+ base64_blinded_pk,
+ hex_str((const char *) hsdir_index,
+ DIGEST256_LEN));
+ memwipe(onion_address, 0, sizeof(onion_address));
+}
+
+/* Send on the control port the "HS_DESC FAILED [...]" event.
+ *
+ * Using a directory connection identifier, the HSDir identity digest and a
+ * reason for the failure. None can be NULL. */
+void
+hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest,
+ const char *reason)
+{
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+
+ tor_assert(ident);
+ tor_assert(hsdir_id_digest);
+ tor_assert(reason);
+
+ /* Build onion address and encoded blinded key. */
+ IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk,
+ &ident->blinded_pk) < 0) {
+ return;
+ }
+ hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address);
+
+ control_event_hsv3_descriptor_failed(onion_address, base64_blinded_pk,
+ hsdir_id_digest, reason);
+}
+
+/* Send on the control port the "HS_DESC RECEIVED [...]" event.
+ *
+ * Using a directory connection identifier and the HSDir identity digest.
+ * None can be NULL. */
+void
+hs_control_desc_event_received(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest)
+{
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+
+ tor_assert(ident);
+ tor_assert(hsdir_id_digest);
+
+ /* Build onion address and encoded blinded key. */
+ IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk,
+ &ident->blinded_pk) < 0) {
+ return;
+ }
+ hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address);
+
+ control_event_hsv3_descriptor_received(onion_address, base64_blinded_pk,
+ hsdir_id_digest);
+}
+
+/* Send on the control port the "HS_DESC CREATED [...]" event.
+ *
+ * Using the onion address of the descriptor's service and the blinded public
+ * key of the descriptor as a descriptor ID. None can be NULL. */
+void
+hs_control_desc_event_created(const char *onion_address,
+ const ed25519_public_key_t *blinded_pk)
+{
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+
+ tor_assert(onion_address);
+ tor_assert(blinded_pk);
+
+ /* Build base64 encoded blinded key. */
+ IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) {
+ return;
+ }
+
+ /* Version 3 doesn't use the replica number in its descriptor ID computation
+ * so we pass negative value so the control port subsystem can ignore it. */
+ control_event_hs_descriptor_created(onion_address, base64_blinded_pk, -1);
+}
+
+/* Send on the control port the "HS_DESC UPLOAD [...]" event.
+ *
+ * Using the onion address of the descriptor's service, the HSDir identity
+ * digest, the blinded public key of the descriptor as a descriptor ID and the
+ * HSDir index for this particular request. None can be NULL. */
+void
+hs_control_desc_event_upload(const char *onion_address,
+ const char *hsdir_id_digest,
+ const ed25519_public_key_t *blinded_pk,
+ const uint8_t *hsdir_index)
+{
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+
+ tor_assert(onion_address);
+ tor_assert(hsdir_id_digest);
+ tor_assert(blinded_pk);
+ tor_assert(hsdir_index);
+
+ /* Build base64 encoded blinded key. */
+ IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) {
+ return;
+ }
+
+ control_event_hs_descriptor_upload(onion_address, hsdir_id_digest,
+ base64_blinded_pk,
+ hex_str((const char *) hsdir_index,
+ DIGEST256_LEN));
+}
+
+/* Send on the control port the "HS_DESC UPLOADED [...]" event.
+ *
+ * Using the directory connection identifier and the HSDir identity digest.
+ * None can be NULL. */
+void
+hs_control_desc_event_uploaded(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest)
+{
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ tor_assert(ident);
+ tor_assert(hsdir_id_digest);
+
+ hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address);
+
+ control_event_hs_descriptor_uploaded(hsdir_id_digest, onion_address);
+}
+
+/* Send on the control port the "HS_DESC_CONTENT [...]" event.
+ *
+ * Using the directory connection identifier, the HSDir identity digest and
+ * the body of the descriptor (as it was received from the directory). None
+ * can be NULL. */
+void
+hs_control_desc_event_content(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest,
+ const char *body)
+{
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+ char base64_blinded_pk[ED25519_BASE64_LEN + 1];
+
+ tor_assert(ident);
+ tor_assert(hsdir_id_digest);
+
+ /* Build onion address and encoded blinded key. */
+ IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk,
+ &ident->blinded_pk) < 0) {
+ return;
+ }
+ hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address);
+
+ control_event_hs_descriptor_content(onion_address, base64_blinded_pk,
+ hsdir_id_digest, body);
+}
+
+/* Handle the "HSPOST [...]" command. The body is an encoded descriptor for
+ * the given onion_address. The descriptor will be uploaded to each directory
+ * in hsdirs_rs. If NULL, the responsible directories for the current time
+ * period will be selected.
+ *
+ * Return -1 on if the descriptor plaintext section is not decodable. Else, 0
+ * on success. */
+int
+hs_control_hspost_command(const char *body, const char *onion_address,
+ const smartlist_t *hsdirs_rs)
+{
+ int ret = -1;
+ ed25519_public_key_t identity_pk;
+ hs_desc_plaintext_data_t plaintext;
+ smartlist_t *hsdirs = NULL;
+
+ tor_assert(body);
+ tor_assert(onion_address);
+
+ /* This can't fail because we require the caller to pass us a valid onion
+ * address that has passed hs_address_is_valid(). */
+ if (BUG(hs_parse_address(onion_address, &identity_pk, NULL, NULL) < 0)) {
+ goto done; // LCOV_EXCL_LINE
+ }
+
+ /* Only decode the plaintext part which is what the directory will do to
+ * validate before caching. */
+ if (hs_desc_decode_plaintext(body, &plaintext) < 0) {
+ goto done;
+ }
+
+ /* No HSDir(s) given, we'll compute what the current ones should be. */
+ if (hsdirs_rs == NULL) {
+ hsdirs = smartlist_new();
+ hs_get_responsible_hsdirs(&plaintext.blinded_pubkey,
+ hs_get_time_period_num(0),
+ 0, /* Always the current descriptor which uses
+ * the first hsdir index. */
+ 0, /* It is for storing on a directory. */
+ hsdirs);
+ hsdirs_rs = hsdirs;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(hsdirs_rs, const routerstatus_t *, rs) {
+ hs_service_upload_desc_to_dir(body, plaintext.version, &identity_pk,
+ &plaintext.blinded_pubkey, rs);
+ } SMARTLIST_FOREACH_END(rs);
+ ret = 0;
+
+ done:
+ /* We don't have ownership of the objects in this list. */
+ smartlist_free(hsdirs);
+ return ret;
+}
diff --git a/src/feature/hs/hs_control.h b/src/feature/hs/hs_control.h
new file mode 100644
index 0000000000..040ce4a793
--- /dev/null
+++ b/src/feature/hs/hs_control.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_control.h
+ * \brief Header file containing control port event related code.
+ **/
+
+#ifndef TOR_HS_CONTROL_H
+#define TOR_HS_CONTROL_H
+
+#include "or/hs_ident.h"
+
+/* Event "HS_DESC REQUESTED [...]" */
+void hs_control_desc_event_requested(const ed25519_public_key_t *onion_pk,
+ const char *base64_blinded_pk,
+ const routerstatus_t *hsdir_rs);
+
+/* Event "HS_DESC FAILED [...]" */
+void hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest,
+ const char *reason);
+
+/* Event "HS_DESC RECEIVED [...]" */
+void hs_control_desc_event_received(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest);
+
+/* Event "HS_DESC CREATED [...]" */
+void hs_control_desc_event_created(const char *onion_address,
+ const ed25519_public_key_t *blinded_pk);
+
+/* Event "HS_DESC UPLOAD [...]" */
+void hs_control_desc_event_upload(const char *onion_address,
+ const char *hsdir_id_digest,
+ const ed25519_public_key_t *blinded_pk,
+ const uint8_t *hsdir_index);
+
+/* Event "HS_DESC UPLOADED [...]" */
+void hs_control_desc_event_uploaded(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest);
+
+/* Event "HS_DESC_CONTENT [...]" */
+void hs_control_desc_event_content(const hs_ident_dir_conn_t *ident,
+ const char *hsdir_id_digest,
+ const char *body);
+
+/* Command "HSPOST [...]" */
+int hs_control_hspost_command(const char *body, const char *onion_address,
+ const smartlist_t *hsdirs_rs);
+
+#endif /* !defined(TOR_HS_CONTROL_H) */
+
diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c
new file mode 100644
index 0000000000..5fd8971dc0
--- /dev/null
+++ b/src/feature/hs/hs_descriptor.c
@@ -0,0 +1,2611 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.c
+ * \brief Handle hidden service descriptor encoding/decoding.
+ *
+ * \details
+ * Here is a graphical depiction of an HS descriptor and its layers:
+ *
+ * +------------------------------------------------------+
+ * |DESCRIPTOR HEADER: |
+ * | hs-descriptor 3 |
+ * | descriptor-lifetime 180 |
+ * | ... |
+ * | superencrypted |
+ * |+---------------------------------------------------+ |
+ * ||SUPERENCRYPTED LAYER (aka OUTER ENCRYPTED LAYER): | |
+ * || desc-auth-type x25519 | |
+ * || desc-auth-ephemeral-key | |
+ * || auth-client | |
+ * || auth-client | |
+ * || ... | |
+ * || encrypted | |
+ * ||+-------------------------------------------------+| |
+ * |||ENCRYPTED LAYER (aka INNER ENCRYPTED LAYER): || |
+ * ||| create2-formats || |
+ * ||| intro-auth-required || |
+ * ||| introduction-point || |
+ * ||| introduction-point || |
+ * ||| ... || |
+ * ||+-------------------------------------------------+| |
+ * |+---------------------------------------------------+ |
+ * +------------------------------------------------------+
+ *
+ * The DESCRIPTOR HEADER section is completely unencrypted and contains generic
+ * descriptor metadata.
+ *
+ * The SUPERENCRYPTED LAYER section is the first layer of encryption, and it's
+ * encrypted using the blinded public key of the hidden service to protect
+ * against entities who don't know its onion address. The clients of the hidden
+ * service know its onion address and blinded public key, whereas third-parties
+ * (like HSDirs) don't know it (except if it's a public hidden service).
+ *
+ * The ENCRYPTED LAYER section is the second layer of encryption, and it's
+ * encrypted using the client authorization key material (if those exist). When
+ * client authorization is enabled, this second layer of encryption protects
+ * the descriptor content from unauthorized entities. If client authorization
+ * is disabled, this second layer of encryption does not provide any extra
+ * security but is still present. The plaintext of this layer contains all the
+ * information required to connect to the hidden service like its list of
+ * introduction points.
+ **/
+
+/* For unit tests.*/
+#define HS_DESCRIPTOR_PRIVATE
+
+#include "or/or.h"
+#include "trunnel/ed25519_cert.h" /* Trunnel interface. */
+#include "or/hs_descriptor.h"
+#include "or/circuitbuild.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/parsecommon.h"
+#include "or/rendcache.h"
+#include "or/hs_cache.h"
+#include "or/hs_config.h"
+#include "or/torcert.h" /* tor_cert_encode_ed22519() */
+#include "lib/memarea/memarea.h"
+#include "lib/crypt_ops/crypto_format.h"
+
+#include "or/extend_info_st.h"
+
+/* Constant string value used for the descriptor format. */
+#define str_hs_desc "hs-descriptor"
+#define str_desc_cert "descriptor-signing-key-cert"
+#define str_rev_counter "revision-counter"
+#define str_superencrypted "superencrypted"
+#define str_encrypted "encrypted"
+#define str_signature "signature"
+#define str_lifetime "descriptor-lifetime"
+/* Constant string value for the encrypted part of the descriptor. */
+#define str_create2_formats "create2-formats"
+#define str_intro_auth_required "intro-auth-required"
+#define str_single_onion "single-onion-service"
+#define str_intro_point "introduction-point"
+#define str_ip_onion_key "onion-key"
+#define str_ip_auth_key "auth-key"
+#define str_ip_enc_key "enc-key"
+#define str_ip_enc_key_cert "enc-key-cert"
+#define str_ip_legacy_key "legacy-key"
+#define str_ip_legacy_key_cert "legacy-key-cert"
+#define str_intro_point_start "\n" str_intro_point " "
+/* Constant string value for the construction to encrypt the encrypted data
+ * section. */
+#define str_enc_const_superencryption "hsdir-superencrypted-data"
+#define str_enc_const_encryption "hsdir-encrypted-data"
+/* Prefix required to compute/verify HS desc signatures */
+#define str_desc_sig_prefix "Tor onion service descriptor sig v3"
+#define str_desc_auth_type "desc-auth-type"
+#define str_desc_auth_key "desc-auth-ephemeral-key"
+#define str_desc_auth_client "auth-client"
+#define str_encrypted "encrypted"
+
+/* Authentication supported types. */
+static const struct {
+ hs_desc_auth_type_t type;
+ const char *identifier;
+} intro_auth_types[] = {
+ { HS_DESC_AUTH_ED25519, "ed25519" },
+ /* Indicate end of array. */
+ { 0, NULL }
+};
+
+/* Descriptor ruleset. */
+static token_rule_t hs_desc_v3_token_table[] = {
+ T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ),
+ T1(str_lifetime, R3_DESC_LIFETIME, EQ(1), NO_OBJ),
+ T1(str_desc_cert, R3_DESC_SIGNING_CERT, NO_ARGS, NEED_OBJ),
+ T1(str_rev_counter, R3_REVISION_COUNTER, EQ(1), NO_OBJ),
+ T1(str_superencrypted, R3_SUPERENCRYPTED, NO_ARGS, NEED_OBJ),
+ T1_END(str_signature, R3_SIGNATURE, EQ(1), NO_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the superencrypted section. */
+static token_rule_t hs_desc_superencrypted_v3_token_table[] = {
+ T1_START(str_desc_auth_type, R3_DESC_AUTH_TYPE, GE(1), NO_OBJ),
+ T1(str_desc_auth_key, R3_DESC_AUTH_KEY, GE(1), NO_OBJ),
+ T1N(str_desc_auth_client, R3_DESC_AUTH_CLIENT, GE(3), NO_OBJ),
+ T1(str_encrypted, R3_ENCRYPTED, NO_ARGS, NEED_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the encrypted section. */
+static token_rule_t hs_desc_encrypted_v3_token_table[] = {
+ T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ),
+ T01(str_intro_auth_required, R3_INTRO_AUTH_REQUIRED, ARGS, NO_OBJ),
+ T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the introduction points section. */
+static token_rule_t hs_desc_intro_point_v3_token_table[] = {
+ T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ),
+ T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK),
+ T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ),
+ T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK),
+ T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK),
+ T01(str_ip_legacy_key, R3_INTRO_LEGACY_KEY, ARGS, NEED_KEY_1024),
+ T01(str_ip_legacy_key_cert, R3_INTRO_LEGACY_KEY_CERT, ARGS, OBJ_OK),
+ END_OF_TABLE
+};
+
+/* Free the content of the plaintext section of a descriptor. */
+STATIC void
+desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->superencrypted_blob) {
+ tor_free(desc->superencrypted_blob);
+ }
+ tor_cert_free(desc->signing_key_cert);
+
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Free the content of the encrypted section of a descriptor. */
+static void
+desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->intro_auth_types) {
+ SMARTLIST_FOREACH(desc->intro_auth_types, char *, a, tor_free(a));
+ smartlist_free(desc->intro_auth_types);
+ }
+ if (desc->intro_points) {
+ SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
+ hs_desc_intro_point_free(ip));
+ smartlist_free(desc->intro_points);
+ }
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out.
+ * We use SHA3-256 for the MAC computation.
+ * This function can't fail. */
+static void
+build_mac(const uint8_t *mac_key, size_t mac_key_len,
+ const uint8_t *salt, size_t salt_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ uint8_t *mac_out, size_t mac_len)
+{
+ crypto_digest_t *digest;
+
+ const uint64_t mac_len_netorder = tor_htonll(mac_key_len);
+ const uint64_t salt_len_netorder = tor_htonll(salt_len);
+
+ tor_assert(mac_key);
+ tor_assert(salt);
+ tor_assert(encrypted);
+ tor_assert(mac_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ /* As specified in section 2.5 of proposal 224, first add the mac key
+ * then add the salt first and then the encrypted section. */
+
+ crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len);
+ crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) salt, salt_len);
+ crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len);
+ crypto_digest_get_digest(digest, (char *) mac_out, mac_len);
+ crypto_digest_free(digest);
+}
+
+/* Using a given decriptor object, build the secret input needed for the
+ * KDF and put it in the dst pointer which is an already allocated buffer
+ * of size dstlen. */
+static void
+build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
+{
+ size_t offset = 0;
+
+ tor_assert(desc);
+ tor_assert(dst);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen);
+
+ /* XXX use the destination length as the memcpy length */
+ /* Copy blinded public key. */
+ memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey,
+ sizeof(desc->plaintext_data.blinded_pubkey.pubkey));
+ offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey);
+ /* Copy subcredential. */
+ memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
+ offset += sizeof(desc->subcredential);
+ /* Copy revision counter value. */
+ set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
+ offset += sizeof(uint64_t);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
+}
+
+/* Do the KDF construction and put the resulting data in key_out which is of
+ * key_out_len length. It uses SHAKE-256 as specified in the spec. */
+static void
+build_kdf_key(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_out_len,
+ int is_superencrypted_layer)
+{
+ uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN];
+ crypto_xof_t *xof;
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+
+ /* Build the secret input for the KDF computation. */
+ build_secret_input(desc, secret_input, sizeof(secret_input));
+
+ xof = crypto_xof_new();
+ /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */
+ crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input));
+ crypto_xof_add_bytes(xof, salt, salt_len);
+
+ /* Feed in the right string constant based on the desc layer */
+ if (is_superencrypted_layer) {
+ crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_superencryption,
+ strlen(str_enc_const_superencryption));
+ } else {
+ crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_const_encryption,
+ strlen(str_enc_const_encryption));
+ }
+
+ /* Eat from our KDF. */
+ crypto_xof_squeeze_bytes(xof, key_out, key_out_len);
+ crypto_xof_free(xof);
+ memwipe(secret_input, 0, sizeof(secret_input));
+}
+
+/* Using the given descriptor and salt, run it through our KDF function and
+ * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out.
+ * This function can't fail. */
+static void
+build_secret_key_iv_mac(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_len,
+ uint8_t *iv_out, size_t iv_len,
+ uint8_t *mac_out, size_t mac_len,
+ int is_superencrypted_layer)
+{
+ size_t offset = 0;
+ uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN];
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+ tor_assert(iv_out);
+ tor_assert(mac_out);
+
+ build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key),
+ is_superencrypted_layer);
+ /* Copy the bytes we need for both the secret key and IV. */
+ memcpy(key_out, kdf_key, key_len);
+ offset += key_len;
+ memcpy(iv_out, kdf_key + offset, iv_len);
+ offset += iv_len;
+ memcpy(mac_out, kdf_key + offset, mac_len);
+ /* Extra precaution to make sure we are not out of bound. */
+ tor_assert((offset + mac_len) == sizeof(kdf_key));
+ memwipe(kdf_key, 0, sizeof(kdf_key));
+}
+
+/* === ENCODING === */
+
+/* Encode the given link specifier objects into a newly allocated string.
+ * This can't fail so caller can always assume a valid string being
+ * returned. */
+STATIC char *
+encode_link_specifiers(const smartlist_t *specs)
+{
+ char *encoded_b64 = NULL;
+ link_specifier_list_t *lslist = link_specifier_list_new();
+
+ tor_assert(specs);
+ /* No link specifiers is a code flow error, can't happen. */
+ tor_assert(smartlist_len(specs) > 0);
+ tor_assert(smartlist_len(specs) <= UINT8_MAX);
+
+ link_specifier_list_set_n_spec(lslist, smartlist_len(specs));
+
+ SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *,
+ spec) {
+ link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec);
+ if (ls) {
+ link_specifier_list_add_spec(lslist, ls);
+ }
+ } SMARTLIST_FOREACH_END(spec);
+
+ {
+ uint8_t *encoded;
+ ssize_t encoded_len, encoded_b64_len, ret;
+
+ encoded_len = link_specifier_list_encoded_len(lslist);
+ tor_assert(encoded_len > 0);
+ encoded = tor_malloc_zero(encoded_len);
+ ret = link_specifier_list_encode(encoded, encoded_len, lslist);
+ tor_assert(ret == encoded_len);
+
+ /* Base64 encode our binary format. Add extra NUL byte for the base64
+ * encoded value. */
+ encoded_b64_len = base64_encode_size(encoded_len, 0) + 1;
+ encoded_b64 = tor_malloc_zero(encoded_b64_len);
+ ret = base64_encode(encoded_b64, encoded_b64_len, (const char *) encoded,
+ encoded_len, 0);
+ tor_assert(ret == (encoded_b64_len - 1));
+ tor_free(encoded);
+ }
+
+ link_specifier_list_free(lslist);
+ return encoded_b64;
+}
+
+/* Encode an introduction point legacy key and certificate. Return a newly
+ * allocated string with it. On failure, return NULL. */
+static char *
+encode_legacy_key(const hs_desc_intro_point_t *ip)
+{
+ char *key_str, b64_cert[256], *encoded = NULL;
+ size_t key_str_len;
+
+ tor_assert(ip);
+
+ /* Encode cross cert. */
+ if (base64_encode(b64_cert, sizeof(b64_cert),
+ (const char *) ip->legacy.cert.encoded,
+ ip->legacy.cert.len, BASE64_ENCODE_MULTILINE) < 0) {
+ log_warn(LD_REND, "Unable to encode legacy crosscert.");
+ goto done;
+ }
+ /* Convert the encryption key to PEM format NUL terminated. */
+ if (crypto_pk_write_public_key_to_string(ip->legacy.key, &key_str,
+ &key_str_len) < 0) {
+ log_warn(LD_REND, "Unable to encode legacy encryption key.");
+ goto done;
+ }
+ tor_asprintf(&encoded,
+ "%s \n%s" /* Newline is added by the call above. */
+ "%s\n"
+ "-----BEGIN CROSSCERT-----\n"
+ "%s"
+ "-----END CROSSCERT-----",
+ str_ip_legacy_key, key_str,
+ str_ip_legacy_key_cert, b64_cert);
+ tor_free(key_str);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point encryption key and certificate. Return a newly
+ * allocated string with it. On failure, return NULL. */
+static char *
+encode_enc_key(const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL, *encoded_cert;
+ char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+
+ tor_assert(ip);
+
+ /* Base64 encode the encryption key for the "enc-key" field. */
+ if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) {
+ goto done;
+ }
+ if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) {
+ goto done;
+ }
+ tor_asprintf(&encoded,
+ "%s ntor %s\n"
+ "%s\n%s",
+ str_ip_enc_key, key_b64,
+ str_ip_enc_key_cert, encoded_cert);
+ tor_free(encoded_cert);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point onion key. Return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_onion_key(const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL;
+ char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+
+ tor_assert(ip);
+
+ /* Base64 encode the encryption key for the "onion-key" field. */
+ if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) {
+ goto done;
+ }
+ tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64);
+
+ done:
+ return encoded;
+}
+
+/* Encode an introduction point object and return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_intro_point(const ed25519_public_key_t *sig_key,
+ const hs_desc_intro_point_t *ip)
+{
+ char *encoded_ip = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(ip);
+ tor_assert(sig_key);
+
+ /* Encode link specifier. */
+ {
+ char *ls_str = encode_link_specifiers(ip->link_specifiers);
+ smartlist_add_asprintf(lines, "%s %s", str_intro_point, ls_str);
+ tor_free(ls_str);
+ }
+
+ /* Onion key encoding. */
+ {
+ char *encoded_onion_key = encode_onion_key(ip);
+ if (encoded_onion_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_onion_key);
+ tor_free(encoded_onion_key);
+ }
+
+ /* Authentication key encoding. */
+ {
+ char *encoded_cert;
+ if (tor_cert_encode_ed22519(ip->auth_key_cert, &encoded_cert) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s\n%s", str_ip_auth_key, encoded_cert);
+ tor_free(encoded_cert);
+ }
+
+ /* Encryption key encoding. */
+ {
+ char *encoded_enc_key = encode_enc_key(ip);
+ if (encoded_enc_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_enc_key);
+ tor_free(encoded_enc_key);
+ }
+
+ /* Legacy key if any. */
+ if (ip->legacy.key != NULL) {
+ /* Strong requirement else the IP creation was badly done. */
+ tor_assert(ip->legacy.cert.encoded);
+ char *encoded_legacy_key = encode_legacy_key(ip);
+ if (encoded_legacy_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_legacy_key);
+ tor_free(encoded_legacy_key);
+ }
+
+ /* Join them all in one blob of text. */
+ encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL);
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return encoded_ip;
+}
+
+/* Given a source length, return the new size including padding for the
+ * plaintext encryption. */
+static size_t
+compute_padded_plaintext_length(size_t plaintext_len)
+{
+ size_t plaintext_padded_len;
+ const int padding_block_length = HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE;
+
+ /* Make sure we won't overflow. */
+ tor_assert(plaintext_len <= (SIZE_T_CEILING - padding_block_length));
+
+ /* Get the extra length we need to add. For example, if srclen is 10200
+ * bytes, this will expand to (2 * 10k) == 20k thus an extra 9800 bytes. */
+ plaintext_padded_len = CEIL_DIV(plaintext_len, padding_block_length) *
+ padding_block_length;
+ /* Can never be extra careful. Make sure we are _really_ padded. */
+ tor_assert(!(plaintext_padded_len % padding_block_length));
+ return plaintext_padded_len;
+}
+
+/* Given a buffer, pad it up to the encrypted section padding requirement. Set
+ * the newly allocated string in padded_out and return the length of the
+ * padded buffer. */
+STATIC size_t
+build_plaintext_padding(const char *plaintext, size_t plaintext_len,
+ uint8_t **padded_out)
+{
+ size_t padded_len;
+ uint8_t *padded;
+
+ tor_assert(plaintext);
+ tor_assert(padded_out);
+
+ /* Allocate the final length including padding. */
+ padded_len = compute_padded_plaintext_length(plaintext_len);
+ tor_assert(padded_len >= plaintext_len);
+ padded = tor_malloc_zero(padded_len);
+
+ memcpy(padded, plaintext, plaintext_len);
+ *padded_out = padded;
+ return padded_len;
+}
+
+/* Using a key, IV and plaintext data of length plaintext_len, create the
+ * encrypted section by encrypting it and setting encrypted_out with the
+ * data. Return size of the encrypted data buffer. */
+static size_t
+build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext,
+ size_t plaintext_len, uint8_t **encrypted_out,
+ int is_superencrypted_layer)
+{
+ size_t encrypted_len;
+ uint8_t *padded_plaintext, *encrypted;
+ crypto_cipher_t *cipher;
+
+ tor_assert(key);
+ tor_assert(iv);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* If we are encrypting the middle layer of the descriptor, we need to first
+ pad the plaintext */
+ if (is_superencrypted_layer) {
+ encrypted_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ /* Extra precautions that we have a valid padding length. */
+ tor_assert(!(encrypted_len % HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE));
+ } else { /* No padding required for inner layers */
+ padded_plaintext = tor_memdup(plaintext, plaintext_len);
+ encrypted_len = plaintext_len;
+ }
+
+ /* This creates a cipher for AES. It can't fail. */
+ cipher = crypto_cipher_new_with_iv_and_bits(key, iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* We use a stream cipher so the encrypted length will be the same as the
+ * plaintext padded length. */
+ encrypted = tor_malloc_zero(encrypted_len);
+ /* This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) encrypted,
+ (const char *) padded_plaintext, encrypted_len);
+ *encrypted_out = encrypted;
+ /* Cleanup. */
+ crypto_cipher_free(cipher);
+ tor_free(padded_plaintext);
+ return encrypted_len;
+}
+
+/* Encrypt the given <b>plaintext</b> buffer using <b>desc</b> to get the
+ * keys. Set encrypted_out with the encrypted data and return the length of
+ * it. <b>is_superencrypted_layer</b> is set if this is the outer encrypted
+ * layer of the descriptor. */
+static size_t
+encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
+ char **encrypted_out, int is_superencrypted_layer)
+{
+ char *final_blob;
+ size_t encrypted_len, final_blob_len, offset = 0;
+ uint8_t *encrypted;
+ uint8_t salt[HS_DESC_ENCRYPTED_SALT_LEN];
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN];
+
+ tor_assert(desc);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* Get our salt. The returned bytes are already hashed. */
+ crypto_strongest_rand(salt, sizeof(salt));
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the encryption. */
+ build_secret_key_iv_mac(desc, salt, sizeof(salt),
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key),
+ is_superencrypted_layer);
+
+ /* Build the encrypted part that is do the actual encryption. */
+ encrypted_len = build_encrypted(secret_key, secret_iv, plaintext,
+ strlen(plaintext), &encrypted,
+ is_superencrypted_layer);
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ /* This construction is specified in section 2.5 of proposal 224. */
+ final_blob_len = sizeof(salt) + encrypted_len + DIGEST256_LEN;
+ final_blob = tor_malloc_zero(final_blob_len);
+
+ /* Build the MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, sizeof(salt),
+ encrypted, encrypted_len, mac, sizeof(mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+
+ /* The salt is the first value. */
+ memcpy(final_blob, salt, sizeof(salt));
+ offset = sizeof(salt);
+ /* Second value is the encrypted data. */
+ memcpy(final_blob + offset, encrypted, encrypted_len);
+ offset += encrypted_len;
+ /* Third value is the MAC. */
+ memcpy(final_blob + offset, mac, sizeof(mac));
+ offset += sizeof(mac);
+ /* Cleanup the buffers. */
+ memwipe(salt, 0, sizeof(salt));
+ memwipe(encrypted, 0, encrypted_len);
+ tor_free(encrypted);
+ /* Extra precaution. */
+ tor_assert(offset == final_blob_len);
+
+ *encrypted_out = final_blob;
+ return final_blob_len;
+}
+
+/* Create and return a string containing a fake client-auth entry. It's the
+ * responsibility of the caller to free the returned string. This function will
+ * never fail. */
+static char *
+get_fake_auth_client_str(void)
+{
+ char *auth_client_str = NULL;
+ /* We are gonna fill these arrays with fake base64 data. They are all double
+ * the size of their binary representation to fit the base64 overhead. */
+ char client_id_b64[8*2];
+ char iv_b64[16*2];
+ char encrypted_cookie_b64[16*2];
+ int retval;
+
+ /* This is a macro to fill a field with random data and then base64 it. */
+#define FILL_WITH_FAKE_DATA_AND_BASE64(field) STMT_BEGIN \
+ crypto_rand((char *)field, sizeof(field)); \
+ retval = base64_encode_nopad(field##_b64, sizeof(field##_b64), \
+ field, sizeof(field)); \
+ tor_assert(retval > 0); \
+ STMT_END
+
+ { /* Get those fakes! */
+ uint8_t client_id[8]; /* fake client-id */
+ uint8_t iv[16]; /* fake IV (initialization vector) */
+ uint8_t encrypted_cookie[16]; /* fake encrypted cookie */
+
+ FILL_WITH_FAKE_DATA_AND_BASE64(client_id);
+ FILL_WITH_FAKE_DATA_AND_BASE64(iv);
+ FILL_WITH_FAKE_DATA_AND_BASE64(encrypted_cookie);
+ }
+
+ /* Build the final string */
+ tor_asprintf(&auth_client_str, "%s %s %s %s", str_desc_auth_client,
+ client_id_b64, iv_b64, encrypted_cookie_b64);
+
+#undef FILL_WITH_FAKE_DATA_AND_BASE64
+
+ return auth_client_str;
+}
+
+/** How many lines of "client-auth" we want in our descriptors; fake or not. */
+#define CLIENT_AUTH_ENTRIES_BLOCK_SIZE 16
+
+/** Create the "client-auth" part of the descriptor and return a
+ * newly-allocated string with it. It's the responsibility of the caller to
+ * free the returned string. */
+static char *
+get_fake_auth_client_lines(void)
+{
+ /* XXX: Client authorization is still not implemented, so all this function
+ does is make fake clients */
+ int i = 0;
+ smartlist_t *auth_client_lines = smartlist_new();
+ char *auth_client_lines_str = NULL;
+
+ /* Make a line for each fake client */
+ const int num_fake_clients = CLIENT_AUTH_ENTRIES_BLOCK_SIZE;
+ for (i = 0; i < num_fake_clients; i++) {
+ char *auth_client_str = get_fake_auth_client_str();
+ tor_assert(auth_client_str);
+ smartlist_add(auth_client_lines, auth_client_str);
+ }
+
+ /* Join all lines together to form final string */
+ auth_client_lines_str = smartlist_join_strings(auth_client_lines,
+ "\n", 1, NULL);
+ /* Cleanup the mess */
+ SMARTLIST_FOREACH(auth_client_lines, char *, a, tor_free(a));
+ smartlist_free(auth_client_lines);
+
+ return auth_client_lines_str;
+}
+
+/* Create the inner layer of the descriptor (which includes the intro points,
+ * etc.). Return a newly-allocated string with the layer plaintext, or NULL if
+ * an error occurred. It's the responsibility of the caller to free the
+ * returned string. */
+static char *
+get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc)
+{
+ char *encoded_str = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ /* Build the start of the section prior to the introduction points. */
+ {
+ if (!desc->encrypted_data.create2_ntor) {
+ log_err(LD_BUG, "HS desc doesn't have recognized handshake type.");
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
+ ONION_HANDSHAKE_TYPE_NTOR);
+
+ if (desc->encrypted_data.intro_auth_types &&
+ smartlist_len(desc->encrypted_data.intro_auth_types)) {
+ /* Put the authentication-required line. */
+ char *buf = smartlist_join_strings(desc->encrypted_data.intro_auth_types,
+ " ", 0, NULL);
+ smartlist_add_asprintf(lines, "%s %s\n", str_intro_auth_required, buf);
+ tor_free(buf);
+ }
+
+ if (desc->encrypted_data.single_onion_service) {
+ smartlist_add_asprintf(lines, "%s\n", str_single_onion);
+ }
+ }
+
+ /* Build the introduction point(s) section. */
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ char *encoded_ip = encode_intro_point(&desc->plaintext_data.signing_pubkey,
+ ip);
+ if (encoded_ip == NULL) {
+ log_err(LD_BUG, "HS desc intro point is malformed.");
+ goto err;
+ }
+ smartlist_add(lines, encoded_ip);
+ } SMARTLIST_FOREACH_END(ip);
+
+ /* Build the entire encrypted data section into one encoded plaintext and
+ * then encrypt it. */
+ encoded_str = smartlist_join_strings(lines, "", 0, NULL);
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+
+ return encoded_str;
+}
+
+/* Create the middle layer of the descriptor, which includes the client auth
+ * data and the encrypted inner layer (provided as a base64 string at
+ * <b>layer2_b64_ciphertext</b>). Return a newly-allocated string with the
+ * layer plaintext, or NULL if an error occurred. It's the responsibility of
+ * the caller to free the returned string. */
+static char *
+get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc,
+ const char *layer2_b64_ciphertext)
+{
+ char *layer1_str = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ /* XXX: Disclaimer: This function generates only _fake_ client auth
+ * data. Real client auth is not yet implemented, but client auth data MUST
+ * always be present in descriptors. In the future this function will be
+ * refactored to use real client auth data if they exist (#20700). */
+ (void) *desc;
+
+ /* Specify auth type */
+ smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_type, "x25519");
+
+ { /* Create fake ephemeral x25519 key */
+ char fake_key_base64[CURVE25519_BASE64_PADDED_LEN + 1];
+ curve25519_keypair_t fake_x25519_keypair;
+ if (curve25519_keypair_generate(&fake_x25519_keypair, 0) < 0) {
+ goto done;
+ }
+ if (curve25519_public_to_base64(fake_key_base64,
+ &fake_x25519_keypair.pubkey) < 0) {
+ goto done;
+ }
+ smartlist_add_asprintf(lines, "%s %s\n",
+ str_desc_auth_key, fake_key_base64);
+ /* No need to memwipe any of these fake keys. They will go unused. */
+ }
+
+ { /* Create fake auth-client lines. */
+ char *auth_client_lines = get_fake_auth_client_lines();
+ tor_assert(auth_client_lines);
+ smartlist_add(lines, auth_client_lines);
+ }
+
+ /* create encrypted section */
+ {
+ smartlist_add_asprintf(lines,
+ "%s\n"
+ "-----BEGIN MESSAGE-----\n"
+ "%s"
+ "-----END MESSAGE-----",
+ str_encrypted, layer2_b64_ciphertext);
+ }
+
+ layer1_str = smartlist_join_strings(lines, "", 0, NULL);
+
+ done:
+ SMARTLIST_FOREACH(lines, char *, a, tor_free(a));
+ smartlist_free(lines);
+
+ return layer1_str;
+}
+
+/* Encrypt <b>encoded_str</b> into an encrypted blob and then base64 it before
+ * returning it. <b>desc</b> is provided to derive the encryption
+ * keys. <b>is_superencrypted_layer</b> is set if <b>encoded_str</b> is the
+ * middle (superencrypted) layer of the descriptor. It's the responsibility of
+ * the caller to free the returned string. */
+static char *
+encrypt_desc_data_and_base64(const hs_descriptor_t *desc,
+ const char *encoded_str,
+ int is_superencrypted_layer)
+{
+ char *enc_b64;
+ ssize_t enc_b64_len, ret_len, enc_len;
+ char *encrypted_blob = NULL;
+
+ enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob,
+ is_superencrypted_layer);
+ /* Get the encoded size plus a NUL terminating byte. */
+ enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1;
+ enc_b64 = tor_malloc_zero(enc_b64_len);
+ /* Base64 the encrypted blob before returning it. */
+ ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len,
+ BASE64_ENCODE_MULTILINE);
+ /* Return length doesn't count the NUL byte. */
+ tor_assert(ret_len == (enc_b64_len - 1));
+ tor_free(encrypted_blob);
+
+ return enc_b64;
+}
+
+/* Generate and encode the superencrypted portion of <b>desc</b>. This also
+ * involves generating the encrypted portion of the descriptor, and performing
+ * the superencryption. A newly allocated NUL-terminated string pointer
+ * containing the encrypted encoded blob is put in encrypted_blob_out. Return 0
+ * on success else a negative value. */
+static int
+encode_superencrypted_data(const hs_descriptor_t *desc,
+ char **encrypted_blob_out)
+{
+ int ret = -1;
+ char *layer2_str = NULL;
+ char *layer2_b64_ciphertext = NULL;
+ char *layer1_str = NULL;
+ char *layer1_b64_ciphertext = NULL;
+
+ tor_assert(desc);
+ tor_assert(encrypted_blob_out);
+
+ /* Func logic: We first create the inner layer of the descriptor (layer2).
+ * We then encrypt it and use it to create the middle layer of the descriptor
+ * (layer1). Finally we superencrypt the middle layer and return it to our
+ * caller. */
+
+ /* Create inner descriptor layer */
+ layer2_str = get_inner_encrypted_layer_plaintext(desc);
+ if (!layer2_str) {
+ goto err;
+ }
+
+ /* Encrypt and b64 the inner layer */
+ layer2_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer2_str, 0);
+ if (!layer2_b64_ciphertext) {
+ goto err;
+ }
+
+ /* Now create middle descriptor layer given the inner layer */
+ layer1_str = get_outer_encrypted_layer_plaintext(desc,layer2_b64_ciphertext);
+ if (!layer1_str) {
+ goto err;
+ }
+
+ /* Encrypt and base64 the middle layer */
+ layer1_b64_ciphertext = encrypt_desc_data_and_base64(desc, layer1_str, 1);
+ if (!layer1_b64_ciphertext) {
+ goto err;
+ }
+
+ /* Success! */
+ ret = 0;
+
+ err:
+ tor_free(layer1_str);
+ tor_free(layer2_str);
+ tor_free(layer2_b64_ciphertext);
+
+ *encrypted_blob_out = layer1_b64_ciphertext;
+ return ret;
+}
+
+/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the
+ * newly allocated string of the encoded descriptor. On error, -1 is returned
+ * and encoded_out is untouched. */
+static int
+desc_encode_v3(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp, char **encoded_out)
+{
+ int ret = -1;
+ char *encoded_str = NULL;
+ size_t encoded_len;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(signing_kp);
+ tor_assert(encoded_out);
+ tor_assert(desc->plaintext_data.version == 3);
+
+ if (BUG(desc->subcredential == NULL)) {
+ goto err;
+ }
+
+ /* Build the non-encrypted values. */
+ {
+ char *encoded_cert;
+ /* Encode certificate then create the first line of the descriptor. */
+ if (desc->plaintext_data.signing_key_cert->cert_type
+ != CERT_TYPE_SIGNING_HS_DESC) {
+ log_err(LD_BUG, "HS descriptor signing key has an unexpected cert type "
+ "(%d)", (int) desc->plaintext_data.signing_key_cert->cert_type);
+ goto err;
+ }
+ if (tor_cert_encode_ed22519(desc->plaintext_data.signing_key_cert,
+ &encoded_cert) < 0) {
+ /* The function will print error logs. */
+ goto err;
+ }
+ /* Create the hs descriptor line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_hs_desc,
+ desc->plaintext_data.version);
+ /* Add the descriptor lifetime line (in minutes). */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_lifetime,
+ desc->plaintext_data.lifetime_sec / 60);
+ /* Create the descriptor certificate line. */
+ smartlist_add_asprintf(lines, "%s\n%s", str_desc_cert, encoded_cert);
+ tor_free(encoded_cert);
+ /* Create the revision counter line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu64, str_rev_counter,
+ desc->plaintext_data.revision_counter);
+ }
+
+ /* Build the superencrypted data section. */
+ {
+ char *enc_b64_blob=NULL;
+ if (encode_superencrypted_data(desc, &enc_b64_blob) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines,
+ "%s\n"
+ "-----BEGIN MESSAGE-----\n"
+ "%s"
+ "-----END MESSAGE-----",
+ str_superencrypted, enc_b64_blob);
+ tor_free(enc_b64_blob);
+ }
+
+ /* Join all lines in one string so we can generate a signature and append
+ * it to the descriptor. */
+ encoded_str = smartlist_join_strings(lines, "\n", 1, &encoded_len);
+
+ /* Sign all fields of the descriptor with our short term signing key. */
+ {
+ ed25519_signature_t sig;
+ char ed_sig_b64[ED25519_SIG_BASE64_LEN + 1];
+ if (ed25519_sign_prefixed(&sig,
+ (const uint8_t *) encoded_str, encoded_len,
+ str_desc_sig_prefix, signing_kp) < 0) {
+ log_warn(LD_BUG, "Can't sign encoded HS descriptor!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) {
+ log_warn(LD_BUG, "Can't base64 encode descriptor signature!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ /* Create the signature line. */
+ smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64);
+ }
+ /* Free previous string that we used so compute the signature. */
+ tor_free(encoded_str);
+ encoded_str = smartlist_join_strings(lines, "\n", 1, NULL);
+ *encoded_out = encoded_str;
+
+ if (strlen(encoded_str) >= hs_cache_get_max_descriptor_size()) {
+ log_warn(LD_GENERAL, "We just made an HS descriptor that's too big (%d)."
+ "Failing.", (int)strlen(encoded_str));
+ tor_free(encoded_str);
+ goto err;
+ }
+
+ /* XXX: Trigger a control port event. */
+
+ /* Success! */
+ ret = 0;
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return ret;
+}
+
+/* === DECODING === */
+
+/* Given an encoded string of the link specifiers, return a newly allocated
+ * list of decoded link specifiers. Return NULL on error. */
+STATIC smartlist_t *
+decode_link_specifiers(const char *encoded)
+{
+ int decoded_len;
+ size_t encoded_len, i;
+ uint8_t *decoded;
+ smartlist_t *results = NULL;
+ link_specifier_list_t *specs = NULL;
+
+ tor_assert(encoded);
+
+ encoded_len = strlen(encoded);
+ decoded = tor_malloc(encoded_len);
+ decoded_len = base64_decode((char *) decoded, encoded_len, encoded,
+ encoded_len);
+ if (decoded_len < 0) {
+ goto err;
+ }
+
+ if (link_specifier_list_parse(&specs, decoded,
+ (size_t) decoded_len) < decoded_len) {
+ goto err;
+ }
+ tor_assert(specs);
+ results = smartlist_new();
+
+ for (i = 0; i < link_specifier_list_getlen_spec(specs); i++) {
+ hs_desc_link_specifier_t *hs_spec;
+ link_specifier_t *ls = link_specifier_list_get_spec(specs, i);
+ tor_assert(ls);
+
+ hs_spec = tor_malloc_zero(sizeof(*hs_spec));
+ hs_spec->type = link_specifier_get_ls_type(ls);
+ switch (hs_spec->type) {
+ case LS_IPV4:
+ tor_addr_from_ipv4h(&hs_spec->u.ap.addr,
+ link_specifier_get_un_ipv4_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv4_port(ls);
+ break;
+ case LS_IPV6:
+ tor_addr_from_ipv6_bytes(&hs_spec->u.ap.addr, (const char *)
+ link_specifier_getarray_un_ipv6_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv6_port(ls);
+ break;
+ case LS_LEGACY_ID:
+ /* Both are known at compile time so let's make sure they are the same
+ * else we can copy memory out of bound. */
+ tor_assert(link_specifier_getlen_un_legacy_id(ls) ==
+ sizeof(hs_spec->u.legacy_id));
+ memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls),
+ sizeof(hs_spec->u.legacy_id));
+ break;
+ case LS_ED25519_ID:
+ /* Both are known at compile time so let's make sure they are the same
+ * else we can copy memory out of bound. */
+ tor_assert(link_specifier_getlen_un_ed25519_id(ls) ==
+ sizeof(hs_spec->u.ed25519_id));
+ memcpy(hs_spec->u.ed25519_id,
+ link_specifier_getconstarray_un_ed25519_id(ls),
+ sizeof(hs_spec->u.ed25519_id));
+ break;
+ default:
+ tor_free(hs_spec);
+ goto err;
+ }
+
+ smartlist_add(results, hs_spec);
+ }
+
+ goto done;
+ err:
+ if (results) {
+ SMARTLIST_FOREACH(results, hs_desc_link_specifier_t *, s, tor_free(s));
+ smartlist_free(results);
+ results = NULL;
+ }
+ done:
+ link_specifier_list_free(specs);
+ tor_free(decoded);
+ return results;
+}
+
+/* Given a list of authentication types, decode it and put it in the encrypted
+ * data section. Return 1 if we at least know one of the type or 0 if we know
+ * none of them. */
+static int
+decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ int match = 0;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ desc->intro_auth_types = smartlist_new();
+ smartlist_split_string(desc->intro_auth_types, list, " ", 0, 0);
+
+ /* Validate the types that we at least know about one. */
+ SMARTLIST_FOREACH_BEGIN(desc->intro_auth_types, const char *, auth) {
+ for (int idx = 0; intro_auth_types[idx].identifier; idx++) {
+ if (!strncmp(auth, intro_auth_types[idx].identifier,
+ strlen(intro_auth_types[idx].identifier))) {
+ match = 1;
+ break;
+ }
+ }
+ } SMARTLIST_FOREACH_END(auth);
+
+ return match;
+}
+
+/* Parse a space-delimited list of integers representing CREATE2 formats into
+ * the bitfield in hs_desc_encrypted_data_t. Ignore unrecognized values. */
+static void
+decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ smartlist_t *tokens;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ tokens = smartlist_new();
+ smartlist_split_string(tokens, list, " ", 0, 0);
+
+ SMARTLIST_FOREACH_BEGIN(tokens, char *, s) {
+ int ok;
+ unsigned long type = tor_parse_ulong(s, 10, 1, UINT16_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Unparseable value %s in create2 list", escaped(s));
+ continue;
+ }
+ switch (type) {
+ case ONION_HANDSHAKE_TYPE_NTOR:
+ desc->create2_ntor = 1;
+ break;
+ default:
+ /* We deliberately ignore unsupported handshake types */
+ continue;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ SMARTLIST_FOREACH(tokens, char *, s, tor_free(s));
+ smartlist_free(tokens);
+}
+
+/* Given a certificate, validate the certificate for certain conditions which
+ * are if the given type matches the cert's one, if the signing key is
+ * included and if the that key was actually used to sign the certificate.
+ *
+ * Return 1 iff if all conditions pass or 0 if one of them fails. */
+STATIC int
+cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type)
+{
+ tor_assert(log_obj_type);
+
+ if (cert == NULL) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", log_obj_type);
+ goto err;
+ }
+ if (cert->cert_type != type) {
+ log_warn(LD_REND, "Invalid cert type %02x for %s.", cert->cert_type,
+ log_obj_type);
+ goto err;
+ }
+ /* All certificate must have its signing key included. */
+ if (!cert->signing_key_included) {
+ log_warn(LD_REND, "Signing key is NOT included for %s.", log_obj_type);
+ goto err;
+ }
+ /* The following will not only check if the signature matches but also the
+ * expiration date and overall validity. */
+ if (tor_cert_checksig(cert, &cert->signing_key, approx_time()) < 0) {
+ log_warn(LD_REND, "Invalid signature for %s: %s", log_obj_type,
+ tor_cert_describe_signature_status(cert));
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/* Given some binary data, try to parse it to get a certificate object. If we
+ * have a valid cert, validate it using the given wanted type. On error, print
+ * a log using the err_msg has the certificate identifier adding semantic to
+ * the log and cert_out is set to NULL. On success, 0 is returned and cert_out
+ * points to a newly allocated certificate object. */
+static int
+cert_parse_and_validate(tor_cert_t **cert_out, const char *data,
+ size_t data_len, unsigned int cert_type_wanted,
+ const char *err_msg)
+{
+ tor_cert_t *cert;
+
+ tor_assert(cert_out);
+ tor_assert(data);
+ tor_assert(err_msg);
+
+ /* Parse certificate. */
+ cert = tor_cert_parse((const uint8_t *) data, data_len);
+ if (!cert) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", err_msg);
+ goto err;
+ }
+
+ /* Validate certificate. */
+ if (!cert_is_valid(cert, cert_type_wanted, err_msg)) {
+ goto err;
+ }
+
+ *cert_out = cert;
+ return 0;
+
+ err:
+ tor_cert_free(cert);
+ *cert_out = NULL;
+ return -1;
+}
+
+/* Return true iff the given length of the encrypted data of a descriptor
+ * passes validation. */
+STATIC int
+encrypted_data_length_is_valid(size_t len)
+{
+ /* Make sure there is enough data for the salt and the mac. The equality is
+ there to ensure that there is at least one byte of encrypted data. */
+ if (len <= HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN) {
+ log_warn(LD_REND, "Length of descriptor's encrypted data is too small. "
+ "Got %lu but minimum value is %d",
+ (unsigned long)len, HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/** Decrypt an encrypted descriptor layer at <b>encrypted_blob</b> of size
+ * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to
+ * generate the right decryption keys; set <b>decrypted_out</b> to the
+ * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter
+ * encrypted layer of the descriptor.
+ *
+ * On any error case, including an empty output, return 0 and set
+ * *<b>decrypted_out</b> to NULL.
+ */
+MOCK_IMPL(STATIC size_t,
+decrypt_desc_layer,(const hs_descriptor_t *desc,
+ const uint8_t *encrypted_blob,
+ size_t encrypted_blob_size,
+ int is_superencrypted_layer,
+ char **decrypted_out))
+{
+ uint8_t *decrypted = NULL;
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN];
+ const uint8_t *salt, *encrypted, *desc_mac;
+ size_t encrypted_len, result_len = 0;
+
+ tor_assert(decrypted_out);
+ tor_assert(desc);
+ tor_assert(encrypted_blob);
+
+ /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC .
+ * Make sure we have enough space for all these things. */
+ if (!encrypted_data_length_is_valid(encrypted_blob_size)) {
+ goto err;
+ }
+
+ /* Start of the blob thus the salt. */
+ salt = encrypted_blob;
+
+ /* Next is the encrypted data. */
+ encrypted = encrypted_blob + HS_DESC_ENCRYPTED_SALT_LEN;
+ encrypted_len = encrypted_blob_size -
+ (HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+ tor_assert(encrypted_len > 0); /* guaranteed by the check above */
+
+ /* And last comes the MAC. */
+ desc_mac = encrypted_blob + encrypted_blob_size - DIGEST256_LEN;
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the decryption. */
+ build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key),
+ is_superencrypted_layer);
+
+ /* Build MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ encrypted, encrypted_len, our_mac, sizeof(our_mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+ /* Verify MAC; MAC is H(mac_key || salt || encrypted)
+ *
+ * This is a critical check that is making sure the computed MAC matches the
+ * one in the descriptor. */
+ if (!tor_memeq(our_mac, desc_mac, sizeof(our_mac))) {
+ log_warn(LD_REND, "Encrypted service descriptor MAC check failed");
+ goto err;
+ }
+
+ {
+ /* Decrypt. Here we are assured that the encrypted length is valid for
+ * decryption. */
+ crypto_cipher_t *cipher;
+
+ cipher = crypto_cipher_new_with_iv_and_bits(secret_key, secret_iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* Extra byte for the NUL terminated byte. */
+ decrypted = tor_malloc_zero(encrypted_len + 1);
+ crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted, encrypted_len);
+ crypto_cipher_free(cipher);
+ }
+
+ {
+ /* Adjust length to remove NUL padding bytes */
+ uint8_t *end = memchr(decrypted, 0, encrypted_len);
+ result_len = encrypted_len;
+ if (end) {
+ result_len = end - decrypted;
+ }
+ }
+
+ if (result_len == 0) {
+ /* Treat this as an error, so that somebody will free the output. */
+ goto err;
+ }
+
+ /* Make sure to NUL terminate the string. */
+ decrypted[encrypted_len] = '\0';
+ *decrypted_out = (char *) decrypted;
+ goto done;
+
+ err:
+ if (decrypted) {
+ tor_free(decrypted);
+ }
+ *decrypted_out = NULL;
+ result_len = 0;
+
+ done:
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ return result_len;
+}
+
+/* Basic validation that the superencrypted client auth portion of the
+ * descriptor is well-formed and recognized. Return True if so, otherwise
+ * return False. */
+static int
+superencrypted_auth_data_is_valid(smartlist_t *tokens)
+{
+ /* XXX: This is just basic validation for now. When we implement client auth,
+ we can refactor this function so that it actually parses and saves the
+ data. */
+
+ { /* verify desc auth type */
+ const directory_token_t *tok;
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_TYPE);
+ tor_assert(tok->n_args >= 1);
+ if (strcmp(tok->args[0], "x25519")) {
+ log_warn(LD_DIR, "Unrecognized desc auth type");
+ return 0;
+ }
+ }
+
+ { /* verify desc auth key */
+ const directory_token_t *tok;
+ curve25519_public_key_t k;
+ tok = find_by_keyword(tokens, R3_DESC_AUTH_KEY);
+ tor_assert(tok->n_args >= 1);
+ if (curve25519_public_from_base64(&k, tok->args[0]) < 0) {
+ log_warn(LD_DIR, "Bogus desc auth key in HS desc");
+ return 0;
+ }
+ }
+
+ /* verify desc auth client items */
+ SMARTLIST_FOREACH_BEGIN(tokens, const directory_token_t *, tok) {
+ if (tok->tp == R3_DESC_AUTH_CLIENT) {
+ tor_assert(tok->n_args >= 3);
+ }
+ } SMARTLIST_FOREACH_END(tok);
+
+ return 1;
+}
+
+/* Parse <b>message</b>, the plaintext of the superencrypted portion of an HS
+ * descriptor. Set <b>encrypted_out</b> to the encrypted blob, and return its
+ * size */
+STATIC size_t
+decode_superencrypted(const char *message, size_t message_len,
+ uint8_t **encrypted_out)
+{
+ int retval = 0;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len, tokens,
+ hs_desc_superencrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Superencrypted portion is not parseable");
+ goto err;
+ }
+
+ /* Do some rudimentary validation of the authentication data */
+ if (!superencrypted_auth_data_is_valid(tokens)) {
+ log_warn(LD_REND, "Invalid auth data");
+ goto err;
+ }
+
+ /* Extract the encrypted data section. */
+ {
+ const directory_token_t *tok;
+ tok = find_by_keyword(tokens, R3_ENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Desc superencrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ tor_assert(tok->object_size <= INT_MAX);
+ *encrypted_out = tor_memdup(tok->object_body, tok->object_size);
+ retval = (int) tok->object_size;
+ }
+
+ err:
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ if (area) {
+ memarea_drop_all(area);
+ }
+
+ return retval;
+}
+
+/* Decrypt both the superencrypted and the encrypted section of the descriptor
+ * using the given descriptor object <b>desc</b>. A newly allocated NUL
+ * terminated string is put in decrypted_out which contains the inner encrypted
+ * layer of the descriptor. Return the length of decrypted_out on success else
+ * 0 is returned and decrypted_out is set to NULL. */
+static size_t
+desc_decrypt_all(const hs_descriptor_t *desc, char **decrypted_out)
+{
+ size_t decrypted_len = 0;
+ size_t encrypted_len = 0;
+ size_t superencrypted_len = 0;
+ char *superencrypted_plaintext = NULL;
+ uint8_t *encrypted_blob = NULL;
+
+ /** Function logic: This function takes us from the descriptor header to the
+ * inner encrypted layer, by decrypting and decoding the middle descriptor
+ * layer. In the end we return the contents of the inner encrypted layer to
+ * our caller. */
+
+ /* 1. Decrypt middle layer of descriptor */
+ superencrypted_len = decrypt_desc_layer(desc,
+ desc->plaintext_data.superencrypted_blob,
+ desc->plaintext_data.superencrypted_blob_size,
+ 1,
+ &superencrypted_plaintext);
+ if (!superencrypted_len) {
+ log_warn(LD_REND, "Decrypting superencrypted desc failed.");
+ goto err;
+ }
+ tor_assert(superencrypted_plaintext);
+
+ /* 2. Parse "superencrypted" */
+ encrypted_len = decode_superencrypted(superencrypted_plaintext,
+ superencrypted_len,
+ &encrypted_blob);
+ if (!encrypted_len) {
+ log_warn(LD_REND, "Decrypting encrypted desc failed.");
+ goto err;
+ }
+ tor_assert(encrypted_blob);
+
+ /* 3. Decrypt "encrypted" and set decrypted_out */
+ char *decrypted_desc;
+ decrypted_len = decrypt_desc_layer(desc,
+ encrypted_blob, encrypted_len,
+ 0, &decrypted_desc);
+ if (!decrypted_len) {
+ log_warn(LD_REND, "Decrypting encrypted desc failed.");
+ goto err;
+ }
+ tor_assert(decrypted_desc);
+
+ *decrypted_out = decrypted_desc;
+
+ err:
+ tor_free(superencrypted_plaintext);
+ tor_free(encrypted_blob);
+
+ return decrypted_len;
+}
+
+/* Given the token tok for an intro point legacy key, the list of tokens, the
+ * introduction point ip being decoded and the descriptor desc from which it
+ * comes from, decode the legacy key and set the intro point object. Return 0
+ * on success else -1 on failure. */
+static int
+decode_intro_legacy_key(const directory_token_t *tok,
+ smartlist_t *tokens,
+ hs_desc_intro_point_t *ip,
+ const hs_descriptor_t *desc)
+{
+ tor_assert(tok);
+ tor_assert(tokens);
+ tor_assert(ip);
+ tor_assert(desc);
+
+ if (!crypto_pk_public_exponent_ok(tok->key)) {
+ log_warn(LD_REND, "Introduction point legacy key is invalid");
+ goto err;
+ }
+ ip->legacy.key = crypto_pk_dup_key(tok->key);
+ /* Extract the legacy cross certification cert which MUST be present if we
+ * have a legacy key. */
+ tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY_CERT);
+ if (!tok) {
+ log_warn(LD_REND, "Introduction point legacy key cert is missing");
+ goto err;
+ }
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "CROSSCERT")) {
+ /* Info level because this might be an unknown field that we should
+ * ignore. */
+ log_info(LD_REND, "Introduction point legacy encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ /* Keep a copy of the certificate. */
+ ip->legacy.cert.encoded = tor_memdup(tok->object_body, tok->object_size);
+ ip->legacy.cert.len = tok->object_size;
+ /* The check on the expiration date is for the entire lifetime of a
+ * certificate which is 24 hours. However, a descriptor has a maximum
+ * lifetime of 12 hours meaning we have a 12h difference between the two
+ * which ultimately accommodate the clock skewed client. */
+ if (rsa_ed25519_crosscert_check(ip->legacy.cert.encoded,
+ ip->legacy.cert.len, ip->legacy.key,
+ &desc->plaintext_data.signing_pubkey,
+ approx_time() - HS_DESC_CERT_LIFETIME)) {
+ log_warn(LD_REND, "Unable to check cross-certification on the "
+ "introduction point legacy encryption key.");
+ ip->cross_certified = 0;
+ goto err;
+ }
+
+ /* Success. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Dig into the descriptor <b>tokens</b> to find the onion key we should use
+ * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it
+ * was found and well-formed, otherwise return -1 in case of errors. */
+static int
+set_intro_point_onion_key(curve25519_public_key_t *onion_key_out,
+ const smartlist_t *tokens)
+{
+ int retval = -1;
+ smartlist_t *onion_keys = NULL;
+
+ tor_assert(onion_key_out);
+
+ onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY);
+ if (!onion_keys) {
+ log_warn(LD_REND, "Descriptor did not contain intro onion keys");
+ goto err;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) {
+ /* This field is using GE(2) so for possible forward compatibility, we
+ * accept more fields but must be at least 2. */
+ tor_assert(tok->n_args >= 2);
+
+ /* Try to find an ntor key, it's the only recognized type right now */
+ if (!strcmp(tok->args[0], "ntor")) {
+ if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor onion-key is invalid");
+ goto err;
+ }
+ /* Got the onion key! Set the appropriate retval */
+ retval = 0;
+ }
+ } SMARTLIST_FOREACH_END(tok);
+
+ /* Log an error if we didn't find it :( */
+ if (retval < 0) {
+ log_warn(LD_REND, "Descriptor did not contain ntor onion keys");
+ }
+
+ err:
+ smartlist_free(onion_keys);
+ return retval;
+}
+
+/* Given the start of a section and the end of it, decode a single
+ * introduction point from that section. Return a newly allocated introduction
+ * point object containing the decoded data. Return NULL if the section can't
+ * be decoded. */
+STATIC hs_desc_intro_point_t *
+decode_introduction_point(const hs_descriptor_t *desc, const char *start)
+{
+ hs_desc_intro_point_t *ip = NULL;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ const directory_token_t *tok;
+
+ tor_assert(desc);
+ tor_assert(start);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, start, start + strlen(start),
+ tokens, hs_desc_intro_point_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Introduction point is not parseable");
+ goto err;
+ }
+
+ /* Ok we seem to have a well formed section containing enough tokens to
+ * parse. Allocate our IP object and try to populate it. */
+ ip = hs_desc_intro_point_new();
+
+ /* "introduction-point" SP link-specifiers NL */
+ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT);
+ tor_assert(tok->n_args == 1);
+ /* Our constructor creates this list by default so free it. */
+ smartlist_free(ip->link_specifiers);
+ ip->link_specifiers = decode_link_specifiers(tok->args[0]);
+ if (!ip->link_specifiers) {
+ log_warn(LD_REND, "Introduction point has invalid link specifiers");
+ goto err;
+ }
+
+ /* "onion-key" SP ntor SP key NL */
+ if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) {
+ goto err;
+ }
+
+ /* "auth-key" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Unexpected object type for introduction auth key");
+ goto err;
+ }
+ /* Parse cert and do some validation. */
+ if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY,
+ "introduction point auth-key") < 0) {
+ goto err;
+ }
+ /* Validate authentication certificate with descriptor signing key. */
+ if (tor_cert_checksig(ip->auth_key_cert,
+ &desc->plaintext_data.signing_pubkey, 0) < 0) {
+ log_warn(LD_REND, "Invalid authentication key signature: %s",
+ tor_cert_describe_signature_status(ip->auth_key_cert));
+ goto err;
+ }
+
+ /* Exactly one "enc-key" SP "ntor" SP key NL */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY);
+ if (!strcmp(tok->args[0], "ntor")) {
+ /* This field is using GE(2) so for possible forward compatibility, we
+ * accept more fields but must be at least 2. */
+ tor_assert(tok->n_args >= 2);
+
+ if (curve25519_public_from_base64(&ip->enc_key, tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor enc-key is invalid");
+ goto err;
+ }
+ } else {
+ /* Unknown key type so we can't use that introduction point. */
+ log_warn(LD_REND, "Introduction point encryption key is unrecognized.");
+ goto err;
+ }
+
+ /* Exactly once "enc-key-cert" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERT);
+ tor_assert(tok->object_body);
+ /* Do the cross certification. */
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Introduction point ntor encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ if (cert_parse_and_validate(&ip->enc_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS,
+ "introduction point enc-key-cert") < 0) {
+ goto err;
+ }
+ if (tor_cert_checksig(ip->enc_key_cert,
+ &desc->plaintext_data.signing_pubkey, 0) < 0) {
+ log_warn(LD_REND, "Invalid encryption key signature: %s",
+ tor_cert_describe_signature_status(ip->enc_key_cert));
+ goto err;
+ }
+ /* It is successfully cross certified. Flag the object. */
+ ip->cross_certified = 1;
+
+ /* Do we have a "legacy-key" SP key NL ?*/
+ tok = find_opt_by_keyword(tokens, R3_INTRO_LEGACY_KEY);
+ if (tok) {
+ if (decode_intro_legacy_key(tok, tokens, ip, desc) < 0) {
+ goto err;
+ }
+ }
+
+ /* Introduction point has been parsed successfully. */
+ goto done;
+
+ err:
+ hs_desc_intro_point_free(ip);
+ ip = NULL;
+
+ done:
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ if (area) {
+ memarea_drop_all(area);
+ }
+
+ return ip;
+}
+
+/* Given a descriptor string at <b>data</b>, decode all possible introduction
+ * points that we can find. Add the introduction point object to desc_enc as we
+ * find them. This function can't fail and it is possible that zero
+ * introduction points can be decoded. */
+static void
+decode_intro_points(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_enc,
+ const char *data)
+{
+ smartlist_t *chunked_desc = smartlist_new();
+ smartlist_t *intro_points = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(desc_enc);
+ tor_assert(data);
+ tor_assert(desc_enc->intro_points);
+
+ /* Take the desc string, and extract the intro point substrings out of it */
+ {
+ /* Split the descriptor string using the intro point header as delimiter */
+ smartlist_split_string(chunked_desc, data, str_intro_point_start, 0, 0);
+
+ /* Check if there are actually any intro points included. The first chunk
+ * should be other descriptor fields (e.g. create2-formats), so it's not an
+ * intro point. */
+ if (smartlist_len(chunked_desc) < 2) {
+ goto done;
+ }
+ }
+
+ /* Take the intro point substrings, and prepare them for parsing */
+ {
+ int i = 0;
+ /* Prepend the introduction-point header to all the chunks, since
+ smartlist_split_string() devoured it. */
+ SMARTLIST_FOREACH_BEGIN(chunked_desc, char *, chunk) {
+ /* Ignore first chunk. It's other descriptor fields. */
+ if (i++ == 0) {
+ continue;
+ }
+
+ smartlist_add_asprintf(intro_points, "%s %s", str_intro_point, chunk);
+ } SMARTLIST_FOREACH_END(chunk);
+ }
+
+ /* Parse the intro points! */
+ SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) {
+ hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point);
+ if (!ip) {
+ /* Malformed introduction point section. We'll ignore this introduction
+ * point and continue parsing. New or unknown fields are possible for
+ * forward compatibility. */
+ continue;
+ }
+ smartlist_add(desc_enc->intro_points, ip);
+ } SMARTLIST_FOREACH_END(intro_point);
+
+ done:
+ SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a));
+ smartlist_free(chunked_desc);
+ SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a));
+ smartlist_free(intro_points);
+}
+/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded
+ * descriptor in encoded_desc validates the descriptor content. */
+STATIC int
+desc_sig_is_valid(const char *b64_sig,
+ const ed25519_public_key_t *signing_pubkey,
+ const char *encoded_desc, size_t encoded_len)
+{
+ int ret = 0;
+ ed25519_signature_t sig;
+ const char *sig_start;
+
+ tor_assert(b64_sig);
+ tor_assert(signing_pubkey);
+ tor_assert(encoded_desc);
+ /* Verifying nothing won't end well :). */
+ tor_assert(encoded_len > 0);
+
+ /* Signature length check. */
+ if (strlen(b64_sig) != ED25519_SIG_BASE64_LEN) {
+ log_warn(LD_REND, "Service descriptor has an invalid signature length."
+ "Exptected %d but got %lu",
+ ED25519_SIG_BASE64_LEN, (unsigned long) strlen(b64_sig));
+ goto err;
+ }
+
+ /* First, convert base64 blob to an ed25519 signature. */
+ if (ed25519_signature_from_base64(&sig, b64_sig) != 0) {
+ log_warn(LD_REND, "Service descriptor does not contain a valid "
+ "signature");
+ goto err;
+ }
+
+ /* Find the start of signature. */
+ sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature " ");
+ /* Getting here means the token parsing worked for the signature so if we
+ * can't find the start of the signature, we have a code flow issue. */
+ if (!sig_start) {
+ log_warn(LD_GENERAL, "Malformed signature line. Rejecting.");
+ goto err;
+ }
+ /* Skip newline, it has to go in the signature check. */
+ sig_start++;
+
+ /* Validate signature with the full body of the descriptor. */
+ if (ed25519_checksig_prefixed(&sig,
+ (const uint8_t *) encoded_desc,
+ sig_start - encoded_desc,
+ str_desc_sig_prefix,
+ signing_pubkey) != 0) {
+ log_warn(LD_REND, "Invalid signature on service descriptor");
+ goto err;
+ }
+ /* Valid signature! All is good. */
+ ret = 1;
+
+ err:
+ return ret;
+}
+
+/* Decode descriptor plaintext data for version 3. Given a list of tokens, an
+ * allocated plaintext object that will be populated and the encoded
+ * descriptor with its length. The last one is needed for signature
+ * verification. Unknown tokens are simply ignored so this won't error on
+ * unknowns but requires that all v3 token be present and valid.
+ *
+ * Return 0 on success else a negative value. */
+static int
+desc_decode_plaintext_v3(smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc, size_t encoded_len)
+{
+ int ok;
+ directory_token_t *tok;
+
+ tor_assert(tokens);
+ tor_assert(desc);
+ /* Version higher could still use this function to decode most of the
+ * descriptor and then they decode the extra part. */
+ tor_assert(desc->version >= 3);
+
+ /* Descriptor lifetime parsing. */
+ tok = find_by_keyword(tokens, R3_DESC_LIFETIME);
+ tor_assert(tok->n_args == 1);
+ desc->lifetime_sec = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor lifetime value is invalid");
+ goto err;
+ }
+ /* Put it from minute to second. */
+ desc->lifetime_sec *= 60;
+ if (desc->lifetime_sec > HS_DESC_MAX_LIFETIME) {
+ log_warn(LD_REND, "Service descriptor lifetime is too big. "
+ "Got %" PRIu32 " but max is %d",
+ desc->lifetime_sec, HS_DESC_MAX_LIFETIME);
+ goto err;
+ }
+
+ /* Descriptor signing certificate. */
+ tok = find_by_keyword(tokens, R3_DESC_SIGNING_CERT);
+ tor_assert(tok->object_body);
+ /* Expecting a prop220 cert with the signing key extension, which contains
+ * the blinded public key. */
+ if (strcmp(tok->object_type, "ED25519 CERT") != 0) {
+ log_warn(LD_REND, "Service descriptor signing cert wrong type (%s)",
+ escaped(tok->object_type));
+ goto err;
+ }
+ if (cert_parse_and_validate(&desc->signing_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_SIGNING_HS_DESC,
+ "service descriptor signing key") < 0) {
+ goto err;
+ }
+
+ /* Copy the public keys into signing_pubkey and blinded_pubkey */
+ memcpy(&desc->signing_pubkey, &desc->signing_key_cert->signed_key,
+ sizeof(ed25519_public_key_t));
+ memcpy(&desc->blinded_pubkey, &desc->signing_key_cert->signing_key,
+ sizeof(ed25519_public_key_t));
+
+ /* Extract revision counter value. */
+ tok = find_by_keyword(tokens, R3_REVISION_COUNTER);
+ tor_assert(tok->n_args == 1);
+ desc->revision_counter = tor_parse_uint64(tok->args[0], 10, 0,
+ UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor revision-counter is invalid");
+ goto err;
+ }
+
+ /* Extract the encrypted data section. */
+ tok = find_by_keyword(tokens, R3_SUPERENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Service descriptor encrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ desc->superencrypted_blob = tor_memdup(tok->object_body, tok->object_size);
+ desc->superencrypted_blob_size = tok->object_size;
+
+ /* Extract signature and verify it. */
+ tok = find_by_keyword(tokens, R3_SIGNATURE);
+ tor_assert(tok->n_args == 1);
+ /* First arg here is the actual encoded signature. */
+ if (!desc_sig_is_valid(tok->args[0], &desc->signing_pubkey,
+ encoded_desc, encoded_len)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Decode the version 3 encrypted section of the given descriptor desc. The
+ * desc_encrypted_out will be populated with the decoded data. Return 0 on
+ * success else -1. */
+static int
+desc_decode_encrypted_v3(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted_out)
+{
+ int result = -1;
+ char *message = NULL;
+ size_t message_len;
+ memarea_t *area = NULL;
+ directory_token_t *tok;
+ smartlist_t *tokens = NULL;
+
+ tor_assert(desc);
+ tor_assert(desc_encrypted_out);
+
+ /* Decrypt the superencrypted data that is located in the plaintext section
+ * in the descriptor as a blob of bytes. */
+ message_len = desc_decrypt_all(desc, &message);
+ if (!message_len) {
+ log_warn(LD_REND, "Service descriptor decryption failed.");
+ goto err;
+ }
+ tor_assert(message);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len,
+ tokens, hs_desc_encrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Encrypted service descriptor is not parseable.");
+ goto err;
+ }
+
+ /* CREATE2 supported cell format. It's mandatory. */
+ tok = find_by_keyword(tokens, R3_CREATE2_FORMATS);
+ tor_assert(tok);
+ decode_create2_list(desc_encrypted_out, tok->args[0]);
+ /* Must support ntor according to the specification */
+ if (!desc_encrypted_out->create2_ntor) {
+ log_warn(LD_REND, "Service create2-formats does not include ntor.");
+ goto err;
+ }
+
+ /* Authentication type. It's optional but only once. */
+ tok = find_opt_by_keyword(tokens, R3_INTRO_AUTH_REQUIRED);
+ if (tok) {
+ if (!decode_auth_type(desc_encrypted_out, tok->args[0])) {
+ log_warn(LD_REND, "Service descriptor authentication type has "
+ "invalid entry(ies).");
+ goto err;
+ }
+ }
+
+ /* Is this service a single onion service? */
+ tok = find_opt_by_keyword(tokens, R3_SINGLE_ONION_SERVICE);
+ if (tok) {
+ desc_encrypted_out->single_onion_service = 1;
+ }
+
+ /* Initialize the descriptor's introduction point list before we start
+ * decoding. Having 0 intro point is valid. Then decode them all. */
+ desc_encrypted_out->intro_points = smartlist_new();
+ decode_intro_points(desc, desc_encrypted_out, message);
+
+ /* Validation of maximum introduction points allowed. */
+ if (smartlist_len(desc_encrypted_out->intro_points) >
+ HS_CONFIG_V3_MAX_INTRO_POINTS) {
+ log_warn(LD_REND, "Service descriptor contains too many introduction "
+ "points. Maximum allowed is %d but we have %d",
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ smartlist_len(desc_encrypted_out->intro_points));
+ goto err;
+ }
+
+ /* NOTE: Unknown fields are allowed because this function could be used to
+ * decode other descriptor version. */
+
+ result = 0;
+ goto done;
+
+ err:
+ tor_assert(result < 0);
+ desc_encrypted_data_free_contents(desc_encrypted_out);
+
+ done:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ if (message) {
+ tor_free(message);
+ }
+ return result;
+}
+
+/* Table of encrypted decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_encrypted_handlers[])(
+ const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_encrypted_v3,
+};
+
+/* Decode the encrypted data section of the given descriptor and store the
+ * data in the given encrypted data object. Return 0 on success else a
+ * negative value on error. */
+int
+hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted)
+{
+ int ret;
+ uint32_t version;
+
+ tor_assert(desc);
+ /* Ease our life a bit. */
+ version = desc->plaintext_data.version;
+ tor_assert(desc_encrypted);
+ /* Calling this function without an encrypted blob to parse is a code flow
+ * error. The plaintext parsing should never succeed in the first place
+ * without an encrypted section. */
+ tor_assert(desc->plaintext_data.superencrypted_blob);
+ /* Let's make sure we have a supported version as well. By correctly parsing
+ * the plaintext, this should not fail. */
+ if (BUG(!hs_desc_is_supported_version(version))) {
+ ret = -1;
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_encrypted_handlers) >= version);
+ tor_assert(decode_encrypted_handlers[version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_encrypted_handlers[version](desc, desc_encrypted);
+ if (ret < 0) {
+ goto err;
+ }
+
+ err:
+ return ret;
+}
+
+/* Table of plaintext decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_plaintext_handlers[])(
+ smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc,
+ size_t encoded_len) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_plaintext_v3,
+};
+
+/* Fully decode the given descriptor plaintext and store the data in the
+ * plaintext data object. Returns 0 on success else a negative value. */
+int
+hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext)
+{
+ int ok = 0, ret = -1;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ size_t encoded_len;
+ directory_token_t *tok;
+
+ tor_assert(encoded);
+ tor_assert(plaintext);
+
+ /* Check that descriptor is within size limits. */
+ encoded_len = strlen(encoded);
+ if (encoded_len >= hs_cache_get_max_descriptor_size()) {
+ log_warn(LD_REND, "Service descriptor is too big (%lu bytes)",
+ (unsigned long) encoded_len);
+ goto err;
+ }
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ /* Tokenize the descriptor so we can start to parse it. */
+ if (tokenize_string(area, encoded, encoded + encoded_len, tokens,
+ hs_desc_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Service descriptor is not parseable");
+ goto err;
+ }
+
+ /* Get the version of the descriptor which is the first mandatory field of
+ * the descriptor. From there, we'll decode the right descriptor version. */
+ tok = find_by_keyword(tokens, R_HS_DESCRIPTOR);
+ tor_assert(tok->n_args == 1);
+ plaintext->version = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor has unparseable version %s",
+ escaped(tok->args[0]));
+ goto err;
+ }
+ if (!hs_desc_is_supported_version(plaintext->version)) {
+ log_warn(LD_REND, "Service descriptor has unsupported version %" PRIu32,
+ plaintext->version);
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_plaintext_handlers) >= plaintext->version);
+ tor_assert(decode_plaintext_handlers[plaintext->version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_plaintext_handlers[plaintext->version](tokens, plaintext,
+ encoded, encoded_len);
+ if (ret < 0) {
+ goto err;
+ }
+ /* Success. Descriptor has been populated with the data. */
+ ret = 0;
+
+ err:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ return ret;
+}
+
+/* Fully decode an encoded descriptor and set a newly allocated descriptor
+ * object in desc_out. Subcredentials are used if not NULL else it's ignored.
+ *
+ * Return 0 on success. A negative value is returned on error and desc_out is
+ * set to NULL. */
+int
+hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out)
+{
+ int ret = -1;
+ hs_descriptor_t *desc;
+
+ tor_assert(encoded);
+
+ desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+
+ /* Subcredentials are optional. */
+ if (BUG(!subcredential)) {
+ log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!");
+ goto err;
+ }
+
+ memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+
+ ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ if (desc_out) {
+ *desc_out = desc;
+ } else {
+ hs_descriptor_free(desc);
+ }
+ return ret;
+
+ err:
+ hs_descriptor_free(desc);
+ if (desc_out) {
+ *desc_out = NULL;
+ }
+
+ tor_assert(ret < 0);
+ return ret;
+}
+
+/* Table of encode function version specific. The functions are indexed by the
+ * version number so v3 callback is at index 3 in the array. */
+static int
+ (*encode_handlers[])(
+ const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_encode_v3,
+};
+
+/* Encode the given descriptor desc including signing with the given key pair
+ * signing_kp. On success, encoded_out points to a newly allocated NUL
+ * terminated string that contains the encoded descriptor as a string.
+ *
+ * Return 0 on success and encoded_out is a valid pointer. On error, -1 is
+ * returned and encoded_out is set to NULL. */
+MOCK_IMPL(int,
+hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out))
+{
+ int ret = -1;
+ uint32_t version;
+
+ tor_assert(desc);
+ tor_assert(encoded_out);
+
+ /* Make sure we support the version of the descriptor format. */
+ version = desc->plaintext_data.version;
+ if (!hs_desc_is_supported_version(version)) {
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(encode_handlers) >= version);
+ tor_assert(encode_handlers[version]);
+
+ ret = encode_handlers[version](desc, signing_kp, encoded_out);
+ if (ret < 0) {
+ goto err;
+ }
+
+ /* Try to decode what we just encoded. Symmetry is nice! */
+ ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL);
+ if (BUG(ret < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ *encoded_out = NULL;
+ return ret;
+}
+
+/* Free the descriptor plaintext data object. */
+void
+hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc)
+{
+ desc_plaintext_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the descriptor encrypted data object. */
+void
+hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc)
+{
+ desc_encrypted_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the given descriptor object. */
+void
+hs_descriptor_free_(hs_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ desc_plaintext_data_free_contents(&desc->plaintext_data);
+ desc_encrypted_data_free_contents(&desc->encrypted_data);
+ tor_free(desc);
+}
+
+/* Return the size in bytes of the given plaintext data object. A sizeof() is
+ * not enough because the object contains pointers and the encrypted blob.
+ * This is particularly useful for our OOM subsystem that tracks the HSDir
+ * cache size for instance. */
+size_t
+hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
+{
+ tor_assert(data);
+ return (sizeof(*data) + sizeof(*data->signing_key_cert) +
+ data->superencrypted_blob_size);
+}
+
+/* Return the size in bytes of the given encrypted data object. Used by OOM
+ * subsystem. */
+static size_t
+hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data)
+{
+ tor_assert(data);
+ size_t intro_size = 0;
+ if (data->intro_auth_types) {
+ intro_size +=
+ smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types);
+ }
+ if (data->intro_points) {
+ /* XXX could follow pointers here and get more accurate size */
+ intro_size +=
+ smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t);
+ }
+
+ return sizeof(*data) + intro_size;
+}
+
+/* Return the size in bytes of the given descriptor object. Used by OOM
+ * subsystem. */
+ size_t
+hs_desc_obj_size(const hs_descriptor_t *data)
+{
+ tor_assert(data);
+ return (hs_desc_plaintext_obj_size(&data->plaintext_data) +
+ hs_desc_encrypted_obj_size(&data->encrypted_data) +
+ sizeof(data->subcredential));
+}
+
+/* Return a newly allocated descriptor intro point. */
+hs_desc_intro_point_t *
+hs_desc_intro_point_new(void)
+{
+ hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
+ ip->link_specifiers = smartlist_new();
+ return ip;
+}
+
+/* Free a descriptor intro point object. */
+void
+hs_desc_intro_point_free_(hs_desc_intro_point_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ if (ip->link_specifiers) {
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
+ ls, hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ }
+ tor_cert_free(ip->auth_key_cert);
+ tor_cert_free(ip->enc_key_cert);
+ crypto_pk_free(ip->legacy.key);
+ tor_free(ip->legacy.cert.encoded);
+ tor_free(ip);
+}
+
+/* Free the given descriptor link specifier. */
+void
+hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls)
+{
+ if (ls == NULL) {
+ return;
+ }
+ tor_free(ls);
+}
+
+/* Return a newly allocated descriptor link specifier using the given extend
+ * info and requested type. Return NULL on error. */
+hs_desc_link_specifier_t *
+hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type)
+{
+ hs_desc_link_specifier_t *ls = NULL;
+
+ tor_assert(info);
+
+ ls = tor_malloc_zero(sizeof(*ls));
+ ls->type = type;
+ switch (ls->type) {
+ case LS_IPV4:
+ if (info->addr.family != AF_INET) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_IPV6:
+ if (info->addr.family != AF_INET6) {
+ goto err;
+ }
+ tor_addr_copy(&ls->u.ap.addr, &info->addr);
+ ls->u.ap.port = info->port;
+ break;
+ case LS_LEGACY_ID:
+ /* Bug out if the identity digest is not set */
+ if (BUG(tor_mem_is_zero(info->identity_digest,
+ sizeof(info->identity_digest)))) {
+ goto err;
+ }
+ memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id));
+ break;
+ case LS_ED25519_ID:
+ /* ed25519 keys are optional for intro points */
+ if (ed25519_public_key_is_zero(&info->ed_identity)) {
+ goto err;
+ }
+ memcpy(ls->u.ed25519_id, info->ed_identity.pubkey,
+ sizeof(ls->u.ed25519_id));
+ break;
+ default:
+ /* Unknown type is code flow error. */
+ tor_assert(0);
+ }
+
+ return ls;
+ err:
+ tor_free(ls);
+ return NULL;
+}
+
+/* From the given descriptor, remove and free every introduction point. */
+void
+hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
+{
+ smartlist_t *ips;
+
+ tor_assert(desc);
+
+ ips = desc->encrypted_data.intro_points;
+ if (ips) {
+ SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *,
+ ip, hs_desc_intro_point_free(ip));
+ smartlist_clear(ips);
+ }
+}
+
+/* From a descriptor link specifier object spec, returned a newly allocated
+ * link specifier object that is the encoded representation of spec. Return
+ * NULL on error. */
+link_specifier_t *
+hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec)
+{
+ tor_assert(spec);
+
+ link_specifier_t *ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, spec->type);
+
+ switch (spec->type) {
+ case LS_IPV4:
+ link_specifier_set_un_ipv4_addr(ls,
+ tor_addr_to_ipv4h(&spec->u.ap.addr));
+ link_specifier_set_un_ipv4_port(ls, spec->u.ap.port);
+ /* Four bytes IPv4 and two bytes port. */
+ link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) +
+ sizeof(spec->u.ap.port));
+ break;
+ case LS_IPV6:
+ {
+ size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls);
+ const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr);
+ uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls);
+ memcpy(ipv6_array, in6_addr, addr_len);
+ link_specifier_set_un_ipv6_port(ls, spec->u.ap.port);
+ /* Sixteen bytes IPv6 and two bytes port. */
+ link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port));
+ break;
+ }
+ case LS_LEGACY_ID:
+ {
+ size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls);
+ uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls);
+ memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len);
+ link_specifier_set_ls_len(ls, legacy_id_len);
+ break;
+ }
+ case LS_ED25519_ID:
+ {
+ size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
+ uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
+ memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
+ link_specifier_set_ls_len(ls, ed25519_id_len);
+ break;
+ }
+ default:
+ tor_assert_nonfatal_unreached();
+ link_specifier_free(ls);
+ ls = NULL;
+ }
+
+ return ls;
+}
diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h
new file mode 100644
index 0000000000..7a4010cd3b
--- /dev/null
+++ b/src/feature/hs/hs_descriptor.h
@@ -0,0 +1,280 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.h
+ * \brief Header file for hs_descriptor.c
+ **/
+
+#ifndef TOR_HS_DESCRIPTOR_H
+#define TOR_HS_DESCRIPTOR_H
+
+#include <stdint.h>
+
+#include "or/or.h"
+#include "trunnel/ed25519_cert.h" /* needed for trunnel */
+#include "or/torcert.h"
+
+/* Trunnel */
+struct link_specifier_t;
+
+/* The earliest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3
+/* The latest descriptor format version we support. */
+#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3
+
+/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours
+ * which is 180 minutes or 10800 seconds. */
+#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60)
+/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours
+ * which is 720 minutes or 43200 seconds. */
+#define HS_DESC_MAX_LIFETIME (12 * 60 * 60)
+/* Lifetime of certificate in the descriptor. This defines the lifetime of the
+ * descriptor signing key and the cross certification cert of that key. It is
+ * set to 54 hours because a descriptor can be around for 48 hours and because
+ * consensuses are used after the hour, add an extra 6 hours to give some time
+ * for the service to stop using it. */
+#define HS_DESC_CERT_LIFETIME (54 * 60 * 60)
+/* Length of the salt needed for the encrypted section of a descriptor. */
+#define HS_DESC_ENCRYPTED_SALT_LEN 16
+/* Length of the secret input needed for the KDF construction which derives
+ * the encryption key for the encrypted data section of the descriptor. This
+ * adds up to 68 bytes being the blinded key, hashed subcredential and
+ * revision counter. */
+#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \
+ ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t)
+/* Length of the KDF output value which is the length of the secret key,
+ * the secret IV and MAC key length which is the length of H() output. */
+#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \
+ CIPHER256_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN
+/* Pad plaintext of superencrypted data section before encryption so that its
+ * length is a multiple of this value. */
+#define HS_DESC_SUPERENC_PLAINTEXT_PAD_MULTIPLE 10000
+/* Maximum length in bytes of a full hidden service descriptor. */
+#define HS_DESC_MAX_LEN 50000 /* 50kb max size */
+
+/* Key length for the descriptor symmetric encryption. As specified in the
+ * protocol, we use AES-256 for the encrypted section of the descriptor. The
+ * following is the length in bytes and the bit size. */
+#define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN
+#define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8)
+
+/* Type of authentication in the descriptor. */
+typedef enum {
+ HS_DESC_AUTH_ED25519 = 1
+} hs_desc_auth_type_t;
+
+/* Link specifier object that contains information on how to extend to the
+ * relay that is the address, port and handshake type. */
+typedef struct hs_desc_link_specifier_t {
+ /* Indicate the type of link specifier. See trunnel ed25519_cert
+ * specification. */
+ uint8_t type;
+
+ /* It must be one of these types, can't be more than one. */
+ union {
+ /* IP address and port of the relay use to extend. */
+ tor_addr_port_t ap;
+ /* Legacy identity. A 20-byte SHA1 identity fingerprint. */
+ uint8_t legacy_id[DIGEST_LEN];
+ /* ed25519 identity. A 32-byte key. */
+ uint8_t ed25519_id[ED25519_PUBKEY_LEN];
+ } u;
+} hs_desc_link_specifier_t;
+
+/* Introduction point information located in a descriptor. */
+typedef struct hs_desc_intro_point_t {
+ /* Link specifier(s) which details how to extend to the relay. This list
+ * contains hs_desc_link_specifier_t object. It MUST have at least one. */
+ smartlist_t *link_specifiers;
+
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
+ /* Authentication key used to establish the introduction point circuit and
+ * cross-certifies the blinded public key for the replica thus signed by
+ * the blinded key and in turn signs it. */
+ tor_cert_t *auth_key_cert;
+
+ /* Encryption key for the "ntor" type. */
+ curve25519_public_key_t enc_key;
+
+ /* Certificate cross certifying the descriptor signing key by the encryption
+ * curve25519 key. This certificate contains the signing key and is of type
+ * CERT_TYPE_CROSS_HS_IP_KEYS [0B]. */
+ tor_cert_t *enc_key_cert;
+
+ /* (Optional): If this introduction point is a legacy one that is version <=
+ * 0.2.9.x (HSIntro=3), we use this extra key for the intro point to be able
+ * to relay the cells to the service correctly. */
+ struct {
+ /* RSA public key. */
+ crypto_pk_t *key;
+
+ /* Cross certified cert with the descriptor signing key (RSA->Ed). Because
+ * of the cross certification API, we need to keep the certificate binary
+ * blob and its length in order to properly encode it after. */
+ struct {
+ uint8_t *encoded;
+ size_t len;
+ } cert;
+ } legacy;
+
+ /* True iff the introduction point has passed the cross certification. Upon
+ * decoding an intro point, this must be true. */
+ unsigned int cross_certified : 1;
+} hs_desc_intro_point_t;
+
+/* The encrypted data section of a descriptor. Obviously the data in this is
+ * in plaintext but encrypted once encoded. */
+typedef struct hs_desc_encrypted_data_t {
+ /* Bitfield of CREATE2 cell supported formats. The only currently supported
+ * format is ntor. */
+ unsigned int create2_ntor : 1;
+
+ /* A list of authentication types that a client must at least support one
+ * in order to contact the service. Contains NULL terminated strings. */
+ smartlist_t *intro_auth_types;
+
+ /* Is this descriptor a single onion service? */
+ unsigned int single_onion_service : 1;
+
+ /* A list of intro points. Contains hs_desc_intro_point_t objects. */
+ smartlist_t *intro_points;
+} hs_desc_encrypted_data_t;
+
+/* Plaintext data that is unencrypted information of the descriptor. */
+typedef struct hs_desc_plaintext_data_t {
+ /* Version of the descriptor format. Spec specifies this field as a
+ * positive integer. */
+ uint32_t version;
+
+ /* The lifetime of the descriptor in seconds. */
+ uint32_t lifetime_sec;
+
+ /* Certificate with the short-term ed22519 descriptor signing key for the
+ * replica which is signed by the blinded public key for that replica. */
+ tor_cert_t *signing_key_cert;
+
+ /* Signing public key which is used to sign the descriptor. Same public key
+ * as in the signing key certificate. */
+ ed25519_public_key_t signing_pubkey;
+
+ /* Blinded public key used for this descriptor derived from the master
+ * identity key and generated for a specific replica number. */
+ ed25519_public_key_t blinded_pubkey;
+
+ /* Revision counter is incremented at each upload, regardless of whether
+ * the descriptor has changed. This avoids leaking whether the descriptor
+ * has changed. Spec specifies this as a 8 bytes positive integer. */
+ uint64_t revision_counter;
+
+ /* Decoding only: The b64-decoded superencrypted blob from the descriptor */
+ uint8_t *superencrypted_blob;
+
+ /* Decoding only: Size of the superencrypted_blob */
+ size_t superencrypted_blob_size;
+} hs_desc_plaintext_data_t;
+
+/* Service descriptor in its decoded form. */
+typedef struct hs_descriptor_t {
+ /* Contains the plaintext part of the descriptor. */
+ hs_desc_plaintext_data_t plaintext_data;
+
+ /* The following contains what's in the encrypted part of the descriptor.
+ * It's only encrypted in the encoded version of the descriptor thus the
+ * data contained in that object is in plaintext. */
+ hs_desc_encrypted_data_t encrypted_data;
+
+ /* Subcredentials of a service, used by the client and service to decrypt
+ * the encrypted data. */
+ uint8_t subcredential[DIGEST256_LEN];
+} hs_descriptor_t;
+
+/* Return true iff the given descriptor format version is supported. */
+static inline int
+hs_desc_is_supported_version(uint32_t version)
+{
+ if (version < HS_DESC_SUPPORTED_FORMAT_VERSION_MIN ||
+ version > HS_DESC_SUPPORTED_FORMAT_VERSION_MAX) {
+ return 0;
+ }
+ return 1;
+}
+
+/* Public API. */
+
+void hs_descriptor_free_(hs_descriptor_t *desc);
+#define hs_descriptor_free(desc) \
+ FREE_AND_NULL(hs_descriptor_t, hs_descriptor_free_, (desc))
+void hs_desc_plaintext_data_free_(hs_desc_plaintext_data_t *desc);
+#define hs_desc_plaintext_data_free(desc) \
+ FREE_AND_NULL(hs_desc_plaintext_data_t, hs_desc_plaintext_data_free_, (desc))
+void hs_desc_encrypted_data_free_(hs_desc_encrypted_data_t *desc);
+#define hs_desc_encrypted_data_free(desc) \
+ FREE_AND_NULL(hs_desc_encrypted_data_t, hs_desc_encrypted_data_free_, (desc))
+
+void hs_desc_link_specifier_free_(hs_desc_link_specifier_t *ls);
+#define hs_desc_link_specifier_free(ls) \
+ FREE_AND_NULL(hs_desc_link_specifier_t, hs_desc_link_specifier_free_, (ls))
+
+hs_desc_link_specifier_t *hs_desc_link_specifier_new(
+ const extend_info_t *info, uint8_t type);
+void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
+
+MOCK_DECL(int,
+ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out));
+
+int hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out);
+int hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext);
+int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_out);
+
+size_t hs_desc_obj_size(const hs_descriptor_t *data);
+size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
+
+hs_desc_intro_point_t *hs_desc_intro_point_new(void);
+void hs_desc_intro_point_free_(hs_desc_intro_point_t *ip);
+#define hs_desc_intro_point_free(ip) \
+ FREE_AND_NULL(hs_desc_intro_point_t, hs_desc_intro_point_free_, (ip))
+
+link_specifier_t *hs_desc_lspec_to_trunnel(
+ const hs_desc_link_specifier_t *spec);
+
+#ifdef HS_DESCRIPTOR_PRIVATE
+
+/* Encoding. */
+STATIC char *encode_link_specifiers(const smartlist_t *specs);
+STATIC size_t build_plaintext_padding(const char *plaintext,
+ size_t plaintext_len,
+ uint8_t **padded_out);
+/* Decoding. */
+STATIC smartlist_t *decode_link_specifiers(const char *encoded);
+STATIC hs_desc_intro_point_t *decode_introduction_point(
+ const hs_descriptor_t *desc,
+ const char *text);
+STATIC int encrypted_data_length_is_valid(size_t len);
+STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type,
+ const char *log_obj_type);
+STATIC int desc_sig_is_valid(const char *b64_sig,
+ const ed25519_public_key_t *signing_pubkey,
+ const char *encoded_desc, size_t encoded_len);
+STATIC size_t decode_superencrypted(const char *message, size_t message_len,
+ uint8_t **encrypted_out);
+STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc);
+
+MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc,
+ const uint8_t *encrypted_blob,
+ size_t encrypted_blob_size,
+ int is_superencrypted_layer,
+ char **decrypted_out));
+
+#endif /* defined(HS_DESCRIPTOR_PRIVATE) */
+
+#endif /* !defined(TOR_HS_DESCRIPTOR_H) */
diff --git a/src/feature/hs/hs_ident.c b/src/feature/hs/hs_ident.c
new file mode 100644
index 0000000000..20539ca878
--- /dev/null
+++ b/src/feature/hs/hs_ident.c
@@ -0,0 +1,127 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.c
+ * \brief Contains circuit and connection identifier code for the whole HS
+ * subsytem.
+ **/
+
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/hs_ident.h"
+
+/* Return a newly allocated circuit identifier. The given public key is copied
+ * identity_pk into the identifier. */
+hs_ident_circuit_t *
+hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type)
+{
+ tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
+ circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ ident->circuit_type = circuit_type;
+ return ident;
+}
+
+/* Free the given circuit identifier. */
+void
+hs_ident_circuit_free_(hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_circuit_t));
+ tor_free(ident);
+}
+
+/* For a given circuit identifier src, return a newly allocated copy of it.
+ * This can't fail. */
+hs_ident_circuit_t *
+hs_ident_circuit_dup(const hs_ident_circuit_t *src)
+{
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* For a given directory connection identifier src, return a newly allocated
+ * copy of it. This can't fail. */
+hs_ident_dir_conn_t *
+hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
+{
+ hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* Free the given directory connection identifier. */
+void
+hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
+ tor_free(ident);
+}
+
+/* Initialized the allocated ident object with identity_pk and blinded_pk.
+ * None of them can be NULL since a valid directory connection identifier must
+ * have all fields set. */
+void
+hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ hs_ident_dir_conn_t *ident)
+{
+ tor_assert(identity_pk);
+ tor_assert(blinded_pk);
+ tor_assert(ident);
+
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk);
+}
+
+/* Return a newly allocated edge connection identifier. The given public key
+ * identity_pk is copied into the identifier. */
+hs_ident_edge_conn_t *
+hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
+{
+ hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ return ident;
+}
+
+/* Free the given edge connection identifier. */
+void
+hs_ident_edge_conn_free_(hs_ident_edge_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
+ tor_free(ident);
+}
+
+/* Return true if the given ident is valid for an introduction circuit. */
+int
+hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ goto invalid;
+ }
+
+ if (ed25519_public_key_is_zero(&ident->identity_pk)) {
+ goto invalid;
+ }
+
+ if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) {
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 1;
+ invalid:
+ return 0;
+}
+
diff --git a/src/feature/hs/hs_ident.h b/src/feature/hs/hs_ident.h
new file mode 100644
index 0000000000..8c53b9dbeb
--- /dev/null
+++ b/src/feature/hs/hs_ident.h
@@ -0,0 +1,146 @@
+/* Copyright (c) 2017-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.h
+ * \brief Header file containing circuit and connection identifier data for
+ * the whole HS subsytem.
+ *
+ * \details
+ * This interface is used to uniquely identify a hidden service on a circuit
+ * or connection using the service identity public key. Once the circuit or
+ * connection subsystem calls in the hidden service one, we use those
+ * identifiers to lookup the corresponding objects like service, intro point
+ * and descriptor.
+ *
+ * Furthermore, the circuit identifier holds cryptographic material needed for
+ * the e2e encryption on the rendezvous circuit which is set once the
+ * rendezvous circuit has opened and ready to be used.
+ **/
+
+#ifndef TOR_HS_IDENT_H
+#define TOR_HS_IDENT_H
+
+#include "lib/crypt_ops/crypto_ed25519.h"
+
+#include "or/hs_common.h"
+
+/* Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define HS_REND_COOKIE_LEN DIGEST_LEN
+
+/* Type of circuit an hs_ident_t object is associated with. */
+typedef enum {
+ HS_IDENT_CIRCUIT_INTRO = 1,
+ HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
+} hs_ident_circuit_type_t;
+
+/* Client and service side circuit identifier that is used for hidden service
+ * circuit establishment. Not all fields contain data, it depends on the
+ * circuit purpose. This is attached to an origin_circuit_t. All fields are
+ * used by both client and service. */
+typedef struct hs_ident_circuit_t {
+ /* (All circuit) The public key used to uniquely identify the service. It is
+ * the one found in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* (All circuit) The type of circuit this identifier is attached to.
+ * Accessors of the fields in this object assert non fatal on this circuit
+ * type. In other words, if a rendezvous field is being accessed, the
+ * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
+ * set when an object is initialized in its constructor. */
+ hs_ident_circuit_type_t circuit_type;
+
+ /* (All circuit) Introduction point authentication key. It's also needed on
+ * the rendezvous circuit for the ntor handshake. It's used as the unique key
+ * of the introduction point so it should not be shared between multiple
+ * intro points. */
+ ed25519_public_key_t intro_auth_pk;
+
+ /* (Only client rendezvous circuit) Introduction point encryption public
+ * key. We keep it in the rendezvous identifier for the ntor handshake. */
+ curve25519_public_key_t intro_enc_pk;
+
+ /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the
+ * service with an INTRODUCE1 cell and used by the service in an
+ * RENDEZVOUS1 cell. */
+ uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN];
+
+ /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the
+ * RENDEZVOUS1 cell of the service. The construction is as follows:
+ * SERVER_PK [32 bytes]
+ * AUTH_MAC [32 bytes]
+ */
+ uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+ /* (Only client rendezvous circuit) Client ephemeral keypair needed for the
+ * e2e encryption with the service. */
+ curve25519_keypair_t rendezvous_client_kp;
+
+ /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for
+ * the e2e encryption with the client on the circuit. */
+ uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN];
+
+ /* (Only rendezvous circuit) Number of streams associated with this
+ * rendezvous circuit. We track this because there is a check on a maximum
+ * value. */
+ uint64_t num_rdv_streams;
+} hs_ident_circuit_t;
+
+/* Client and service side directory connection identifier used for a
+ * directory connection to identify which service is being queried. This is
+ * attached to a dir_connection_t. */
+typedef struct hs_ident_dir_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* The blinded public key used to uniquely identify the descriptor that this
+ * directory connection identifier is for. Only used by the service-side code
+ * to fine control descriptor uploads. */
+ ed25519_public_key_t blinded_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_dir_conn_t;
+
+/* Client and service side edge connection identifier used for an edge
+ * connection to identify which service is being queried. This is attached to
+ * a edge_connection_t. */
+typedef struct hs_ident_edge_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_edge_conn_t;
+
+/* Circuit identifier API. */
+hs_ident_circuit_t *hs_ident_circuit_new(
+ const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type);
+void hs_ident_circuit_free_(hs_ident_circuit_t *ident);
+#define hs_ident_circuit_free(id) \
+ FREE_AND_NULL(hs_ident_circuit_t, hs_ident_circuit_free_, (id))
+hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src);
+
+/* Directory connection identifier API. */
+hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
+void hs_ident_dir_conn_free_(hs_ident_dir_conn_t *ident);
+#define hs_ident_dir_conn_free(id) \
+ FREE_AND_NULL(hs_ident_dir_conn_t, hs_ident_dir_conn_free_, (id))
+void hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ hs_ident_dir_conn_t *ident);
+
+/* Edge connection identifier API. */
+hs_ident_edge_conn_t *hs_ident_edge_conn_new(
+ const ed25519_public_key_t *identity_pk);
+void hs_ident_edge_conn_free_(hs_ident_edge_conn_t *ident);
+#define hs_ident_edge_conn_free(id) \
+ FREE_AND_NULL(hs_ident_edge_conn_t, hs_ident_edge_conn_free_, (id))
+
+/* Validators */
+int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident);
+
+#endif /* !defined(TOR_HS_IDENT_H) */
+
diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c
new file mode 100644
index 0000000000..2594058679
--- /dev/null
+++ b/src/feature/hs/hs_intropoint.c
@@ -0,0 +1,614 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.c
+ * \brief Implement next generation introductions point functionality
+ **/
+
+#define HS_INTROPOINT_PRIVATE
+
+#include "or/or.h"
+#include "or/config.h"
+#include "or/circuitlist.h"
+#include "or/circuituse.h"
+#include "or/relay.h"
+#include "or/rendmid.h"
+#include "or/rephist.h"
+#include "lib/crypt_ops/crypto_format.h"
+
+/* Trunnel */
+#include "trunnel/ed25519_cert.h"
+#include "trunnel/hs/cell_common.h"
+#include "trunnel/hs/cell_establish_intro.h"
+#include "trunnel/hs/cell_introduce1.h"
+
+#include "or/hs_circuitmap.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_intropoint.h"
+#include "or/hs_common.h"
+
+#include "or/or_circuit_st.h"
+
+/** Extract the authentication key from an ESTABLISH_INTRO or INTRODUCE1 using
+ * the given <b>cell_type</b> from <b>cell</b> and place it in
+ * <b>auth_key_out</b>. */
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell)
+{
+ size_t auth_key_len;
+ const uint8_t *key_array;
+
+ tor_assert(auth_key_out);
+ tor_assert(cell);
+
+ switch (cell_type) {
+ case RELAY_COMMAND_ESTABLISH_INTRO:
+ {
+ const trn_cell_establish_intro_t *c_cell = cell;
+ key_array = trn_cell_establish_intro_getconstarray_auth_key(c_cell);
+ auth_key_len = trn_cell_establish_intro_getlen_auth_key(c_cell);
+ break;
+ }
+ case RELAY_COMMAND_INTRODUCE1:
+ {
+ const trn_cell_introduce1_t *c_cell = cell;
+ key_array = trn_cell_introduce1_getconstarray_auth_key(cell);
+ auth_key_len = trn_cell_introduce1_getlen_auth_key(c_cell);
+ break;
+ }
+ default:
+ /* Getting here is really bad as it means we got a unknown cell type from
+ * this file where every call has an hardcoded value. */
+ tor_assert(0); /* LCOV_EXCL_LINE */
+ }
+ tor_assert(key_array);
+ tor_assert(auth_key_len == sizeof(auth_key_out->pubkey));
+ memcpy(auth_key_out->pubkey, key_array, auth_key_len);
+}
+
+/** We received an ESTABLISH_INTRO <b>cell</b>. Verify its signature and MAC,
+ * given <b>circuit_key_material</b>. Return 0 on success else -1 on error. */
+STATIC int
+verify_establish_intro_cell(const trn_cell_establish_intro_t *cell,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len)
+{
+ /* We only reach this function if the first byte of the cell is 0x02 which
+ * means that auth_key_type is of ed25519 type, hence this check should
+ * always pass. See hs_intro_received_establish_intro(). */
+ if (BUG(cell->auth_key_type != HS_INTRO_AUTH_KEY_TYPE_ED25519)) {
+ return -1;
+ }
+
+ /* Make sure the auth key length is of the right size for this type. For
+ * EXTRA safety, we check both the size of the array and the length which
+ * must be the same. Safety first!*/
+ if (trn_cell_establish_intro_getlen_auth_key(cell) != ED25519_PUBKEY_LEN ||
+ trn_cell_establish_intro_get_auth_key_len(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO auth key length is invalid");
+ return -1;
+ }
+
+ const uint8_t *msg = cell->start_cell;
+
+ /* Verify the sig */
+ {
+ ed25519_signature_t sig_struct;
+ const uint8_t *sig_array =
+ trn_cell_establish_intro_getconstarray_sig(cell);
+
+ /* Make sure the signature length is of the right size. For EXTRA safety,
+ * we check both the size of the array and the length which must be the
+ * same. Safety first!*/
+ if (trn_cell_establish_intro_getlen_sig(cell) != sizeof(sig_struct.sig) ||
+ trn_cell_establish_intro_get_sig_len(cell) != sizeof(sig_struct.sig)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO sig len is invalid");
+ return -1;
+ }
+ /* We are now sure that sig_len is of the right size. */
+ memcpy(sig_struct.sig, sig_array, cell->sig_len);
+
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, cell);
+
+ const size_t sig_msg_len = cell->end_sig_fields - msg;
+ int sig_mismatch = ed25519_checksig_prefixed(&sig_struct,
+ msg, sig_msg_len,
+ ESTABLISH_INTRO_SIG_PREFIX,
+ &auth_key);
+ if (sig_mismatch) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO signature not as expected");
+ return -1;
+ }
+ }
+
+ /* Verify the MAC */
+ {
+ const size_t auth_msg_len = cell->end_mac_fields - msg;
+ uint8_t mac[DIGEST256_LEN];
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ circuit_key_material, circuit_key_material_len,
+ msg, auth_msg_len);
+ if (tor_memneq(mac, cell->handshake_mac, sizeof(mac))) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO handshake_auth not as expected");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Send an INTRO_ESTABLISHED cell to <b>circ</b>. */
+MOCK_IMPL(int,
+hs_intro_send_intro_established_cell,(or_circuit_t *circ))
+{
+ int ret;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ trn_cell_intro_established_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Build the cell payload. */
+ cell = trn_cell_intro_established_new();
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_intro_established_set_extensions(cell, ext);
+ /* Encode the cell to binary format. */
+ encoded_len = trn_cell_intro_established_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = trn_cell_intro_established_encode(encoded_cell, encoded_len,
+ cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRO_ESTABLISHED,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ trn_cell_intro_established_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's
+ * well-formed and passed our verifications. Perform appropriate actions to
+ * establish an intro point. */
+static int
+handle_verified_establish_intro_cell(or_circuit_t *circ,
+ const trn_cell_establish_intro_t *parsed_cell)
+{
+ /* Get the auth key of this intro point */
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO,
+ parsed_cell);
+
+ /* Then notify the hidden service that the intro point is established by
+ sending an INTRO_ESTABLISHED cell */
+ if (hs_intro_send_intro_established_cell(circ)) {
+ log_warn(LD_PROTOCOL, "Couldn't send INTRO_ESTABLISHED cell.");
+ return -1;
+ }
+
+ /* Associate intro point auth key with this circuit. */
+ hs_circuitmap_register_intro_circ_v3_relay_side(circ, &auth_key);
+ /* Repurpose this circuit into an intro circuit. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
+
+ return 0;
+}
+
+/** We just received an ESTABLISH_INTRO cell in <b>circ</b> with payload in
+ * <b>request</b>. Handle it by making <b>circ</b> an intro circuit. Return 0
+ * if everything went well, or -1 if there were errors. */
+static int
+handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int cell_ok, retval = -1;
+ trn_cell_establish_intro_t *parsed_cell = NULL;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ log_info(LD_REND, "Received an ESTABLISH_INTRO request on circuit %" PRIu32,
+ circ->p_circ_id);
+
+ /* Check that the circuit is in shape to become an intro point */
+ if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
+ goto err;
+ }
+
+ /* Parse the cell */
+ ssize_t parsing_result = trn_cell_establish_intro_parse(&parsed_cell,
+ request, request_len);
+ if (parsing_result < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s ESTABLISH_INTRO cell.",
+ parsing_result == -1 ? "invalid" : "truncated");
+ goto err;
+ }
+
+ cell_ok = verify_establish_intro_cell(parsed_cell,
+ (uint8_t *) circ->rend_circ_nonce,
+ sizeof(circ->rend_circ_nonce));
+ if (cell_ok < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Failed to verify ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* This cell is legit. Take the appropriate actions. */
+ cell_ok = handle_verified_establish_intro_cell(circ, parsed_cell);
+ if (cell_ok < 0) {
+ goto err;
+ }
+
+ /* We are done! */
+ retval = 0;
+ goto done;
+
+ err:
+ /* When sending the intro establish ack, on error the circuit can be marked
+ * as closed so avoid a double close. */
+ if (!TO_CIRCUIT(circ)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ }
+
+ done:
+ trn_cell_establish_intro_free(parsed_cell);
+ return retval;
+}
+
+/* Return True if circuit is suitable for being an intro circuit. */
+static int
+circuit_is_suitable_intro_point(const or_circuit_t *circ,
+ const char *log_cell_type_str)
+{
+ tor_assert(circ);
+ tor_assert(log_cell_type_str);
+
+ /* Basic circuit state sanity checks. */
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_OR) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-OR circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ if (circ->base_.n_chan) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-edge circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ /* Suitable. */
+ return 1;
+}
+
+/* Return True if circuit is suitable for being service-side intro circuit. */
+int
+hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ)
+{
+ return circuit_is_suitable_intro_point(circ, "ESTABLISH_INTRO");
+}
+
+/* We just received an ESTABLISH_INTRO cell in <b>circ</b>. Figure out of it's
+ * a legacy or a next gen cell, and pass it to the appropriate handler. */
+int
+hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ tor_assert(circ);
+ tor_assert(request);
+
+ if (request_len == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Empty ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* Using the first byte of the cell, figure out the version of
+ * ESTABLISH_INTRO and pass it to the appropriate cell handler */
+ const uint8_t first_byte = request[0];
+ switch (first_byte) {
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY0:
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY1:
+ return rend_mid_establish_intro_legacy(circ, request, request_len);
+ case HS_INTRO_AUTH_KEY_TYPE_ED25519:
+ return handle_establish_intro(circ, request, request_len);
+ default:
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unrecognized AUTH_KEY_TYPE %u.", first_byte);
+ goto err;
+ }
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Send an INTRODUCE_ACK cell onto the circuit <b>circ</b> with the status
+ * value in <b>status</b>. Depending on the status, it can be ACK or a NACK.
+ * Return 0 on success else a negative value on error which will close the
+ * circuit. */
+static int
+send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status)
+{
+ int ret = -1;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ trn_cell_introduce_ack_t *cell;
+ trn_cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Setup the INTRODUCE_ACK cell. We have no extensions so the N_EXTENSIONS
+ * field is set to 0 by default with a new object. */
+ cell = trn_cell_introduce_ack_new();
+ ret = trn_cell_introduce_ack_set_status(cell, status);
+ /* We have no cell extensions in an INTRODUCE_ACK cell. */
+ ext = trn_cell_extension_new();
+ trn_cell_extension_set_num(ext, 0);
+ trn_cell_introduce_ack_set_extensions(cell, ext);
+ /* A wrong status is a very bad code flow error as this value is controlled
+ * by the code in this file and not an external input. This means we use a
+ * code that is not known by the trunnel ABI. */
+ tor_assert(ret == 0);
+ /* Encode the payload. We should never fail to get the encoded length. */
+ encoded_len = trn_cell_introduce_ack_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = trn_cell_introduce_ack_encode(encoded_cell, encoded_len, cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRODUCE_ACK,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ trn_cell_introduce_ack_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/* Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a
+ * negative value for an invalid cell that should be NACKed. */
+STATIC int
+validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell)
+{
+ size_t legacy_key_id_len;
+ const uint8_t *legacy_key_id;
+
+ tor_assert(cell);
+
+ /* This code path SHOULD NEVER be reached if the cell is a legacy type so
+ * safety net here. The legacy ID must be zeroes in this case. */
+ legacy_key_id_len = trn_cell_introduce1_getlen_legacy_key_id(cell);
+ legacy_key_id = trn_cell_introduce1_getconstarray_legacy_key_id(cell);
+ if (BUG(!tor_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) {
+ goto invalid;
+ }
+
+ /* The auth key of an INTRODUCE1 should be of type ed25519 thus leading to a
+ * known fixed length as well. */
+ if (trn_cell_introduce1_get_auth_key_type(cell) !=
+ HS_INTRO_AUTH_KEY_TYPE_ED25519) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key type. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (trn_cell_introduce1_get_auth_key_len(cell) != ED25519_PUBKEY_LEN ||
+ trn_cell_introduce1_getlen_auth_key(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (trn_cell_introduce1_getlen_encrypted(cell) == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell encrypted length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* We just received a non legacy INTRODUCE1 cell on <b>client_circ</b> with
+ * the payload in <b>request</b> of size <b>request_len</b>. Return 0 if
+ * everything went well, or -1 if an error occurred. This function is in charge
+ * of sending back an INTRODUCE_ACK cell and will close client_circ on error.
+ */
+STATIC int
+handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret = -1;
+ or_circuit_t *service_circ;
+ trn_cell_introduce1_t *parsed_cell;
+ hs_intro_ack_status_t status = HS_INTRO_ACK_STATUS_SUCCESS;
+
+ tor_assert(client_circ);
+ tor_assert(request);
+
+ /* Parse cell. Note that we can only parse the non encrypted section for
+ * which we'll use the authentication key to find the service introduction
+ * circuit and relay the cell on it. */
+ ssize_t cell_size = trn_cell_introduce1_parse(&parsed_cell, request,
+ request_len);
+ if (cell_size < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s INTRODUCE1 cell. Responding with NACK.",
+ cell_size == -1 ? "invalid" : "truncated");
+ /* Inform client that the INTRODUCE1 has a bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Once parsed validate the cell format. */
+ if (validate_introduce1_parsed_cell(parsed_cell) < 0) {
+ /* Inform client that the INTRODUCE1 has bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Find introduction circuit through our circuit map. */
+ {
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_INTRODUCE1, parsed_cell);
+ service_circ = hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key);
+ if (service_circ == NULL) {
+ char b64_key[ED25519_BASE64_LEN + 1];
+ ed25519_public_to_base64(b64_key, &auth_key);
+ log_info(LD_REND, "No intro circuit found for INTRODUCE1 cell "
+ "with auth key %s from circuit %" PRIu32 ". "
+ "Responding with NACK.",
+ safe_str(b64_key), client_circ->p_circ_id);
+ /* Inform the client that we don't know the requested service ID. */
+ status = HS_INTRO_ACK_STATUS_UNKNOWN_ID;
+ goto send_ack;
+ }
+ }
+
+ /* Relay the cell to the service on its intro circuit with an INTRODUCE2
+ * cell which is the same exact payload. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
+ RELAY_COMMAND_INTRODUCE2,
+ (char *) request, request_len, NULL)) {
+ log_warn(LD_PROTOCOL, "Unable to send INTRODUCE2 cell to the service.");
+ /* Inform the client that we can't relay the cell. */
+ status = HS_INTRO_ACK_STATUS_CANT_RELAY;
+ goto send_ack;
+ }
+
+ /* Success! Send an INTRODUCE_ACK success status onto the client circuit. */
+ status = HS_INTRO_ACK_STATUS_SUCCESS;
+ ret = 0;
+
+ send_ack:
+ /* Send INTRODUCE_ACK or INTRODUCE_NACK to client */
+ if (send_introduce_ack_cell(client_circ, status) < 0) {
+ log_warn(LD_PROTOCOL, "Unable to send an INTRODUCE ACK status %d "
+ "to client.", status);
+ /* Circuit has been closed on failure of transmission. */
+ goto done;
+ }
+ if (status != HS_INTRO_ACK_STATUS_SUCCESS) {
+ /* We just sent a NACK that is a non success status code so close the
+ * circuit because it's not useful to keep it open. Remember, a client can
+ * only send one INTRODUCE1 cell on a circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(client_circ), END_CIRC_REASON_INTERNAL);
+ }
+ done:
+ trn_cell_introduce1_free(parsed_cell);
+ return ret;
+}
+
+/* Identify if the encoded cell we just received is a legacy one or not. The
+ * <b>request</b> should be at least DIGEST_LEN bytes long. */
+STATIC int
+introduce1_cell_is_legacy(const uint8_t *request)
+{
+ tor_assert(request);
+
+ /* If the first 20 bytes of the cell (DIGEST_LEN) are NOT zeroes, it
+ * indicates a legacy cell (v2). */
+ if (!tor_mem_is_zero((const char *) request, DIGEST_LEN)) {
+ /* Legacy cell. */
+ return 1;
+ }
+ /* Not a legacy cell. */
+ return 0;
+}
+
+/* Return true iff the circuit <b>circ</b> is suitable for receiving an
+ * INTRODUCE1 cell. */
+STATIC int
+circuit_is_suitable_for_introduce1(const or_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Is this circuit an intro point circuit? */
+ if (!circuit_is_suitable_intro_point(circ, "INTRODUCE1")) {
+ return 0;
+ }
+
+ if (circ->already_received_introduce1) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Blocking multiple introductions on the same circuit. "
+ "Someone might be trying to attack a hidden service through "
+ "this relay.");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* We just received an INTRODUCE1 cell on <b>circ</b>. Figure out which type
+ * it is and pass it to the appropriate handler. Return 0 on success else a
+ * negative value and the circuit is closed. */
+int
+hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ /* A cell that can't hold a DIGEST_LEN is invalid as we need to check if
+ * it's a legacy cell or not using the first DIGEST_LEN bytes. */
+ if (request_len < DIGEST_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Invalid INTRODUCE1 cell length.");
+ goto err;
+ }
+
+ /* Make sure we have a circuit that can have an INTRODUCE1 cell on it. */
+ if (!circuit_is_suitable_for_introduce1(circ)) {
+ /* We do not send a NACK because the circuit is not suitable for any kind
+ * of response or transmission as it's a violation of the protocol. */
+ goto err;
+ }
+ /* Mark the circuit that we got this cell. None are allowed after this as a
+ * DoS mitigation since one circuit with one client can hammer a service. */
+ circ->already_received_introduce1 = 1;
+
+ /* We are sure here to have at least DIGEST_LEN bytes. */
+ if (introduce1_cell_is_legacy(request)) {
+ /* Handle a legacy cell. */
+ ret = rend_mid_introduce_legacy(circ, request, request_len);
+ } else {
+ /* Handle a non legacy cell. */
+ ret = handle_introduce1(circ, request, request_len);
+ }
+ return ret;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Clear memory allocated by the given intropoint object ip (but don't free the
+ * object itself). */
+void
+hs_intropoint_clear(hs_intropoint_t *ip)
+{
+ if (ip == NULL) {
+ return;
+ }
+ tor_cert_free(ip->auth_key_cert);
+ SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls,
+ hs_desc_link_specifier_free(ls));
+ smartlist_free(ip->link_specifiers);
+ memset(ip, 0, sizeof(hs_intropoint_t));
+}
diff --git a/src/feature/hs/hs_intropoint.h b/src/feature/hs/hs_intropoint.h
new file mode 100644
index 0000000000..5f82920991
--- /dev/null
+++ b/src/feature/hs/hs_intropoint.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.h
+ * \brief Header file for hs_intropoint.c.
+ **/
+
+#ifndef TOR_HS_INTRO_H
+#define TOR_HS_INTRO_H
+
+#include "lib/crypt_ops/crypto_curve25519.h"
+#include "or/torcert.h"
+
+/* Authentication key type in an ESTABLISH_INTRO cell. */
+typedef enum {
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00,
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02,
+} hs_intro_auth_key_type_t;
+
+/* INTRODUCE_ACK status code. */
+typedef enum {
+ HS_INTRO_ACK_STATUS_SUCCESS = 0x0000,
+ HS_INTRO_ACK_STATUS_UNKNOWN_ID = 0x0001,
+ HS_INTRO_ACK_STATUS_BAD_FORMAT = 0x0002,
+ HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003,
+} hs_intro_ack_status_t;
+
+/* Object containing introduction point common data between the service and
+ * the client side. */
+typedef struct hs_intropoint_t {
+ /* Does this intro point only supports legacy ID ?. */
+ unsigned int is_only_legacy : 1;
+
+ /* Authentication key certificate from the descriptor. */
+ tor_cert_t *auth_key_cert;
+ /* A list of link specifier. */
+ smartlist_t *link_specifiers;
+} hs_intropoint_t;
+
+int hs_intro_received_establish_intro(or_circuit_t *circ,
+ const uint8_t *request,
+ size_t request_len);
+int hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
+
+MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ));
+
+/* also used by rendservice.c */
+int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ);
+
+hs_intropoint_t *hs_intro_new(void);
+void hs_intropoint_clear(hs_intropoint_t *ip);
+
+#ifdef HS_INTROPOINT_PRIVATE
+
+#include "trunnel/hs/cell_establish_intro.h"
+#include "trunnel/hs/cell_introduce1.h"
+
+STATIC int
+verify_establish_intro_cell(const trn_cell_establish_intro_t *out,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len);
+
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell);
+
+STATIC int introduce1_cell_is_legacy(const uint8_t *request);
+STATIC int handle_introduce1(or_circuit_t *client_circ,
+ const uint8_t *request, size_t request_len);
+STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell);
+STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ);
+
+#endif /* defined(HS_INTROPOINT_PRIVATE) */
+
+#endif /* !defined(TOR_HS_INTRO_H) */
+
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
new file mode 100644
index 0000000000..6cb01b57c9
--- /dev/null
+++ b/src/feature/hs/hs_service.c
@@ -0,0 +1,3644 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service functionality
+ **/
+
+#define HS_SERVICE_PRIVATE
+
+#include "or/or.h"
+#include "or/circpathbias.h"
+#include "or/circuitbuild.h"
+#include "or/circuitlist.h"
+#include "or/circuituse.h"
+#include "or/config.h"
+#include "or/connection.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "or/directory.h"
+#include "or/main.h"
+#include "or/networkstatus.h"
+#include "or/nodelist.h"
+#include "or/relay.h"
+#include "or/rendservice.h"
+#include "or/router.h"
+#include "or/routerkeys.h"
+#include "or/routerlist.h"
+#include "or/shared_random_client.h"
+#include "or/statefile.h"
+
+#include "or/hs_circuit.h"
+#include "or/hs_common.h"
+#include "or/hs_config.h"
+#include "or/hs_control.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_ident.h"
+#include "or/hs_intropoint.h"
+#include "or/hs_service.h"
+#include "or/hs_stats.h"
+
+#include "or/dir_connection_st.h"
+#include "or/edge_connection_st.h"
+#include "or/extend_info_st.h"
+#include "or/networkstatus_st.h"
+#include "or/node_st.h"
+#include "or/origin_circuit_st.h"
+#include "or/or_state_st.h"
+#include "or/routerstatus_st.h"
+
+#include "lib/encoding/confline.h"
+#include "lib/crypt_ops/crypto_format.h"
+
+/* Trunnel */
+#include "trunnel/ed25519_cert.h"
+#include "trunnel/hs/cell_common.h"
+#include "trunnel/hs/cell_establish_intro.h"
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* Helper macro. Iterate over every service in the global map. The var is the
+ * name of the service pointer. */
+#define FOR_EACH_SERVICE_BEGIN(var) \
+ STMT_BEGIN \
+ hs_service_t **var##_iter, *var; \
+ HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \
+ var = *var##_iter;
+#define FOR_EACH_SERVICE_END } STMT_END ;
+
+/* Helper macro. Iterate over both current and previous descriptor of a
+ * service. The var is the name of the descriptor pointer. This macro skips
+ * any descriptor object of the service that is NULL. */
+#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \
+ STMT_BEGIN \
+ hs_service_descriptor_t *var; \
+ for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \
+ ++var ## _loop_idx) { \
+ (var ## _loop_idx == 0) ? (var = service->desc_current) : \
+ (var = service->desc_next); \
+ if (var == NULL) continue;
+#define FOR_EACH_DESCRIPTOR_END } STMT_END ;
+
+/* Onion service directory file names. */
+static const char fname_keyfile_prefix[] = "hs_ed25519";
+static const char fname_hostname[] = "hostname";
+static const char address_tld[] = "onion";
+
+/* Staging list of service object. When configuring service, we add them to
+ * this list considered a staging area and they will get added to our global
+ * map once the keys have been loaded. These two steps are separated because
+ * loading keys requires that we are an actual running tor process. */
+static smartlist_t *hs_service_staging_list;
+
+/** True if the list of available router descriptors might have changed which
+ * might result in an altered hash ring. Check if the hash ring changed and
+ * reupload if needed */
+static int consider_republishing_hs_descriptors = 0;
+
+/* Static declaration. */
+static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc);
+static void move_descriptors(hs_service_t *src, hs_service_t *dst);
+
+/* Helper: Function to compare two objects in the service map. Return 1 if the
+ * two service have the same master public identity key. */
+static inline int
+hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second)
+{
+ tor_assert(first);
+ tor_assert(second);
+ /* Simple key compare. */
+ return ed25519_pubkey_eq(&first->keys.identity_pk,
+ &second->keys.identity_pk);
+}
+
+/* Helper: Function for the service hash table code below. The key used is the
+ * master public identity key which is ultimately the onion address. */
+static inline unsigned int
+hs_service_ht_hash(const hs_service_t *service)
+{
+ tor_assert(service);
+ return (unsigned int) siphash24g(service->keys.identity_pk.pubkey,
+ sizeof(service->keys.identity_pk.pubkey));
+}
+
+/* This is _the_ global hash map of hidden services which indexed the service
+ * contained in it by master public identity key which is roughly the onion
+ * address of the service. */
+static struct hs_service_ht *hs_service_map;
+
+/* Register the service hash table. */
+HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */
+ hs_service_t, /* Object contained in the map. */
+ hs_service_node, /* The name of the HT_ENTRY member. */
+ hs_service_ht_hash, /* Hashing function. */
+ hs_service_ht_eq) /* Compare function for objects. */
+
+HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node,
+ hs_service_ht_hash, hs_service_ht_eq,
+ 0.6, tor_reallocarray, tor_free_)
+
+/* Query the given service map with a public key and return a service object
+ * if found else NULL. It is also possible to set a directory path in the
+ * search query. If pk is NULL, then it will be set to zero indicating the
+ * hash table to compare the directory path instead. */
+STATIC hs_service_t *
+find_service(hs_service_ht *map, const ed25519_public_key_t *pk)
+{
+ hs_service_t dummy_service;
+ tor_assert(map);
+ tor_assert(pk);
+ memset(&dummy_service, 0, sizeof(dummy_service));
+ ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk);
+ return HT_FIND(hs_service_ht, map, &dummy_service);
+}
+
+/* Register the given service in the given map. If the service already exists
+ * in the map, -1 is returned. On success, 0 is returned and the service
+ * ownership has been transferred to the global map. */
+STATIC int
+register_service(hs_service_ht *map, hs_service_t *service)
+{
+ tor_assert(map);
+ tor_assert(service);
+ tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk));
+
+ if (find_service(map, &service->keys.identity_pk)) {
+ /* Existing service with the same key. Do not register it. */
+ return -1;
+ }
+ /* Taking ownership of the object at this point. */
+ HT_INSERT(hs_service_ht, map, service);
+
+ /* If we just modified the global map, we notify. */
+ if (map == hs_service_map) {
+ hs_service_map_has_changed();
+ }
+
+ return 0;
+}
+
+/* Remove a given service from the given map. If service is NULL or the
+ * service key is unset, return gracefully. */
+STATIC void
+remove_service(hs_service_ht *map, hs_service_t *service)
+{
+ hs_service_t *elm;
+
+ tor_assert(map);
+
+ /* Ignore if no service or key is zero. */
+ if (BUG(service == NULL) ||
+ BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) {
+ return;
+ }
+
+ elm = HT_REMOVE(hs_service_ht, map, service);
+ if (elm) {
+ tor_assert(elm == service);
+ } else {
+ log_warn(LD_BUG, "Could not find service in the global map "
+ "while removing service %s",
+ escaped(service->config.directory_path));
+ }
+
+ /* If we just modified the global map, we notify. */
+ if (map == hs_service_map) {
+ hs_service_map_has_changed();
+ }
+}
+
+/* Set the default values for a service configuration object <b>c</b>. */
+static void
+set_service_default_config(hs_service_config_t *c,
+ const or_options_t *options)
+{
+ (void) options;
+ tor_assert(c);
+ c->ports = smartlist_new();
+ c->directory_path = NULL;
+ c->max_streams_per_rdv_circuit = 0;
+ c->max_streams_close_circuit = 0;
+ c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
+ c->allow_unknown_ports = 0;
+ c->is_single_onion = 0;
+ c->dir_group_readable = 0;
+ c->is_ephemeral = 0;
+}
+
+/* From a service configuration object config, clear everything from it
+ * meaning free allocated pointers and reset the values. */
+static void
+service_clear_config(hs_service_config_t *config)
+{
+ if (config == NULL) {
+ return;
+ }
+ tor_free(config->directory_path);
+ if (config->ports) {
+ SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p,
+ rend_service_port_config_free(p););
+ smartlist_free(config->ports);
+ }
+ memset(config, 0, sizeof(*config));
+}
+
+/* Helper function to return a human readable description of the given intro
+ * point object.
+ *
+ * This function is not thread-safe. Each call to this invalidates the
+ * previous values returned by it. */
+static const char *
+describe_intro_point(const hs_service_intro_point_t *ip)
+{
+ /* Hex identity digest of the IP prefixed by the $ sign and ends with NUL
+ * byte hence the plus two. */
+ static char buf[HEX_DIGEST_LEN + 2];
+ const char *legacy_id = NULL;
+
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ const hs_desc_link_specifier_t *, lspec) {
+ if (lspec->type == LS_LEGACY_ID) {
+ legacy_id = (const char *) lspec->u.legacy_id;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(lspec);
+
+ /* For now, we only print the identity digest but we could improve this with
+ * much more information such as the ed25519 identity has well. */
+ buf[0] = '$';
+ if (legacy_id) {
+ base16_encode(buf + 1, HEX_DIGEST_LEN + 1, legacy_id, DIGEST_LEN);
+ }
+
+ return buf;
+}
+
+/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_min_introduce2(void)
+{
+ /* The [0, 2147483647] range is quite large to accommodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_introduce2",
+ INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
+}
+
+/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we
+ * rotate intro point (defined by a consensus parameter or the default
+ * value). */
+static int32_t
+get_intro_point_max_introduce2(void)
+{
+ /* The [0, 2147483647] range is quite large to accommodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_introduce2",
+ INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS,
+ 0, INT32_MAX);
+}
+
+/* Return the minimum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_min_lifetime(void)
+{
+#define MIN_INTRO_POINT_LIFETIME_TESTING 10
+ if (get_options()->TestingTorNetwork) {
+ return MIN_INTRO_POINT_LIFETIME_TESTING;
+ }
+
+ /* The [0, 2147483647] range is quite large to accommodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_min_lifetime",
+ INTRO_POINT_LIFETIME_MIN_SECONDS,
+ 0, INT32_MAX);
+}
+
+/* Return the maximum lifetime in seconds of an introduction point defined by a
+ * consensus parameter or the default value. */
+static int32_t
+get_intro_point_max_lifetime(void)
+{
+#define MAX_INTRO_POINT_LIFETIME_TESTING 30
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_LIFETIME_TESTING;
+ }
+
+ /* The [0, 2147483647] range is quite large to accommodate anything we decide
+ * in the future. */
+ return networkstatus_get_param(NULL, "hs_intro_max_lifetime",
+ INTRO_POINT_LIFETIME_MAX_SECONDS,
+ 0, INT32_MAX);
+}
+
+/* Return the number of extra introduction point defined by a consensus
+ * parameter or the default value. */
+static int32_t
+get_intro_point_num_extra(void)
+{
+ /* The [0, 128] range bounds the number of extra introduction point allowed.
+ * Above 128 intro points, it's getting a bit crazy. */
+ return networkstatus_get_param(NULL, "hs_intro_num_extra",
+ NUM_INTRO_POINTS_EXTRA, 0, 128);
+}
+
+/* Helper: Function that needs to return 1 for the HT for each loop which
+ * frees every service in an hash map. */
+static int
+ht_free_service_(struct hs_service_t *service, void *data)
+{
+ (void) data;
+ hs_service_free(service);
+ /* This function MUST return 1 so the given object is then removed from the
+ * service map leading to this free of the object being safe. */
+ return 1;
+}
+
+/* Free every service that can be found in the global map. Once done, clear
+ * and free the global map. */
+static void
+service_free_all(void)
+{
+ if (hs_service_map) {
+ /* The free helper function returns 1 so this is safe. */
+ hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL);
+ HT_CLEAR(hs_service_ht, hs_service_map);
+ tor_free(hs_service_map);
+ hs_service_map = NULL;
+ }
+
+ if (hs_service_staging_list) {
+ /* Cleanup staging list. */
+ SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s,
+ hs_service_free(s));
+ smartlist_free(hs_service_staging_list);
+ hs_service_staging_list = NULL;
+ }
+}
+
+/* Free a given service intro point object. */
+STATIC void
+service_intro_point_free_(hs_service_intro_point_t *ip)
+{
+ if (!ip) {
+ return;
+ }
+ memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp));
+ memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp));
+ crypto_pk_free(ip->legacy_key);
+ replaycache_free(ip->replay_cache);
+ hs_intropoint_clear(&ip->base);
+ tor_free(ip);
+}
+
+/* Helper: free an hs_service_intro_point_t object. This function is used by
+ * digest256map_free() which requires a void * pointer. */
+static void
+service_intro_point_free_void(void *obj)
+{
+ service_intro_point_free_(obj);
+}
+
+/* Return a newly allocated service intro point and fully initialized from the
+ * given extend_info_t ei if non NULL. If is_legacy is true, we also generate
+ * the legacy key. On error, NULL is returned.
+ *
+ * If ei is NULL, returns a hs_service_intro_point_t with an empty link
+ * specifier list and no onion key. (This is used for testing.)
+ *
+ * ei must be an extend_info_t containing an IPv4 address. (We will add supoort
+ * for IPv6 in a later release.) When calling extend_info_from_node(), pass
+ * 0 in for_direct_connection to make sure ei always has an IPv4 address. */
+STATIC hs_service_intro_point_t *
+service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
+{
+ hs_desc_link_specifier_t *ls;
+ hs_service_intro_point_t *ip;
+
+ ip = tor_malloc_zero(sizeof(*ip));
+ /* We'll create the key material. No need for extra strong, those are short
+ * term keys. */
+ ed25519_keypair_generate(&ip->auth_key_kp, 0);
+
+ { /* Set introduce2 max cells limit */
+ int32_t min_introduce2_cells = get_intro_point_min_introduce2();
+ int32_t max_introduce2_cells = get_intro_point_max_introduce2();
+ if (BUG(max_introduce2_cells < min_introduce2_cells)) {
+ goto err;
+ }
+ ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells,
+ max_introduce2_cells);
+ }
+ { /* Set intro point lifetime */
+ int32_t intro_point_min_lifetime = get_intro_point_min_lifetime();
+ int32_t intro_point_max_lifetime = get_intro_point_max_lifetime();
+ if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) {
+ goto err;
+ }
+ ip->time_to_expire = time(NULL) +
+ crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime);
+ }
+
+ ip->replay_cache = replaycache_new(0, 0);
+
+ /* Initialize the base object. We don't need the certificate object. */
+ ip->base.link_specifiers = smartlist_new();
+
+ /* Generate the encryption key for this intro point. */
+ curve25519_keypair_generate(&ip->enc_key_kp, 0);
+ /* Figure out if this chosen node supports v3 or is legacy only. */
+ if (is_legacy) {
+ ip->base.is_only_legacy = 1;
+ /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */
+ ip->legacy_key = crypto_pk_new();
+ if (crypto_pk_generate_key(ip->legacy_key) < 0) {
+ goto err;
+ }
+ if (crypto_pk_get_digest(ip->legacy_key,
+ (char *) ip->legacy_key_digest) < 0) {
+ goto err;
+ }
+ }
+
+ if (ei == NULL) {
+ goto done;
+ }
+
+ /* We'll try to add all link specifiers. Legacy is mandatory.
+ * IPv4 or IPv6 is required, and we always send IPv4. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV4);
+ /* It is impossible to have an extend info object without a v4. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID);
+ /* It is impossible to have an extend info object without an identity
+ * digest. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ /* ed25519 identity key is optional for intro points */
+ ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* IPv6 is not supported in this release. */
+
+ /* Finally, copy onion key from the extend_info_t object. */
+ memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key));
+
+ done:
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ return NULL;
+}
+
+/* Add the given intro point object to the given intro point map. The intro
+ * point MUST have its RSA encryption key set if this is a legacy type or the
+ * authentication key set otherwise. */
+STATIC void
+service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip)
+{
+ hs_service_intro_point_t *old_ip_entry;
+
+ tor_assert(map);
+ tor_assert(ip);
+
+ old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip);
+ /* Make sure we didn't just try to double-add an intro point */
+ tor_assert_nonfatal(!old_ip_entry);
+}
+
+/* For a given service, remove the intro point from that service's descriptors
+ * (check both current and next descriptor) */
+STATIC void
+service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ tor_assert(service);
+ tor_assert(ip);
+
+ /* Trying all descriptors. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We'll try to remove the descriptor on both descriptors which is not
+ * very expensive to do instead of doing loopup + remove. */
+ digest256map_remove(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For a given service and authentication key, return the intro point or NULL
+ * if not found. This will check both descriptors in the service. */
+STATIC hs_service_intro_point_t *
+service_intro_point_find(const hs_service_t *service,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(service);
+ tor_assert(auth_key);
+
+ /* Trying all descriptors to find the right intro point.
+ *
+ * Even if we use the same node as intro point in both descriptors, the node
+ * will have a different intro auth key for each descriptor since we generate
+ * a new one everytime we pick an intro point.
+ *
+ * After #22893 gets implemented, intro points will be moved to be
+ * per-service instead of per-descriptor so this function will need to
+ * change.
+ */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if ((ip = digest256map_get(desc->intro_points.map,
+ auth_key->pubkey)) != NULL) {
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return ip;
+}
+
+/* For a given service and intro point, return the descriptor for which the
+ * intro point is assigned to. NULL is returned if not found. */
+STATIC hs_service_descriptor_t *
+service_desc_find_by_intro(const hs_service_t *service,
+ const hs_service_intro_point_t *ip)
+{
+ hs_service_descriptor_t *descp = NULL;
+
+ tor_assert(service);
+ tor_assert(ip);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ if (digest256map_get(desc->intro_points.map,
+ ip->auth_key_kp.pubkey.pubkey)) {
+ descp = desc;
+ break;
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+
+ return descp;
+}
+
+/* From a circuit identifier, get all the possible objects associated with the
+ * ident. If not NULL, service, ip or desc are set if the object can be found.
+ * They are untouched if they can't be found.
+ *
+ * This is an helper function because we do those lookups often so it's more
+ * convenient to simply call this functions to get all the things at once. */
+STATIC void
+get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service, hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc)
+{
+ hs_service_t *s;
+
+ tor_assert(ident);
+
+ /* Get service object from the circuit identifier. */
+ s = find_service(hs_service_map, &ident->identity_pk);
+ if (s && service) {
+ *service = s;
+ }
+
+ /* From the service object, get the intro point object of that circuit. The
+ * following will query both descriptors intro points list. */
+ if (s && ip) {
+ *ip = service_intro_point_find(s, &ident->intro_auth_pk);
+ }
+
+ /* Get the descriptor for this introduction point and service. */
+ if (s && ip && *ip && desc) {
+ *desc = service_desc_find_by_intro(s, *ip);
+ }
+}
+
+/* From a given intro point, return the first link specifier of type
+ * encountered in the link specifier list. Return NULL if it can't be found.
+ *
+ * The caller does NOT have ownership of the object, the intro point does. */
+static hs_desc_link_specifier_t *
+get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type)
+{
+ hs_desc_link_specifier_t *lnk_spec = NULL;
+
+ tor_assert(ip);
+
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ hs_desc_link_specifier_t *, ls) {
+ if (ls->type == type) {
+ lnk_spec = ls;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ end:
+ return lnk_spec;
+}
+
+/* Given a service intro point, return the node_t associated to it. This can
+ * return NULL if the given intro point has no legacy ID or if the node can't
+ * be found in the consensus. */
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip)
+{
+ const hs_desc_link_specifier_t *ls;
+
+ tor_assert(ip);
+
+ ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ if (BUG(!ls)) {
+ return NULL;
+ }
+ /* XXX In the future, we want to only use the ed25519 ID (#22173). */
+ return node_get_by_id((const char *) ls->u.legacy_id);
+}
+
+/* Given a service intro point, return the extend_info_t for it. This can
+ * return NULL if the node can't be found for the intro point or the extend
+ * info can't be created for the found node. If direct_conn is set, the extend
+ * info is validated on if we can connect directly. */
+static extend_info_t *
+get_extend_info_from_intro_point(const hs_service_intro_point_t *ip,
+ unsigned int direct_conn)
+{
+ extend_info_t *info = NULL;
+ const node_t *node;
+
+ tor_assert(ip);
+
+ node = get_node_from_intro_point(ip);
+ if (node == NULL) {
+ /* This can happen if the relay serving as intro point has been removed
+ * from the consensus. In that case, the intro point will be removed from
+ * the descriptor during the scheduled events. */
+ goto end;
+ }
+
+ /* In the case of a direct connection (single onion service), it is possible
+ * our firewall policy won't allow it so this can return a NULL value. */
+ info = extend_info_from_node(node, direct_conn);
+
+ end:
+ return info;
+}
+
+/* Return the number of introduction points that are established for the
+ * given descriptor. */
+static unsigned int
+count_desc_circuit_established(const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+
+ tor_assert(desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ count += ip->circuit_established;
+ } DIGEST256MAP_FOREACH_END;
+
+ return count;
+}
+
+/* For a given service and descriptor of that service, close all active
+ * directory connections. */
+static void
+close_directory_connections(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ unsigned int count = 0;
+ smartlist_t *dir_conns;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Close pending HS desc upload connections for the blinded key of 'desc'. */
+ dir_conns = connection_list_by_type_purpose(CONN_TYPE_DIR,
+ DIR_PURPOSE_UPLOAD_HSDESC);
+ SMARTLIST_FOREACH_BEGIN(dir_conns, connection_t *, conn) {
+ dir_connection_t *dir_conn = TO_DIR_CONN(conn);
+ if (ed25519_pubkey_eq(&dir_conn->hs_ident->identity_pk,
+ &service->keys.identity_pk) &&
+ ed25519_pubkey_eq(&dir_conn->hs_ident->blinded_pk,
+ &desc->blinded_kp.pubkey)) {
+ connection_mark_for_close(conn);
+ count++;
+ continue;
+ }
+ } SMARTLIST_FOREACH_END(conn);
+
+ log_info(LD_REND, "Closed %u active service directory connections for "
+ "descriptor %s of service %s",
+ count, safe_str_client(ed25519_fmt(&desc->blinded_kp.pubkey)),
+ safe_str_client(service->onion_address));
+ /* We don't have ownership of the objects in this list. */
+ smartlist_free(dir_conns);
+}
+
+/* Close all rendezvous circuits for the given service. */
+static void
+close_service_rp_circuits(hs_service_t *service)
+{
+ origin_circuit_t *ocirc = NULL;
+
+ tor_assert(service);
+
+ /* The reason we go over all circuit instead of using the circuitmap API is
+ * because most hidden service circuits are rendezvous circuits so there is
+ * no real improvement at getting all rendezvous circuits from the
+ * circuitmap and then going over them all to find the right ones.
+ * Furthermore, another option would have been to keep a list of RP cookies
+ * for a service but it creates an engineering complexity since we don't
+ * have a "RP circuit closed" event to clean it up properly so we avoid a
+ * memory DoS possibility. */
+
+ while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) {
+ /* Only close circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, it is removed from the
+ * hs circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ }
+}
+
+/* Close the circuit(s) for the given map of introduction points. */
+static void
+close_intro_circuits(hs_service_intropoints_t *intro_points)
+{
+ tor_assert(intro_points);
+
+ DIGEST256MAP_FOREACH(intro_points->map, key,
+ const hs_service_intro_point_t *, ip) {
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, the circuit is removed
+ * from the HS circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Close all introduction circuits for the given service. */
+static void
+close_service_intro_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ close_intro_circuits(&desc->intro_points);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Close any circuits related to the given service. */
+static void
+close_service_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* Only support for version >= 3. */
+ if (BUG(service->config.version < HS_VERSION_THREE)) {
+ return;
+ }
+ /* Close intro points. */
+ close_service_intro_circuits(service);
+ /* Close rendezvous points. */
+ close_service_rp_circuits(service);
+}
+
+/* Move every ephemeral services from the src service map to the dst service
+ * map. It is possible that a service can't be register to the dst map which
+ * won't stop the process of moving them all but will trigger a log warn. */
+static void
+move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst)
+{
+ hs_service_t **iter, **next;
+
+ tor_assert(src);
+ tor_assert(dst);
+
+ /* Iterate over the map to find ephemeral service and move them to the other
+ * map. We loop using this method to have a safe removal process. */
+ for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) {
+ hs_service_t *s = *iter;
+ if (!s->config.is_ephemeral) {
+ /* Yeah, we are in a very manual loop :). */
+ next = HT_NEXT(hs_service_ht, src, iter);
+ continue;
+ }
+ /* Remove service from map and then register to it to the other map.
+ * Reminder that "*iter" and "s" are the same thing. */
+ next = HT_NEXT_RMV(hs_service_ht, src, iter);
+ if (register_service(dst, s) < 0) {
+ log_warn(LD_BUG, "Ephemeral service key is already being used. "
+ "Skipping.");
+ }
+ }
+}
+
+/* Return a const string of the directory path escaped. If this is an
+ * ephemeral service, it returns "[EPHEMERAL]". This can only be called from
+ * the main thread because escaped() uses a static variable. */
+static const char *
+service_escaped_dir(const hs_service_t *s)
+{
+ return (s->config.is_ephemeral) ? "[EPHEMERAL]" :
+ escaped(s->config.directory_path);
+}
+
+/** Move the hidden service state from <b>src</b> to <b>dst</b>. We do this
+ * when we receive a SIGHUP: <b>dst</b> is the post-HUP service */
+static void
+move_hs_state(hs_service_t *src_service, hs_service_t *dst_service)
+{
+ tor_assert(src_service);
+ tor_assert(dst_service);
+
+ hs_service_state_t *src = &src_service->state;
+ hs_service_state_t *dst = &dst_service->state;
+
+ /* Let's do a shallow copy */
+ dst->intro_circ_retry_started_time = src->intro_circ_retry_started_time;
+ dst->num_intro_circ_launched = src->num_intro_circ_launched;
+ /* Freeing a NULL replaycache triggers an info LD_BUG. */
+ if (dst->replay_cache_rend_cookie != NULL) {
+ replaycache_free(dst->replay_cache_rend_cookie);
+ }
+ dst->replay_cache_rend_cookie = src->replay_cache_rend_cookie;
+
+ src->replay_cache_rend_cookie = NULL; /* steal pointer reference */
+}
+
+/* Register services that are in the staging list. Once this function returns,
+ * the global service map will be set with the right content and all non
+ * surviving services will be cleaned up. */
+static void
+register_all_services(void)
+{
+ struct hs_service_ht *new_service_map;
+
+ tor_assert(hs_service_staging_list);
+
+ /* Allocate a new map that will replace the current one. */
+ new_service_map = tor_malloc_zero(sizeof(*new_service_map));
+ HT_INIT(hs_service_ht, new_service_map);
+
+ /* First step is to transfer all ephemeral services from the current global
+ * map to the new one we are constructing. We do not prune ephemeral
+ * services as the only way to kill them is by deleting it from the control
+ * port or stopping the tor daemon. */
+ move_ephemeral_services(hs_service_map, new_service_map);
+
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) {
+ hs_service_t *s;
+
+ /* Check if that service is already in our global map and if so, we'll
+ * transfer the intro points to it. */
+ s = find_service(hs_service_map, &snew->keys.identity_pk);
+ if (s) {
+ /* Pass ownership of the descriptors from s (the current service) to
+ * snew (the newly configured one). */
+ move_descriptors(s, snew);
+ move_hs_state(s, snew);
+ /* Remove the service from the global map because after this, we need to
+ * go over the remaining service in that map that aren't surviving the
+ * reload to close their circuits. */
+ remove_service(hs_service_map, s);
+ hs_service_free(s);
+ }
+ /* Great, this service is now ready to be added to our new map. */
+ if (BUG(register_service(new_service_map, snew) < 0)) {
+ /* This should never happen because prior to registration, we validate
+ * every service against the entire set. Not being able to register a
+ * service means we failed to validate correctly. In that case, don't
+ * break tor and ignore the service but tell user. */
+ log_warn(LD_BUG, "Unable to register service with directory %s",
+ service_escaped_dir(snew));
+ SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew);
+ hs_service_free(snew);
+ }
+ } SMARTLIST_FOREACH_END(snew);
+
+ /* Close any circuits associated with the non surviving services. Every
+ * service in the current global map are roaming. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ close_service_circuits(service);
+ } FOR_EACH_SERVICE_END;
+
+ /* Time to make the switch. We'll clear the staging list because its content
+ * has now changed ownership to the map. */
+ smartlist_clear(hs_service_staging_list);
+ service_free_all();
+ hs_service_map = new_service_map;
+ /* We've just register services into the new map and now we've replaced the
+ * global map with it so we have to notify that the change happened. When
+ * registering a service, the notify is only triggered if the destination
+ * map is the global map for which in here it was not. */
+ hs_service_map_has_changed();
+}
+
+/* Write the onion address of a given service to the given filename fname_ in
+ * the service directory. Return 0 on success else -1 on error. */
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_)
+{
+ int ret = -1;
+ char *fname = NULL;
+ char *addr_buf = NULL;
+
+ tor_assert(service);
+ tor_assert(fname_);
+
+ /* Construct the full address with the onion tld and write the hostname file
+ * to disk. */
+ tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld);
+ /* Notice here that we use the given "fname_". */
+ fname = hs_path_from_filename(service->config.directory_path, fname_);
+ if (write_str_to_file(fname, addr_buf, 0) < 0) {
+ log_warn(LD_REND, "Could not write onion address to hostname file %s",
+ escaped(fname));
+ goto end;
+ }
+
+#ifndef _WIN32
+ if (service->config.dir_group_readable) {
+ /* Mode to 0640. */
+ if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) {
+ log_warn(LD_FS, "Unable to make onion service hostname file %s "
+ "group-readable.", escaped(fname));
+ }
+ }
+#endif /* !defined(_WIN32) */
+
+ /* Success. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ tor_free(addr_buf);
+ return ret;
+}
+
+/* Load and/or generate private keys for the given service. On success, the
+ * hostname file will be written to disk along with the master private key iff
+ * the service is not configured for offline keys. Return 0 on success else -1
+ * on failure. */
+static int
+load_service_keys(hs_service_t *service)
+{
+ int ret = -1;
+ char *fname = NULL;
+ ed25519_keypair_t *kp;
+ const hs_service_config_t *config;
+
+ tor_assert(service);
+
+ config = &service->config;
+
+ /* Create and fix permission on service directory. We are about to write
+ * files to that directory so make sure it exists and has the right
+ * permissions. We do this here because at this stage we know that Tor is
+ * actually running and the service we have has been validated. */
+ if (BUG(hs_check_service_private_dir(get_options()->User,
+ config->directory_path,
+ config->dir_group_readable, 1) < 0)) {
+ goto end;
+ }
+
+ /* Try to load the keys from file or generate it if not found. */
+ fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix);
+ /* Don't ask for key creation, we want to know if we were able to load it or
+ * we had to generate it. Better logging! */
+ kp = ed_key_init_from_file(fname, INIT_ED_KEY_SPLIT, LOG_INFO, NULL, 0, 0,
+ 0, NULL);
+ if (!kp) {
+ log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname);
+ /* We'll now try to generate the keys and for it we want the strongest
+ * randomness for it. The keypair will be written in different files. */
+ uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG |
+ INIT_ED_KEY_SPLIT;
+ kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0,
+ NULL);
+ if (!kp) {
+ log_warn(LD_REND, "Unable to generate keys and save in %s.", fname);
+ goto end;
+ }
+ }
+
+ /* Copy loaded or generated keys to service object. */
+ ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey);
+ memcpy(&service->keys.identity_sk, &kp->seckey,
+ sizeof(service->keys.identity_sk));
+ /* This does a proper memory wipe. */
+ ed25519_keypair_free(kp);
+
+ /* Build onion address from the newly loaded keys. */
+ tor_assert(service->config.version <= UINT8_MAX);
+ hs_build_address(&service->keys.identity_pk,
+ (uint8_t) service->config.version,
+ service->onion_address);
+
+ /* Write onion address to hostname file. */
+ if (write_address_to_file(service, fname_hostname) < 0) {
+ goto end;
+ }
+
+ /* Succes. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ return ret;
+}
+
+/* Free a given service descriptor object and all key material is wiped. */
+STATIC void
+service_descriptor_free_(hs_service_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+ hs_descriptor_free(desc->desc);
+ memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp));
+ memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp));
+ /* Cleanup all intro points. */
+ digest256map_free(desc->intro_points.map, service_intro_point_free_void);
+ digestmap_free(desc->intro_points.failed_id, tor_free_);
+ if (desc->previous_hsdirs) {
+ SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s));
+ smartlist_free(desc->previous_hsdirs);
+ }
+ tor_free(desc);
+}
+
+/* Return a newly allocated service descriptor object. */
+STATIC hs_service_descriptor_t *
+service_descriptor_new(void)
+{
+ hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc));
+ sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+ /* Initialize the intro points map. */
+ sdesc->intro_points.map = digest256map_new();
+ sdesc->intro_points.failed_id = digestmap_new();
+ sdesc->previous_hsdirs = smartlist_new();
+ return sdesc;
+}
+
+/* Move descriptor(s) from the src service to the dst service. We do this
+ * during SIGHUP when we re-create our hidden services. */
+static void
+move_descriptors(hs_service_t *src, hs_service_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ if (src->desc_current) {
+ /* Nothing should be there, but clean it up just in case */
+ if (BUG(dst->desc_current)) {
+ service_descriptor_free(dst->desc_current);
+ }
+ dst->desc_current = src->desc_current;
+ src->desc_current = NULL;
+ }
+
+ if (src->desc_next) {
+ /* Nothing should be there, but clean it up just in case */
+ if (BUG(dst->desc_next)) {
+ service_descriptor_free(dst->desc_next);
+ }
+ dst->desc_next = src->desc_next;
+ src->desc_next = NULL;
+ }
+}
+
+/* From the given service, remove all expired failing intro points for each
+ * descriptor. */
+static void
+remove_expired_failing_intro(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the failing intro points list. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) {
+ time_t failure_time = *t;
+ if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) {
+ MAP_DEL_CURRENT(key);
+ tor_free(t);
+ }
+ } DIGESTMAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* For the given descriptor desc, put all node_t object found from its failing
+ * intro point list and put them in the given node_list. */
+static void
+setup_intro_point_exclude_list(const hs_service_descriptor_t *desc,
+ smartlist_t *node_list)
+{
+ tor_assert(desc);
+ tor_assert(node_list);
+
+ DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) {
+ (void) t; /* Make gcc happy. */
+ const node_t *node = node_get_by_id(key);
+ if (node) {
+ smartlist_add(node_list, (void *) node);
+ }
+ } DIGESTMAP_FOREACH_END;
+}
+
+/* For the given failing intro point ip, we add its time of failure to the
+ * failed map and index it by identity digest (legacy ID) in the descriptor
+ * desc failed id map. */
+static void
+remember_failing_intro_point(const hs_service_intro_point_t *ip,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ time_t *time_of_failure, *prev_ptr;
+ const hs_desc_link_specifier_t *legacy_ls;
+
+ tor_assert(ip);
+ tor_assert(desc);
+
+ time_of_failure = tor_malloc_zero(sizeof(time_t));
+ *time_of_failure = now;
+ legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ tor_assert(legacy_ls);
+ prev_ptr = digestmap_set(desc->intro_points.failed_id,
+ (const char *) legacy_ls->u.legacy_id,
+ time_of_failure);
+ tor_free(prev_ptr);
+}
+
+/* Copy the descriptor link specifier object from src to dst. */
+static void
+link_specifier_copy(hs_desc_link_specifier_t *dst,
+ const hs_desc_link_specifier_t *src)
+{
+ tor_assert(dst);
+ tor_assert(src);
+ memcpy(dst, src, sizeof(hs_desc_link_specifier_t));
+}
+
+/* Using a given descriptor signing keypair signing_kp, a service intro point
+ * object ip and the time now, setup the content of an already allocated
+ * descriptor intro desc_ip.
+ *
+ * Return 0 on success else a negative value. */
+static int
+setup_desc_intro_point(const ed25519_keypair_t *signing_kp,
+ const hs_service_intro_point_t *ip,
+ time_t now, hs_desc_intro_point_t *desc_ip)
+{
+ int ret = -1;
+ time_t nearest_hour = now - (now % 3600);
+
+ tor_assert(signing_kp);
+ tor_assert(ip);
+ tor_assert(desc_ip);
+
+ /* Copy the onion key. */
+ memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key));
+
+ /* Key and certificate material. */
+ desc_ip->auth_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_AUTH_HS_IP_KEY,
+ &ip->auth_key_kp.pubkey,
+ nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->auth_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create intro point auth-key certificate");
+ goto done;
+ }
+
+ /* Copy link specifier(s). */
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ const hs_desc_link_specifier_t *, ls) {
+ hs_desc_link_specifier_t *copy = tor_malloc_zero(sizeof(*copy));
+ link_specifier_copy(copy, ls);
+ smartlist_add(desc_ip->link_specifiers, copy);
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* For a legacy intro point, we'll use an RSA/ed cross certificate. */
+ if (ip->base.is_only_legacy) {
+ desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key);
+ /* Create cross certification cert. */
+ ssize_t cert_len = tor_make_rsa_ed25519_crosscert(
+ &signing_kp->pubkey,
+ desc_ip->legacy.key,
+ nearest_hour + HS_DESC_CERT_LIFETIME,
+ &desc_ip->legacy.cert.encoded);
+ if (cert_len < 0) {
+ log_warn(LD_REND, "Unable to create enc key legacy cross cert.");
+ goto done;
+ }
+ desc_ip->legacy.cert.len = cert_len;
+ }
+
+ /* Encryption key and its cross certificate. */
+ {
+ ed25519_public_key_t ed25519_pubkey;
+
+ /* Use the public curve25519 key. */
+ memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey,
+ sizeof(desc_ip->enc_key));
+ /* The following can't fail. */
+ ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey,
+ &ip->enc_key_kp.pubkey,
+ 0);
+ desc_ip->enc_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_CROSS_HS_IP_KEYS,
+ &ed25519_pubkey, nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->enc_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create enc key curve25519 cross cert.");
+ goto done;
+ }
+ }
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* Using the given descriptor from the given service, build the descriptor
+ * intro point list so we can then encode the descriptor for publication. This
+ * function does not pick intro points, they have to be in the descriptor
+ * current map. Cryptographic material (keys) must be initialized in the
+ * descriptor for this function to make sense. */
+static void
+build_desc_intro_points(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Ease our life. */
+ encrypted = &desc->desc->encrypted_data;
+ /* Cleanup intro points, we are about to set them from scratch. */
+ hs_descriptor_clear_intro_points(desc->desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new();
+ if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) {
+ hs_desc_intro_point_free(desc_ip);
+ continue;
+ }
+ /* We have a valid descriptor intro point. Add it to the list. */
+ smartlist_add(encrypted->intro_points, desc_ip);
+ } DIGEST256MAP_FOREACH_END;
+}
+
+/* Populate the descriptor encrypted section from the given service object.
+ * This will generate a valid list of introduction points that can be used
+ * after for circuit creation. Return 0 on success else -1 on error. */
+static int
+build_service_desc_encrypted(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ encrypted = &desc->desc->encrypted_data;
+
+ encrypted->create2_ntor = 1;
+ encrypted->single_onion_service = service->config.is_single_onion;
+
+ /* Setup introduction points from what we have in the service. */
+ if (encrypted->intro_points == NULL) {
+ encrypted->intro_points = smartlist_new();
+ }
+ /* We do NOT build introduction point yet, we only do that once the circuit
+ * have been opened. Until we have the right number of introduction points,
+ * we do not encode anything in the descriptor. */
+
+ /* XXX: Support client authorization (#20700). */
+ encrypted->intro_auth_types = NULL;
+ return 0;
+}
+
+/* Populate the descriptor plaintext section from the given service object.
+ * The caller must make sure that the keys in the descriptors are valid that
+ * is are non-zero. Return 0 on success else -1 on error. */
+static int
+build_service_desc_plaintext(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ int ret = -1;
+ hs_desc_plaintext_data_t *plaintext;
+
+ tor_assert(service);
+ tor_assert(desc);
+ /* XXX: Use a "assert_desc_ok()" ? */
+ tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp,
+ sizeof(desc->blinded_kp)));
+ tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp,
+ sizeof(desc->signing_kp)));
+
+ /* Set the subcredential. */
+ hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
+ desc->desc->subcredential);
+
+ plaintext = &desc->desc->plaintext_data;
+
+ plaintext->version = service->config.version;
+ plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME;
+ plaintext->signing_key_cert =
+ tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC,
+ &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (plaintext->signing_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create descriptor signing certificate for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ /* Copy public key material to go in the descriptor. */
+ ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey);
+ ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey);
+ /* Success. */
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+/* For the given service and descriptor object, create the key material which
+ * is the blinded keypair and the descriptor signing keypair. Return 0 on
+ * success else -1 on error where the generated keys MUST be ignored. */
+static int
+build_service_desc_keys(const hs_service_t *service,
+ hs_service_descriptor_t *desc,
+ uint64_t time_period_num)
+{
+ int ret = 0;
+ ed25519_keypair_t kp;
+
+ tor_assert(desc);
+ tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk,
+ ED25519_PUBKEY_LEN));
+
+ /* XXX: Support offline key feature (#18098). */
+
+ /* Copy the identity keys to the keypair so we can use it to create the
+ * blinded key. */
+ memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey));
+ memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey));
+ /* Build blinded keypair for this time period. */
+ hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp);
+ /* Let's not keep too much traces of our keys in memory. */
+ memwipe(&kp, 0, sizeof(kp));
+
+ /* No need for extra strong, this is a temporary key only for this
+ * descriptor. Nothing long term. */
+ if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) {
+ log_warn(LD_REND, "Can't generate descriptor signing keypair for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* Given a service and the current time, build a descriptor for the service.
+ * This function does not pick introduction point, this needs to be done by
+ * the update function. On success, desc_out will point to the newly allocated
+ * descriptor object.
+ *
+ * This can error if we are unable to create keys or certificate. */
+static void
+build_service_descriptor(hs_service_t *service, time_t now,
+ uint64_t time_period_num,
+ hs_service_descriptor_t **desc_out)
+{
+ char *encoded_desc;
+ hs_service_descriptor_t *desc;
+
+ tor_assert(service);
+ tor_assert(desc_out);
+
+ desc = service_descriptor_new();
+ desc->time_period_num = time_period_num;
+
+ /* Create the needed keys so we can setup the descriptor content. */
+ if (build_service_desc_keys(service, desc, time_period_num) < 0) {
+ goto err;
+ }
+ /* Setup plaintext descriptor content. */
+ if (build_service_desc_plaintext(service, desc, now) < 0) {
+ goto err;
+ }
+ /* Setup encrypted descriptor content. */
+ if (build_service_desc_encrypted(service, desc) < 0) {
+ goto err;
+ }
+
+ /* Set the revision counter for this descriptor */
+ set_descriptor_revision_counter(desc->desc);
+
+ /* Let's make sure that we've created a descriptor that can actually be
+ * encoded properly. This function also checks if the encoded output is
+ * decodable after. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto err;
+ }
+ tor_free(encoded_desc);
+
+ /* Assign newly built descriptor to the next slot. */
+ *desc_out = desc;
+ /* Fire a CREATED control port event. */
+ hs_control_desc_event_created(service->onion_address,
+ &desc->blinded_kp.pubkey);
+ return;
+
+ err:
+ service_descriptor_free(desc);
+}
+
+/* Build both descriptors for the given service that has just booted up.
+ * Because it's a special case, it deserves its special function ;). */
+static void
+build_descriptors_for_new_service(hs_service_t *service, time_t now)
+{
+ uint64_t current_desc_tp, next_desc_tp;
+
+ tor_assert(service);
+ /* These are the conditions for a new service. */
+ tor_assert(!service->desc_current);
+ tor_assert(!service->desc_next);
+
+ /*
+ * +------------------------------------------------------------------+
+ * | |
+ * | 00:00 12:00 00:00 12:00 00:00 12:00 |
+ * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 |
+ * | |
+ * | $==========|-----------$===========|-----------$===========| |
+ * | ^ ^ |
+ * | A B |
+ * +------------------------------------------------------------------+
+ *
+ * Case A: The service boots up before a new time period, the current time
+ * period is thus TP#1 and the next is TP#2 which for both we have access to
+ * their SRVs.
+ *
+ * Case B: The service boots up inside TP#2, we can't use the TP#3 for the
+ * next descriptor because we don't have the SRV#3 so the current should be
+ * TP#1 and next TP#2.
+ */
+
+ if (hs_in_period_between_tp_and_srv(NULL, now)) {
+ /* Case B from the above, inside of the new time period. */
+ current_desc_tp = hs_get_previous_time_period_num(0); /* TP#1 */
+ next_desc_tp = hs_get_time_period_num(0); /* TP#2 */
+ } else {
+ /* Case A from the above, outside of the new time period. */
+ current_desc_tp = hs_get_time_period_num(0); /* TP#1 */
+ next_desc_tp = hs_get_next_time_period_num(0); /* TP#2 */
+ }
+
+ /* Build descriptors. */
+ build_service_descriptor(service, now, current_desc_tp,
+ &service->desc_current);
+ build_service_descriptor(service, now, next_desc_tp,
+ &service->desc_next);
+ log_info(LD_REND, "Hidden service %s has just started. Both descriptors "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+}
+
+/* Build descriptors for each service if needed. There are conditions to build
+ * a descriptor which are details in the function. */
+STATIC void
+build_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+
+ /* A service booting up will have both descriptors to NULL. No other cases
+ * makes both descriptor non existent. */
+ if (service->desc_current == NULL && service->desc_next == NULL) {
+ build_descriptors_for_new_service(service, now);
+ continue;
+ }
+
+ /* Reaching this point means we are pass bootup so at runtime. We should
+ * *never* have an empty current descriptor. If the next descriptor is
+ * empty, we'll try to build it for the next time period. This only
+ * happens when we rotate meaning that we are guaranteed to have a new SRV
+ * at that point for the next time period. */
+ if (BUG(service->desc_current == NULL)) {
+ continue;
+ }
+
+ if (service->desc_next == NULL) {
+ build_service_descriptor(service, now, hs_get_next_time_period_num(0),
+ &service->desc_next);
+ log_info(LD_REND, "Hidden service %s next descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Randomly pick a node to become an introduction point but not present in the
+ * given exclude_nodes list. The chosen node is put in the exclude list
+ * regardless of success or not because in case of failure, the node is simply
+ * unsusable from that point on.
+ *
+ * If direct_conn is set, try to pick a node that our local firewall/policy
+ * allows us to connect to directly. If we can't find any, return NULL.
+ * This function supports selecting dual-stack nodes for direct single onion
+ * service IPv6 connections. But it does not send IPv6 addresses in link
+ * specifiers. (Current clients don't use IPv6 addresses to extend, and
+ * direct client connections to intro points are not supported.)
+ *
+ * Return a newly allocated service intro point ready to be used for encoding.
+ * Return NULL on error. */
+static hs_service_intro_point_t *
+pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
+{
+ const node_t *node;
+ extend_info_t *info = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ /* Normal 3-hop introduction point flags. */
+ router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC;
+ /* Single onion flags. */
+ router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN;
+
+ node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes,
+ direct_conn ? direct_flags : flags);
+ /* Unable to find a node. When looking for a node for a direct connection,
+ * we could try a 3-hop path instead. We'll add support for this in a later
+ * release. */
+ if (!node) {
+ goto err;
+ }
+
+ /* We have a suitable node, add it to the exclude list. We do this *before*
+ * we can validate the extend information because even in case of failure,
+ * we don't want to use that node anymore. */
+ smartlist_add(exclude_nodes, (void *) node);
+
+ /* We do this to ease our life but also this call makes appropriate checks
+ * of the node object such as validating ntor support for instance.
+ *
+ * We must provide an extend_info for clients to connect over a 3-hop path,
+ * so we don't pass direct_conn here. */
+ info = extend_info_from_node(node, 0);
+ if (BUG(info == NULL)) {
+ goto err;
+ }
+
+ /* Let's do a basic sanity check here so that we don't end up advertising the
+ * ed25519 identity key of relays that don't actually support the link
+ * protocol */
+ if (!node_supports_ed25519_link_authentication(node, 0)) {
+ tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity));
+ } else {
+ /* Make sure we *do* have an ed key if we support the link authentication.
+ * Sending an empty key would result in a failure to extend. */
+ tor_assert_nonfatal(!ed25519_public_key_is_zero(&info->ed_identity));
+ }
+
+ /* Create our objects and populate them with the node information. */
+ ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node));
+ if (ip == NULL) {
+ goto err;
+ }
+
+ log_info(LD_REND, "Picked intro point: %s", extend_info_describe(info));
+ extend_info_free(info);
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ extend_info_free(info);
+ return NULL;
+}
+
+/* For a given descriptor from the given service, pick any needed intro points
+ * and update the current map with those newly picked intro points. Return the
+ * number node that might have been added to the descriptor current map. */
+static unsigned int
+pick_needed_intro_points(hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ int i = 0, num_needed_ip;
+ smartlist_t *exclude_nodes = smartlist_new();
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Compute how many intro points we actually need to open. */
+ num_needed_ip = service->config.num_intro_points -
+ digest256map_size(desc->intro_points.map);
+ if (BUG(num_needed_ip < 0)) {
+ /* Let's not make tor freak out here and just skip this. */
+ goto done;
+ }
+
+ /* We want to end up with config.num_intro_points intro points, but if we
+ * have no intro points at all (chances are they all cycled or we are
+ * starting up), we launch get_intro_point_num_extra() extra circuits and
+ * use the first config.num_intro_points that complete. See proposal #155,
+ * section 4 for the rationale of this which is purely for performance.
+ *
+ * The ones after the first config.num_intro_points will be converted to
+ * 'General' internal circuits and then we'll drop them from the list of
+ * intro points. */
+ if (digest256map_size(desc->intro_points.map) == 0) {
+ num_needed_ip += get_intro_point_num_extra();
+ }
+
+ /* Build an exclude list of nodes of our intro point(s). The expiring intro
+ * points are OK to pick again because this is afterall a concept of round
+ * robin so they are considered valid nodes to pick again. */
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *intro_node = get_node_from_intro_point(ip);
+ if (intro_node) {
+ smartlist_add(exclude_nodes, (void*)intro_node);
+ }
+ } DIGEST256MAP_FOREACH_END;
+ /* Also, add the failing intro points that our descriptor encounteered in
+ * the exclude node list. */
+ setup_intro_point_exclude_list(desc, exclude_nodes);
+
+ for (i = 0; i < num_needed_ip; i++) {
+ hs_service_intro_point_t *ip;
+
+ /* This function will add the picked intro point node to the exclude nodes
+ * list so we don't pick the same one at the next iteration. */
+ ip = pick_intro_point(service->config.is_single_onion, exclude_nodes);
+ if (ip == NULL) {
+ /* If we end up unable to pick an introduction point it is because we
+ * can't find suitable node and calling this again is highly unlikely to
+ * give us a valid node all of the sudden. */
+ log_info(LD_REND, "Unable to find a suitable node to be an "
+ "introduction point for service %s.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ /* Valid intro point object, add it to the descriptor current map. */
+ service_intro_point_add(desc->intro_points.map, ip);
+ }
+ /* We've successfully picked all our needed intro points thus none are
+ * missing which will tell our upload process to expect the number of
+ * circuits to be the number of configured intro points circuits and not the
+ * number of intro points object that we have. */
+ desc->missing_intro_points = 0;
+
+ /* Success. */
+ done:
+ /* We don't have ownership of the node_t object in this list. */
+ smartlist_free(exclude_nodes);
+ return i;
+}
+
+/** Clear previous cached HSDirs in <b>desc</b>. */
+static void
+service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc)
+{
+ if (BUG(!desc->previous_hsdirs)) {
+ return;
+ }
+
+ SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s));
+ smartlist_clear(desc->previous_hsdirs);
+}
+
+/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */
+static void
+service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+ char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+ digest_to_base64(b64_digest, hsdir->identity);
+
+ if (BUG(!desc->previous_hsdirs)) {
+ return;
+ }
+
+ if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) {
+ smartlist_add_strdup(desc->previous_hsdirs, b64_digest);
+ }
+}
+
+/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it
+ * means that this descriptor is dirty. */
+STATIC void
+service_desc_schedule_upload(hs_service_descriptor_t *desc,
+ time_t now,
+ int descriptor_changed)
+
+{
+ desc->next_upload_time = now;
+
+ /* If the descriptor changed, clean up the old HSDirs list. We want to
+ * re-upload no matter what. */
+ if (descriptor_changed) {
+ service_desc_clear_previous_hsdirs(desc);
+ }
+}
+
+/* Update the given descriptor from the given service. The possible update
+ * actions includes:
+ * - Picking missing intro points if needed.
+ * - Incrementing the revision counter if needed.
+ */
+static void
+update_service_descriptor(hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(desc->desc);
+
+ num_intro_points = digest256map_size(desc->intro_points.map);
+
+ /* Pick any missing introduction point(s). */
+ if (num_intro_points < service->config.num_intro_points) {
+ unsigned int num_new_intro_points = pick_needed_intro_points(service,
+ desc);
+ if (num_new_intro_points != 0) {
+ log_info(LD_REND, "Service %s just picked %u intro points and wanted "
+ "%u for %s descriptor. It currently has %d intro "
+ "points. Launching ESTABLISH_INTRO circuit shortly.",
+ safe_str_client(service->onion_address),
+ num_new_intro_points,
+ service->config.num_intro_points - num_intro_points,
+ (desc == service->desc_current) ? "current" : "next",
+ num_intro_points);
+ /* We'll build those introduction point into the descriptor once we have
+ * confirmation that the circuits are opened and ready. However,
+ * indicate that this descriptor should be uploaded from now on. */
+ service_desc_schedule_upload(desc, now, 1);
+ }
+ /* Were we able to pick all the intro points we needed? If not, we'll
+ * flag the descriptor that it's missing intro points because it
+ * couldn't pick enough which will trigger a descriptor upload. */
+ if ((num_new_intro_points + num_intro_points) <
+ service->config.num_intro_points) {
+ desc->missing_intro_points = 1;
+ }
+ }
+}
+
+/* Update descriptors for each service if needed. */
+STATIC void
+update_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We'll try to update each descriptor that is if certain conditions apply
+ * in order for the descriptor to be updated. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ update_service_descriptor(service, desc, now);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Return true iff the given intro point has expired that is it has been used
+ * for too long or we've reached our max seen INTRODUCE2 cell. */
+STATIC int
+intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now)
+{
+ tor_assert(ip);
+
+ if (ip->introduce2_count >= ip->introduce2_max) {
+ goto expired;
+ }
+
+ if (ip->time_to_expire <= now) {
+ goto expired;
+ }
+
+ /* Not expiring. */
+ return 0;
+ expired:
+ return 1;
+}
+
+/* Go over the given set of intro points for each service and remove any
+ * invalid ones. The conditions for removal are:
+ *
+ * - The node doesn't exists anymore (not in consensus)
+ * OR
+ * - The intro point maximum circuit retry count has been reached and no
+ * circuit can be found associated with it.
+ * OR
+ * - The intro point has expired and we should pick a new one.
+ *
+ * If an intro point is removed, the circuit (if any) is immediately close.
+ * If a circuit can't be found, the intro point is kept if it hasn't reached
+ * its maximum circuit retry value and thus should be retried. */
+static void
+cleanup_intro_points(hs_service_t *service, time_t now)
+{
+ /* List of intro points to close. We can't mark the intro circuits for close
+ * in the modify loop because doing so calls
+ * hs_service_intro_circ_has_closed() which does a digest256map_get() on the
+ * intro points map (that we are iterating over). This can't be done in a
+ * single iteration after a MAP_DEL_CURRENT, the object will still be
+ * returned leading to a use-after-free. So, we close the circuits and free
+ * the intro points after the loop if any. */
+ smartlist_t *ips_to_free = smartlist_new();
+
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the intro points. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Go over the current intro points we have, make sure they are still
+ * valid and remove any of them that aren't. */
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *node = get_node_from_intro_point(ip);
+ int has_expired = intro_point_should_expire(ip, now);
+
+ /* We cleanup an intro point if it has expired or if we do not know the
+ * node_t anymore (removed from our latest consensus) or if we've
+ * reached the maximum number of retry with a non existing circuit. */
+ if (has_expired || node == NULL ||
+ ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ log_info(LD_REND, "Intro point %s%s (retried: %u times). "
+ "Removing it.",
+ describe_intro_point(ip),
+ has_expired ? " has expired" :
+ (node == NULL) ? " fell off the consensus" : "",
+ ip->circuit_retries);
+
+ /* We've retried too many times, remember it as a failed intro point
+ * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */
+ if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+ remember_failing_intro_point(ip, desc, approx_time());
+ }
+
+ /* Remove intro point from descriptor map and add it to the list of
+ * ips to free for which we'll also try to close the intro circuit. */
+ MAP_DEL_CURRENT(key);
+ smartlist_add(ips_to_free, ip);
+ }
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+
+ /* Go over the intro points to free and close their circuit if any. */
+ SMARTLIST_FOREACH_BEGIN(ips_to_free, hs_service_intro_point_t *, ip) {
+ /* See if we need to close the intro point circuit as well */
+
+ /* XXX: Legacy code does NOT close circuits like this: it keeps the circuit
+ * open until a new descriptor is uploaded and then closed all expiring
+ * intro point circuit. Here, we close immediately and because we just
+ * discarded the intro point, a new one will be selected, a new descriptor
+ * created and uploaded. There is no difference to an attacker between the
+ * timing of a new consensus and intro point rotation (possibly?). */
+ origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+
+ /* Cleanup the intro point */
+ service_intro_point_free(ip);
+ } SMARTLIST_FOREACH_END(ip);
+
+ smartlist_free(ips_to_free);
+}
+
+/* Set the next rotation time of the descriptors for the given service for the
+ * time now. */
+static void
+set_rotation_time(hs_service_t *service, time_t now)
+{
+ time_t valid_after;
+ const networkstatus_t *ns = networkstatus_get_live_consensus(now);
+ if (ns) {
+ valid_after = ns->valid_after;
+ } else {
+ valid_after = now;
+ }
+
+ tor_assert(service);
+ service->state.next_rotation_time =
+ sr_state_get_start_time_of_current_protocol_run(valid_after) +
+ sr_state_get_protocol_run_duration();
+
+ {
+ char fmt_time[ISO_TIME_LEN + 1];
+ format_local_iso_time(fmt_time, service->state.next_rotation_time);
+ log_info(LD_REND, "Next descriptor rotation time set to %s for %s",
+ fmt_time, safe_str_client(service->onion_address));
+ }
+}
+
+/* Return true iff the service should rotate its descriptor. The time now is
+ * only used to fetch the live consensus and if none can be found, this
+ * returns false. */
+static unsigned int
+should_rotate_descriptors(hs_service_t *service, time_t now)
+{
+ const networkstatus_t *ns;
+
+ tor_assert(service);
+
+ ns = networkstatus_get_live_consensus(now);
+ if (ns == NULL) {
+ goto no_rotation;
+ }
+
+ if (ns->valid_after >= service->state.next_rotation_time) {
+ /* In theory, we should never get here with no descriptors. We can never
+ * have a NULL current descriptor except when tor starts up. The next
+ * descriptor can be NULL after a rotation but we build a new one right
+ * after.
+ *
+ * So, when tor starts, the next rotation time is set to the start of the
+ * next SRV period using the consensus valid after time so it should
+ * always be set to a future time value. This means that we should never
+ * reach this point at bootup that is this check safeguards tor in never
+ * allowing a rotation if the valid after time is smaller than the next
+ * rotation time.
+ *
+ * This is all good in theory but we've had a NULL descriptor issue here
+ * so this is why we BUG() on both with extra logging to try to understand
+ * how this can possibly happens. We'll simply ignore and tor should
+ * recover from this by skipping rotation and building the missing
+ * descriptors just after this. */
+ if (BUG(service->desc_current == NULL || service->desc_next == NULL)) {
+ log_warn(LD_BUG, "Service descriptor is NULL (%p/%p). Next rotation "
+ "time is %ld (now: %ld). Valid after time from "
+ "consensus is %ld",
+ service->desc_current, service->desc_next,
+ (long)service->state.next_rotation_time,
+ (long)now,
+ (long)ns->valid_after);
+ goto no_rotation;
+ }
+ goto rotation;
+ }
+
+ no_rotation:
+ return 0;
+ rotation:
+ return 1;
+}
+
+/* Rotate the service descriptors of the given service. The current descriptor
+ * will be freed, the next one put in as the current and finally the next
+ * descriptor pointer is NULLified. */
+static void
+rotate_service_descriptors(hs_service_t *service, time_t now)
+{
+ if (service->desc_current) {
+ /* Close all IP circuits for the descriptor. */
+ close_intro_circuits(&service->desc_current->intro_points);
+ /* We don't need this one anymore, we won't serve any clients coming with
+ * this service descriptor. */
+ service_descriptor_free(service->desc_current);
+ }
+ /* The next one become the current one and emptying the next will trigger
+ * a descriptor creation for it. */
+ service->desc_current = service->desc_next;
+ service->desc_next = NULL;
+
+ /* We've just rotated, set the next time for the rotation. */
+ set_rotation_time(service, now);
+}
+
+/* Rotate descriptors for each service if needed. A non existing current
+ * descriptor will trigger a descriptor build for the next time period. */
+STATIC void
+rotate_all_descriptors(time_t now)
+{
+ /* XXX We rotate all our service descriptors at once. In the future it might
+ * be wise, to rotate service descriptors independently to hide that all
+ * those descriptors are on the same tor instance */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+
+ /* Note for a service booting up: Both descriptors are NULL in that case
+ * so this function might return true if we are in the timeframe for a
+ * rotation leading to basically swapping two NULL pointers which is
+ * harmless. However, the side effect is that triggering a rotation will
+ * update the service state and avoid doing anymore rotations after the
+ * two descriptors have been built. */
+ if (!should_rotate_descriptors(service, now)) {
+ continue;
+ }
+
+ log_info(LD_REND, "Time to rotate our descriptors (%p / %p) for %s",
+ service->desc_current, service->desc_next,
+ safe_str_client(service->onion_address));
+
+ rotate_service_descriptors(service, now);
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all our services are up
+ * to date and ready for the other scheduled events. This includes looking at
+ * the introduction points status and descriptor rotation time. */
+STATIC void
+run_housekeeping_event(time_t now)
+{
+ /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are
+ * simply moving things around or removing unneeded elements. */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+
+ /* If the service is starting off, set the rotation time. We can't do that
+ * at configure time because the get_options() needs to be set for setting
+ * that time that uses the voting interval. */
+ if (service->state.next_rotation_time == 0) {
+ /* Set the next rotation time of the descriptors. If it's Oct 25th
+ * 23:47:00, the next rotation time is when the next SRV is computed
+ * which is at Oct 26th 00:00:00 that is in 13 minutes. */
+ set_rotation_time(service, now);
+ }
+
+ /* Cleanup invalid intro points from the service descriptor. */
+ cleanup_intro_points(service, now);
+
+ /* Remove expired failing intro point from the descriptor failed list. We
+ * reset them at each INTRO_CIRC_RETRY_PERIOD. */
+ remove_expired_failing_intro(service, now);
+
+ /* At this point, the service is now ready to go through the scheduled
+ * events guaranteeing a valid state. Intro points might be missing from
+ * the descriptors after the cleanup but the update/build process will
+ * make sure we pick those missing ones. */
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Scheduled event run from the main loop. Make sure all descriptors are up to
+ * date. Once this returns, each service descriptor needs to be considered for
+ * new introduction circuits and then for upload. */
+static void
+run_build_descriptor_event(time_t now)
+{
+ /* For v2 services, this step happens in the upload event. */
+
+ /* Run v3+ events. */
+ /* We start by rotating the descriptors only if needed. */
+ rotate_all_descriptors(now);
+
+ /* Then, we'll try to build new descriptors that we might need. The
+ * condition is that the next descriptor is non existing because it has
+ * been rotated or we just started up. */
+ build_all_descriptors(now);
+
+ /* Finally, we'll check if we should update the descriptors. Missing
+ * introduction points will be picked in this function which is useful for
+ * newly built descriptors. */
+ update_all_descriptors(now);
+}
+
+/* For the given service, launch any intro point circuits that could be
+ * needed. This considers every descriptor of the service. */
+static void
+launch_intro_point_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* For both descriptors, try to launch any missing introduction point
+ * circuits using the current map. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Keep a ref on if we need a direct connection. We use this often. */
+ unsigned int direct_conn = service->config.is_single_onion;
+
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ extend_info_t *ei;
+
+ /* Skip the intro point that already has an existing circuit
+ * (established or not). */
+ if (hs_circ_service_get_intro_circ(ip)) {
+ continue;
+ }
+
+ ei = get_extend_info_from_intro_point(ip, direct_conn);
+ if (ei == NULL) {
+ /* This is possible if we can get a node_t but not the extend info out
+ * of it. In this case, we remove the intro point and a new one will
+ * be picked at the next main loop callback. */
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+ continue;
+ }
+
+ /* Launch a circuit to the intro point. */
+ ip->circuit_retries++;
+ if (hs_circ_launch_intro_point(service, ip, ei) < 0) {
+ log_info(LD_REND, "Unable to launch intro circuit to node %s "
+ "for service %s.",
+ safe_str_client(extend_info_describe(ei)),
+ safe_str_client(service->onion_address));
+ /* Intro point will be retried if possible after this. */
+ }
+ extend_info_free(ei);
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Don't try to build more than this many circuits before giving up for a
+ * while. Dynamically calculated based on the configured number of intro
+ * points for the given service and how many descriptor exists. The default
+ * use case of 3 introduction points and two descriptors will allow 28
+ * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */
+static unsigned int
+get_max_intro_circ_per_period(const hs_service_t *service)
+{
+ unsigned int count = 0;
+ unsigned int multiplier = 0;
+ unsigned int num_wanted_ip;
+
+ tor_assert(service);
+ tor_assert(service->config.num_intro_points <=
+ HS_CONFIG_V3_MAX_INTRO_POINTS);
+
+/* For a testing network, allow to do it for the maximum amount so circuit
+ * creation and rotation and so on can actually be tested without limit. */
+#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1
+ if (get_options()->TestingTorNetwork) {
+ return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING;
+ }
+
+ num_wanted_ip = service->config.num_intro_points;
+
+ /* The calculation is as follow. We have a number of intro points that we
+ * want configured as a torrc option (num_intro_points). We then add an
+ * extra value so we can launch multiple circuits at once and pick the
+ * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll
+ * pick 5 intros and launch 5 circuits. */
+ count += (num_wanted_ip + get_intro_point_num_extra());
+
+ /* Then we add the number of retries that is possible to do for each intro
+ * point. If we want 3 intros, we'll allow 3 times the number of possible
+ * retry. */
+ count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES);
+
+ /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we
+ * have none. */
+ multiplier += (service->desc_current) ? 1 : 0;
+ multiplier += (service->desc_next) ? 1 : 0;
+
+ return (count * multiplier);
+}
+
+/* For the given service, return 1 if the service is allowed to launch more
+ * introduction circuits else 0 if the maximum has been reached for the retry
+ * period of INTRO_CIRC_RETRY_PERIOD. */
+STATIC int
+can_service_launch_intro_circuit(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* Consider the intro circuit retry period of the service. */
+ if (now > (service->state.intro_circ_retry_started_time +
+ INTRO_CIRC_RETRY_PERIOD)) {
+ service->state.intro_circ_retry_started_time = now;
+ service->state.num_intro_circ_launched = 0;
+ goto allow;
+ }
+ /* Check if we can still launch more circuits in this period. */
+ if (service->state.num_intro_circ_launched <=
+ get_max_intro_circ_per_period(service)) {
+ goto allow;
+ }
+
+ /* Rate limit log that we've reached our circuit creation limit. */
+ {
+ char *msg;
+ time_t elapsed_time = now - service->state.intro_circ_retry_started_time;
+ static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD);
+ if ((msg = rate_limit_log(&rlimit, now))) {
+ log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit "
+ "of %u per %d seconds. It launched %u circuits in "
+ "the last %ld seconds. Will retry in %ld seconds.",
+ safe_str_client(service->onion_address),
+ get_max_intro_circ_per_period(service),
+ INTRO_CIRC_RETRY_PERIOD,
+ service->state.num_intro_circ_launched,
+ (long int) elapsed_time,
+ (long int) (INTRO_CIRC_RETRY_PERIOD - elapsed_time));
+ tor_free(msg);
+ }
+ }
+
+ /* Not allow. */
+ return 0;
+ allow:
+ return 1;
+}
+
+/* Scheduled event run from the main loop. Make sure we have all the circuits
+ * we need for each service. */
+static void
+run_build_circuit_event(time_t now)
+{
+ /* Make sure we can actually have enough information or able to build
+ * internal circuits as required by services. */
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
+ !have_completed_a_circuit()) {
+ return;
+ }
+
+ /* Run v2 check. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_intro_points(now);
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* For introduction circuit, we need to make sure we don't stress too much
+ * circuit creation so make sure this service is respecting that limit. */
+ if (can_service_launch_intro_circuit(service, now)) {
+ /* Launch intro point circuits if needed. */
+ launch_intro_point_circuits(service);
+ /* Once the circuits have opened, we'll make sure to update the
+ * descriptor intro point list and cleanup any extraneous. */
+ }
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the given
+ * hidden service directory. This does nothing if PublishHidServDescriptors
+ * is false. */
+static void
+upload_descriptor_to_hsdir(const hs_service_t *service,
+ hs_service_descriptor_t *desc, const node_t *hsdir)
+{
+ char *encoded_desc = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(hsdir);
+
+ /* Let's avoid doing that if tor is configured to not publish. */
+ if (!get_options()->PublishHidServDescriptors) {
+ log_info(LD_REND, "Service %s not publishing descriptor. "
+ "PublishHidServDescriptors is set to 1.",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+
+ /* First of all, we'll encode the descriptor. This should NEVER fail but
+ * just in case, let's make sure we have an actual usable descriptor. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto end;
+ }
+
+ /* Time to upload the descriptor to the directory. */
+ hs_service_upload_desc_to_dir(encoded_desc, service->config.version,
+ &service->keys.identity_pk,
+ &desc->blinded_kp.pubkey, hsdir->rs);
+
+ /* Add this node to previous_hsdirs list */
+ service_desc_note_upload(desc, hsdir);
+
+ /* Logging so we know where it was sent. */
+ {
+ int is_next_desc = (service->desc_next == desc);
+ const uint8_t *idx = (is_next_desc) ? hsdir->hsdir_index.store_second:
+ hsdir->hsdir_index.store_first;
+ log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64
+ " initiated upload request to %s with index %s",
+ safe_str_client(service->onion_address),
+ (is_next_desc) ? "next" : "current",
+ desc->desc->plaintext_data.revision_counter,
+ safe_str_client(node_describe(hsdir)),
+ safe_str_client(hex_str((const char *) idx, 32)));
+
+ /* Fire a UPLOAD control port event. */
+ hs_control_desc_event_upload(service->onion_address, hsdir->identity,
+ &desc->blinded_kp.pubkey, idx);
+ }
+
+ end:
+ tor_free(encoded_desc);
+ return;
+}
+
+/** Return a newly-allocated string for our state file which contains revision
+ * counter information for <b>desc</b>. The format is:
+ *
+ * HidServRevCounter <blinded_pubkey> <rev_counter>
+ */
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc)
+{
+ char *state_str = NULL;
+ char blinded_pubkey_b64[ED25519_BASE64_LEN+1];
+ uint64_t rev_counter = desc->desc->plaintext_data.revision_counter;
+ const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey;
+
+ /* Turn the blinded key into b64 so that we save it on state */
+ tor_assert(blinded_pubkey);
+ if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) {
+ goto done;
+ }
+
+ /* Format is: <blinded key> <rev counter> */
+ tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter);
+
+ log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!",
+ rev_counter, blinded_pubkey_b64);
+
+ done:
+ return state_str;
+}
+
+/** Update HS descriptor revision counters in our state by removing the old
+ * ones and writing down the ones that are currently active. */
+static void
+update_revision_counters_in_state(void)
+{
+ config_line_t *lines = NULL;
+ config_line_t **nextline = &lines;
+ or_state_t *state = get_or_state();
+
+ /* Prepare our state structure with the rev counters */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* We don't want to save zero counters */
+ if (desc->desc->plaintext_data.revision_counter == 0) {
+ continue;
+ }
+
+ *nextline = tor_malloc_zero(sizeof(config_line_t));
+ (*nextline)->key = tor_strdup("HidServRevCounter");
+ (*nextline)->value = encode_desc_rev_counter_for_state(desc);
+ nextline = &(*nextline)->next;
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+
+ /* Remove the old rev counters, and replace them with the new ones */
+ config_free_lines(state->HidServRevCounter);
+ state->HidServRevCounter = lines;
+
+ /* Set the state as dirty since we just edited it */
+ if (!get_options()->AvoidDiskWrites) {
+ or_state_mark_dirty(state, 0);
+ }
+}
+
+/** Scan the string <b>state_line</b> for the revision counter of the service
+ * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the
+ * line is relevant to this service, and return the cached revision
+ * counter. Else set <b>service_found_out</b> to False. */
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out)
+{
+ smartlist_t *items = NULL;
+ int ok;
+ ed25519_public_key_t pubkey_in_state;
+ uint64_t rev_counter = 0;
+
+ tor_assert(service_found_out);
+ tor_assert(state_line);
+ tor_assert(blinded_pubkey);
+
+ /* Assume that the line is not for this service */
+ *service_found_out = 0;
+
+ /* Start parsing the state line */
+ items = smartlist_new();
+ smartlist_split_string(items, state_line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring.");
+ goto done;
+ }
+
+ char *b64_key_str = smartlist_get(items, 0);
+ char *saved_rev_counter_str = smartlist_get(items, 1);
+
+ /* Parse blinded key to check if it's for this hidden service */
+ if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) {
+ log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring.");
+ goto done;
+ }
+ /* State line not for this hidden service */
+ if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) {
+ goto done;
+ }
+
+ rev_counter = tor_parse_uint64(saved_rev_counter_str,
+ 10, 0, UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring.");
+ goto done;
+ }
+
+ /* Since we got this far, the line was for this service */
+ *service_found_out = 1;
+
+ log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64,
+ b64_key_str, rev_counter);
+
+ done:
+ tor_assert(items);
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+
+ return rev_counter;
+}
+
+/** Dig into our state file and find the current revision counter for the
+ * service with blinded key <b>blinded_pubkey</b>. If no revision counter is
+ * found, return 0. */
+static uint64_t
+get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey)
+{
+ or_state_t *state = get_or_state();
+ config_line_t *line;
+
+ /* Set default value for rev counters (if not found) to 0 */
+ uint64_t final_rev_counter = 0;
+
+ for (line = state->HidServRevCounter ; line ; line = line->next) {
+ int service_found = 0;
+ uint64_t rev_counter = 0;
+
+ tor_assert(!strcmp(line->key, "HidServRevCounter"));
+
+ /* Scan all the HidServRevCounter lines till we find the line for this
+ service: */
+ rev_counter = check_state_line_for_service_rev_counter(line->value,
+ blinded_pubkey,
+ &service_found);
+ if (service_found) {
+ final_rev_counter = rev_counter;
+ goto done;
+ }
+ }
+
+ done:
+ return final_rev_counter;
+}
+
+/** Update the value of the revision counter for <b>hs_desc</b> and save it on
+ our state file. */
+static void
+increment_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ /* Increment the revision counter of <b>hs_desc</b> so the next update (which
+ * will trigger an upload) will have the right value. We do this at this
+ * stage to only do it once because a descriptor can have many updates before
+ * being uploaded. By doing it at upload, we are sure to only increment by 1
+ * and thus avoid leaking how many operations we made on the descriptor from
+ * the previous one before uploading. */
+ rev_counter++;
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+
+ update_revision_counters_in_state();
+}
+
+/** Set the revision counter in <b>hs_desc</b>, using the state file to find
+ * the current counter value if it exists. */
+static void
+set_descriptor_revision_counter(hs_descriptor_t *hs_desc)
+{
+ /* Find stored rev counter if it exists */
+ uint64_t rev_counter =
+ get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey);
+
+ hs_desc->plaintext_data.revision_counter = rev_counter;
+}
+
+/* Encode and sign the service descriptor desc and upload it to the
+ * responsible hidden service directories. If for_next_period is true, the set
+ * of directories are selected using the next hsdir_index. This does nothing
+ * if PublishHidServDescriptors is false. */
+STATIC void
+upload_descriptor_to_all(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ smartlist_t *responsible_dirs = NULL;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* We'll first cancel any directory request that are ongoing for this
+ * descriptor. It is possible that we can trigger multiple uploads in a
+ * short time frame which can lead to a race where the second upload arrives
+ * before the first one leading to a 400 malformed descriptor response from
+ * the directory. Closing all pending requests avoids that. */
+ close_directory_connections(service, desc);
+
+ /* Get our list of responsible HSDir. */
+ responsible_dirs = smartlist_new();
+ /* The parameter 0 means that we aren't a client so tell the function to use
+ * the spread store consensus paremeter. */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ service->desc_next == desc, 0, responsible_dirs);
+
+ /** Clear list of previous hsdirs since we are about to upload to a new
+ * list. Let's keep it up to date. */
+ service_desc_clear_previous_hsdirs(desc);
+
+ /* For each responsible HSDir we have, initiate an upload command. */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *,
+ hsdir_rs) {
+ const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest);
+ /* Getting responsible hsdir implies that the node_t object exists for the
+ * routerstatus_t found in the consensus else we have a problem. */
+ tor_assert(hsdir_node);
+ /* Upload this descriptor to the chosen directory. */
+ upload_descriptor_to_hsdir(service, desc, hsdir_node);
+ } SMARTLIST_FOREACH_END(hsdir_rs);
+
+ /* Set the next upload time for this descriptor. Even if we are configured
+ * to not upload, we still want to follow the right cycle of life for this
+ * descriptor. */
+ desc->next_upload_time =
+ (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN,
+ HS_SERVICE_NEXT_UPLOAD_TIME_MAX));
+ {
+ char fmt_next_time[ISO_TIME_LEN+1];
+ format_local_iso_time(fmt_next_time, desc->next_upload_time);
+ log_debug(LD_REND, "Service %s set to upload a descriptor at %s",
+ safe_str_client(service->onion_address), fmt_next_time);
+ }
+
+ /* Update the revision counter of this descriptor */
+ increment_descriptor_revision_counter(desc->desc);
+
+ smartlist_free(responsible_dirs);
+ return;
+}
+
+/** The set of HSDirs have changed: check if the change affects our descriptor
+ * HSDir placement, and if it does, reupload the desc. */
+STATIC int
+service_desc_hsdirs_changed(const hs_service_t *service,
+ const hs_service_descriptor_t *desc)
+{
+ int should_reupload = 0;
+ smartlist_t *responsible_dirs = smartlist_new();
+
+ /* No desc upload has happened yet: it will happen eventually */
+ if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) {
+ goto done;
+ }
+
+ /* Get list of responsible hsdirs */
+ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+ service->desc_next == desc, 0, responsible_dirs);
+
+ /* Check if any new hsdirs have been added to the responsible hsdirs set:
+ * Iterate over the list of new hsdirs, and reupload if any of them is not
+ * present in the list of previous hsdirs.
+ */
+ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) {
+ char b64_digest[BASE64_DIGEST_LEN+1] = {0};
+ digest_to_base64(b64_digest, hsdir_rs->identity_digest);
+
+ if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) {
+ should_reupload = 1;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(hsdir_rs);
+
+ done:
+ smartlist_free(responsible_dirs);
+
+ return should_reupload;
+}
+
+/* Return 1 if the given descriptor from the given service can be uploaded
+ * else return 0 if it can not. */
+static int
+should_service_upload_descriptor(const hs_service_t *service,
+ const hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* If this descriptors has missing intro points that is that it couldn't get
+ * them all when it was time to pick them, it means that we should upload
+ * instead of waiting an arbitrary amount of time breaking the service.
+ * Else, if we have no missing intro points, we use the value taken from the
+ * service configuration. */
+ if (desc->missing_intro_points) {
+ num_intro_points = digest256map_size(desc->intro_points.map);
+ } else {
+ num_intro_points = service->config.num_intro_points;
+ }
+
+ /* This means we tried to pick intro points but couldn't get any so do not
+ * upload descriptor in this case. We need at least one for the service to
+ * be reachable. */
+ if (desc->missing_intro_points && num_intro_points == 0) {
+ goto cannot;
+ }
+
+ /* Check if all our introduction circuit have been established for all the
+ * intro points we have selected. */
+ if (count_desc_circuit_established(desc) != num_intro_points) {
+ goto cannot;
+ }
+
+ /* Is it the right time to upload? */
+ if (desc->next_upload_time > now) {
+ goto cannot;
+ }
+
+ /* Don't upload desc if we don't have a live consensus */
+ if (!networkstatus_get_live_consensus(now)) {
+ goto cannot;
+ }
+
+ /* Do we know enough router descriptors to have adequate vision of the HSDir
+ hash ring? */
+ if (!router_have_minimum_dir_info()) {
+ goto cannot;
+ }
+
+ /* Can upload! */
+ return 1;
+ cannot:
+ return 0;
+}
+
+/* Scheduled event run from the main loop. Try to upload the descriptor for
+ * each service. */
+STATIC void
+run_upload_descriptor_event(time_t now)
+{
+ /* v2 services use the same function for descriptor creation and upload so
+ * we do everything here because the intro circuits were checked before. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_upload(now);
+ rend_consider_descriptor_republication();
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* If we were asked to re-examine the hash ring, and it changed, then
+ schedule an upload */
+ if (consider_republishing_hs_descriptors &&
+ service_desc_hsdirs_changed(service, desc)) {
+ service_desc_schedule_upload(desc, now, 0);
+ }
+
+ /* Can this descriptor be uploaded? */
+ if (!should_service_upload_descriptor(service, desc, now)) {
+ continue;
+ }
+
+ log_info(LD_REND, "Initiating upload for hidden service %s descriptor "
+ "for service %s with %u/%u introduction points%s.",
+ (desc == service->desc_current) ? "current" : "next",
+ safe_str_client(service->onion_address),
+ digest256map_size(desc->intro_points.map),
+ service->config.num_intro_points,
+ (desc->missing_intro_points) ? " (couldn't pick more)" : "");
+
+ /* At this point, we have to upload the descriptor so start by building
+ * the intro points descriptor section which we are now sure to be
+ * accurate because all circuits have been established. */
+ build_desc_intro_points(service, desc, now);
+
+ upload_descriptor_to_all(service, desc);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+
+ /* We are done considering whether to republish rend descriptors */
+ consider_republishing_hs_descriptors = 0;
+}
+
+/* Called when the introduction point circuit is done building and ready to be
+ * used. */
+static void
+service_intro_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ /* Let's do some basic sanity checking of the circ state */
+ if (BUG(!circ->cpath)) {
+ return;
+ }
+ if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
+ return;
+ }
+ if (BUG(!circ->hs_ident)) {
+ return;
+ }
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ log_warn(LD_REND, "Unknown introduction point auth key on circuit %u "
+ "for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* We can't have an IP object without a descriptor. */
+ tor_assert(desc);
+
+ if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) {
+ /* Getting here means that the circuit has been re-purposed because we
+ * have enough intro circuit opened. Remove the IP from the service. */
+ service_intro_point_remove(service, ip);
+ service_intro_point_free(ip);
+ }
+
+ goto done;
+
+ err:
+ /* Close circuit, we can't use it. */
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* Called when a rendezvous circuit is done building and ready to be used. */
+static void
+service_rendezvous_circ_has_opened(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(circ->cpath);
+ /* Getting here means this is a v3 rendezvous circuit. */
+ tor_assert(circ->hs_ident);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
+
+ /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the
+ * timestamp regardless of its content because that circuit could have been
+ * cannibalized so in any cases, we are about to use that circuit more. */
+ TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
+ pathbias_count_use_attempt(circ);
+
+ /* Get the corresponding service and intro point. */
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the rendezvous "
+ "circuit %u with cookie %s. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id,
+ hex_str((const char *) circ->hs_ident->rendezvous_cookie,
+ REND_COOKIE_LEN));
+ goto err;
+ }
+
+ /* If the cell can't be sent, the circuit will be closed within this
+ * function. */
+ hs_circ_service_rp_has_opened(service, circ);
+ goto done;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ return;
+}
+
+/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just
+ * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given
+ * introduction circuit. Return 0 on success else a negative value. */
+static int
+service_handle_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
+
+ /* We need the service and intro point for this cell. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, NULL);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_REND, "Unknown service identity key %s on the introduction "
+ "circuit %u. Can't find onion service.",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_REND, "Introduction circuit established without an intro "
+ "point object on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+
+ /* Try to parse the payload into a cell making sure we do actually have a
+ * valid cell. On success, the ip object and circuit purpose is updated to
+ * reflect the fact that the introduction circuit is established. */
+ if (hs_circ_handle_intro_established(service, ip, circ, payload,
+ payload_len) < 0) {
+ goto err;
+ }
+
+ /* Flag that we have an established circuit for this intro point. This value
+ * is what indicates the upload scheduled event if we are ready to build the
+ * intro point into the descriptor and upload. */
+ ip->circuit_established = 1;
+
+ log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell "
+ "on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* We just received an INTRODUCE2 cell on the established introduction circuit
+ * circ. Handle the cell and return 0 on success else a negative value. */
+static int
+service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+ tor_assert(payload);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
+
+ /* We'll need every object associated with this circuit. */
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+
+ /* Get service object from the circuit identifier. */
+ if (service == NULL) {
+ log_warn(LD_BUG, "Unknown service identity key %s when handling "
+ "an INTRODUCE2 cell on circuit %u",
+ safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto err;
+ }
+ if (ip == NULL) {
+ /* We don't recognize the key. */
+ log_warn(LD_BUG, "Unknown introduction auth key when handling "
+ "an INTRODUCE2 cell on circuit %u for service %s",
+ TO_CIRCUIT(circ)->n_circ_id,
+ safe_str_client(service->onion_address));
+ goto err;
+ }
+ /* If we have an IP object, we MUST have a descriptor object. */
+ tor_assert(desc);
+
+ /* The following will parse, decode and launch the rendezvous point circuit.
+ * Both current and legacy cells are handled. */
+ if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential,
+ payload, payload_len) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/* Add to list every filename used by service. This is used by the sandbox
+ * subsystem. */
+static void
+service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list)
+{
+ const char *s_dir;
+ char fname[128] = {0};
+
+ tor_assert(service);
+ tor_assert(list);
+
+ /* Ease our life. */
+ s_dir = service->config.directory_path;
+ /* The hostname file. */
+ smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname));
+ /* The key files splitted in two. */
+ tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+ tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix);
+ smartlist_add(list, hs_path_from_filename(s_dir, fname));
+}
+
+/* ========== */
+/* Public API */
+/* ========== */
+
+/* This is called everytime the service map (v2 or v3) changes that is if an
+ * element is added or removed. */
+void
+hs_service_map_has_changed(void)
+{
+ /* If we now have services where previously we had not, we need to enable
+ * the HS service main loop event. If we changed to having no services, we
+ * need to disable the event. */
+ rescan_periodic_events(get_options());
+}
+
+/* Upload an encoded descriptor in encoded_desc of the given version. This
+ * descriptor is for the service identity_pk and blinded_pk used to setup the
+ * directory connection identifier. It is uploaded to the directory hsdir_rs
+ * routerstatus_t object.
+ *
+ * NOTE: This function does NOT check for PublishHidServDescriptors because it
+ * is only used by the control port command HSPOST outside of this subsystem.
+ * Inside this code, upload_descriptor_to_hsdir() should be used. */
+void
+hs_service_upload_desc_to_dir(const char *encoded_desc,
+ const uint8_t version,
+ const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ const routerstatus_t *hsdir_rs)
+{
+ char version_str[4] = {0};
+ directory_request_t *dir_req;
+ hs_ident_dir_conn_t ident;
+
+ tor_assert(encoded_desc);
+ tor_assert(identity_pk);
+ tor_assert(blinded_pk);
+ tor_assert(hsdir_rs);
+
+ /* Setup the connection identifier. */
+ memset(&ident, 0, sizeof(ident));
+ hs_ident_dir_conn_init(identity_pk, blinded_pk, &ident);
+
+ /* This is our resource when uploading which is used to construct the URL
+ * with the version number: "/tor/hs/<version>/publish". */
+ tor_snprintf(version_str, sizeof(version_str), "%u", version);
+
+ /* Build the directory request for this HSDir. */
+ dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC);
+ directory_request_set_routerstatus(dir_req, hsdir_rs);
+ directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS);
+ directory_request_set_resource(dir_req, version_str);
+ directory_request_set_payload(dir_req, encoded_desc,
+ strlen(encoded_desc));
+ /* The ident object is copied over the directory connection object once
+ * the directory request is initiated. */
+ directory_request_upload_set_hs_ident(dir_req, &ident);
+
+ /* Initiate the directory request to the hsdir.*/
+ directory_initiate_request(dir_req);
+ directory_request_free(dir_req);
+}
+
+/* Add the ephemeral service using the secret key sk and ports. Both max
+ * streams parameter will be set in the newly created service.
+ *
+ * Ownership of sk and ports is passed to this routine. Regardless of
+ * success/failure, callers should not touch these values after calling this
+ * routine, and may assume that correct cleanup has been done on failure.
+ *
+ * Return an appropriate hs_service_add_ephemeral_status_t. */
+hs_service_add_ephemeral_status_t
+hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports,
+ int max_streams_per_rdv_circuit,
+ int max_streams_close_circuit, char **address_out)
+{
+ hs_service_add_ephemeral_status_t ret;
+ hs_service_t *service = NULL;
+
+ tor_assert(sk);
+ tor_assert(ports);
+ tor_assert(address_out);
+
+ service = hs_service_new(get_options());
+
+ /* Setup the service configuration with specifics. A default service is
+ * HS_VERSION_TWO so explicitly set it. */
+ service->config.version = HS_VERSION_THREE;
+ service->config.max_streams_per_rdv_circuit = max_streams_per_rdv_circuit;
+ service->config.max_streams_close_circuit = !!max_streams_close_circuit;
+ service->config.is_ephemeral = 1;
+ smartlist_free(service->config.ports);
+ service->config.ports = ports;
+
+ /* Handle the keys. */
+ memcpy(&service->keys.identity_sk, sk, sizeof(service->keys.identity_sk));
+ if (ed25519_public_key_generate(&service->keys.identity_pk,
+ &service->keys.identity_sk) < 0) {
+ log_warn(LD_CONFIG, "Unable to generate ed25519 public key"
+ "for v3 service.");
+ ret = RSAE_BADPRIVKEY;
+ goto err;
+ }
+
+ /* Make sure we have at least one port. */
+ if (smartlist_len(service->config.ports) == 0) {
+ log_warn(LD_CONFIG, "At least one VIRTPORT/TARGET must be specified "
+ "for v3 service.");
+ ret = RSAE_BADVIRTPORT;
+ goto err;
+ }
+
+ /* Build the onion address for logging purposes but also the control port
+ * uses it for the HS_DESC event. */
+ hs_build_address(&service->keys.identity_pk,
+ (uint8_t) service->config.version,
+ service->onion_address);
+
+ /* The only way the registration can fail is if the service public key
+ * already exists. */
+ if (BUG(register_service(hs_service_map, service) < 0)) {
+ log_warn(LD_CONFIG, "Onion Service private key collides with an "
+ "existing v3 service.");
+ ret = RSAE_ADDREXISTS;
+ goto err;
+ }
+
+ log_info(LD_CONFIG, "Added ephemeral v3 onion service: %s",
+ safe_str_client(service->onion_address));
+
+ *address_out = tor_strdup(service->onion_address);
+ ret = RSAE_OKAY;
+ goto end;
+
+ err:
+ hs_service_free(service);
+
+ end:
+ memwipe(sk, 0, sizeof(ed25519_secret_key_t));
+ tor_free(sk);
+ return ret;
+}
+
+/* For the given onion address, delete the ephemeral service. Return 0 on
+ * success else -1 on error. */
+int
+hs_service_del_ephemeral(const char *address)
+{
+ uint8_t version;
+ ed25519_public_key_t pk;
+ hs_service_t *service = NULL;
+
+ tor_assert(address);
+
+ if (hs_parse_address(address, &pk, NULL, &version) < 0) {
+ log_warn(LD_CONFIG, "Requested malformed v3 onion address for removal.");
+ goto err;
+ }
+
+ if (version != HS_VERSION_THREE) {
+ log_warn(LD_CONFIG, "Requested version of onion address for removal "
+ "is not supported.");
+ goto err;
+ }
+
+ service = find_service(hs_service_map, &pk);
+ if (service == NULL) {
+ log_warn(LD_CONFIG, "Requested non-existent v3 hidden service for "
+ "removal.");
+ goto err;
+ }
+
+ if (!service->config.is_ephemeral) {
+ log_warn(LD_CONFIG, "Requested non-ephemeral v3 hidden service for "
+ "removal.");
+ goto err;
+ }
+
+ /* Close circuits, remove from map and finally free. */
+ close_service_circuits(service);
+ remove_service(hs_service_map, service);
+ hs_service_free(service);
+
+ log_info(LD_CONFIG, "Removed ephemeral v3 hidden service: %s",
+ safe_str_client(address));
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Using the ed25519 public key pk, find a service for that key and return the
+ * current encoded descriptor as a newly allocated string or NULL if not
+ * found. This is used by the control port subsystem. */
+char *
+hs_service_lookup_current_desc(const ed25519_public_key_t *pk)
+{
+ const hs_service_t *service;
+
+ tor_assert(pk);
+
+ service = find_service(hs_service_map, pk);
+ if (service && service->desc_current) {
+ char *encoded_desc = NULL;
+ /* No matter what is the result (which should never be a failure), return
+ * the encoded variable, if success it will contain the right thing else
+ * it will be NULL. */
+ hs_desc_encode_descriptor(service->desc_current->desc,
+ &service->desc_current->signing_kp,
+ &encoded_desc);
+ return encoded_desc;
+ }
+
+ return NULL;
+}
+
+/* Return the number of service we have configured and usable. */
+unsigned int
+hs_service_get_num_services(void)
+{
+ if (hs_service_map == NULL) {
+ return 0;
+ }
+ return HT_SIZE(hs_service_map);
+}
+
+/* Called once an introduction circuit is closed. If the circuit doesn't have
+ * a v3 identifier, it is ignored. */
+void
+hs_service_intro_circ_has_closed(origin_circuit_t *circ)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ if (circ->hs_ident == NULL) {
+ /* This is not a v3 circuit, ignore. */
+ goto end;
+ }
+
+ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc);
+ if (service == NULL) {
+ /* This is possible if the circuits are closed and the service is
+ * immediately deleted. */
+ log_info(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on intro circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ goto end;
+ }
+ if (ip == NULL) {
+ /* The introduction point object has already been removed probably by our
+ * cleanup process so ignore. */
+ goto end;
+ }
+ /* Can't have an intro point object without a descriptor. */
+ tor_assert(desc);
+
+ /* Circuit disappeared so make sure the intro point is updated. By
+ * keeping the object in the descriptor, we'll be able to retry. */
+ ip->circuit_established = 0;
+
+ end:
+ return;
+}
+
+/* Given conn, a rendezvous edge connection acting as an exit stream, look up
+ * the hidden service for the circuit circ, and look up the port and address
+ * based on the connection port. Assign the actual connection address.
+ *
+ * Return 0 on success. Return -1 on failure and the caller should NOT close
+ * the circuit. Return -2 on failure and the caller MUST close the circuit for
+ * security reasons. */
+int
+hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn)
+{
+ hs_service_t *service = NULL;
+
+ tor_assert(circ);
+ tor_assert(conn);
+ tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ tor_assert(circ->hs_ident);
+
+ get_objects_from_ident(circ->hs_ident, &service, NULL, NULL);
+
+ if (service == NULL) {
+ log_warn(LD_REND, "Unable to find any hidden service associated "
+ "identity key %s on rendezvous circuit %u.",
+ ed25519_fmt(&circ->hs_ident->identity_pk),
+ TO_CIRCUIT(circ)->n_circ_id);
+ /* We want the caller to close the circuit because it's not a valid
+ * service so no danger. Attempting to bruteforce the entire key space by
+ * opening circuits to learn which service is being hosted here is
+ * impractical. */
+ goto err_close;
+ }
+
+ /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if
+ * this circuit will exceed the limit. */
+ if (service->config.max_streams_per_rdv_circuit > 0 &&
+ (circ->hs_ident->num_rdv_streams >=
+ service->config.max_streams_per_rdv_circuit)) {
+#define MAX_STREAM_WARN_INTERVAL 600
+ static struct ratelim_t stream_ratelim =
+ RATELIM_INIT(MAX_STREAM_WARN_INTERVAL);
+ log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND,
+ "Maximum streams per circuit limit reached on "
+ "rendezvous circuit %u for service %s. Circuit has "
+ "%" PRIu64 " out of %" PRIu64 " streams. %s.",
+ TO_CIRCUIT(circ)->n_circ_id,
+ service->onion_address,
+ circ->hs_ident->num_rdv_streams,
+ service->config.max_streams_per_rdv_circuit,
+ service->config.max_streams_close_circuit ?
+ "Closing circuit" : "Ignoring open stream request");
+ if (service->config.max_streams_close_circuit) {
+ /* Service explicitly configured to close immediately. */
+ goto err_close;
+ }
+ /* Exceeding the limit makes tor silently ignore the stream creation
+ * request and keep the circuit open. */
+ goto err_no_close;
+ }
+
+ /* Find a virtual port of that service mathcing the one in the connection if
+ * successful, set the address in the connection. */
+ if (hs_set_conn_addr_port(service->config.ports, conn) < 0) {
+ log_info(LD_REND, "No virtual port mapping exists for port %d for "
+ "hidden service %s.",
+ TO_CONN(conn)->port, service->onion_address);
+ if (service->config.allow_unknown_ports) {
+ /* Service explicitly allow connection to unknown ports so close right
+ * away because we do not care about port mapping. */
+ goto err_close;
+ }
+ /* If the service didn't explicitly allow it, we do NOT close the circuit
+ * here to raise the bar in terms of performance for port mapping. */
+ goto err_no_close;
+ }
+
+ /* Success. */
+ return 0;
+ err_close:
+ /* Indicate the caller that the circuit should be closed. */
+ return -2;
+ err_no_close:
+ /* Indicate the caller to NOT close the circuit. */
+ return -1;
+}
+
+/* Add to file_list every filename used by a configured hidden service, and to
+ * dir_list every directory path used by a configured hidden service. This is
+ * used by the sandbox subsystem to whitelist those. */
+void
+hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list)
+{
+ tor_assert(file_list);
+ tor_assert(dir_list);
+
+ /* Add files and dirs for legacy services. */
+ rend_services_add_filenames_to_lists(file_list, dir_list);
+
+ /* Add files and dirs for v3+. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Skip ephemeral service, they don't touch the disk. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ service_add_fnames_to_list(service, file_list);
+ smartlist_add_strdup(dir_list, service->config.directory_path);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Called when our internal view of the directory has changed. We might have
+ * received a new batch of descriptors which might affect the shape of the
+ * HSDir hash ring. Signal that we should reexamine the hash ring and
+ * re-upload our HS descriptors if needed. */
+void
+hs_service_dir_info_changed(void)
+{
+ if (hs_service_get_num_services() > 0) {
+ /* New directory information usually goes every consensus so rate limit
+ * every 30 minutes to not be too conservative. */
+ static struct ratelim_t dir_info_changed_ratelim = RATELIM_INIT(30 * 60);
+ log_fn_ratelim(&dir_info_changed_ratelim, LOG_INFO, LD_REND,
+ "New dirinfo arrived: consider reuploading descriptor");
+ consider_republishing_hs_descriptors = 1;
+ }
+}
+
+/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and
+ * launch a circuit to the rendezvous point. */
+int
+hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ /* Do some initial validation and logging before we parse the cell */
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto done;
+ }
+
+ if (circ->hs_ident) {
+ ret = service_handle_introduce2(circ, payload, payload_len);
+ hs_stats_note_introduce2_cell(1);
+ } else {
+ ret = rend_service_receive_introduction(circ, payload, payload_len);
+ hs_stats_note_introduce2_cell(0);
+ }
+
+ done:
+ return ret;
+}
+
+/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an
+ * established introduction point. Return 0 on success else a negative value
+ * and the circuit is closed. */
+int
+hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len)
+{
+ int ret = -1;
+
+ tor_assert(circ);
+ tor_assert(payload);
+
+ if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
+ log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a "
+ "non introduction circuit of purpose %d",
+ TO_CIRCUIT(circ)->purpose);
+ goto err;
+ }
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ if (circ->hs_ident) {
+ ret = service_handle_intro_established(circ, payload, payload_len);
+ } else {
+ ret = rend_service_intro_established(circ, payload, payload_len);
+ }
+
+ if (ret < 0) {
+ goto err;
+ }
+ return 0;
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Called when any kind of hidden service circuit is done building thus
+ * opened. This is the entry point from the circuit subsystem. */
+void
+hs_service_circuit_has_opened(origin_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Handle both version. v2 uses rend_data and v3 uses the hs circuit
+ * identifier hs_ident. Can't be both. */
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ if (circ->hs_ident) {
+ service_intro_circ_has_opened(circ);
+ } else {
+ rend_service_intro_has_opened(circ);
+ }
+ break;
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ if (circ->hs_ident) {
+ service_rendezvous_circ_has_opened(circ);
+ } else {
+ rend_service_rendezvous_has_opened(circ);
+ }
+ break;
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Load and/or generate keys for all onion services including the client
+ * authorization if any. Return 0 on success, -1 on failure. */
+int
+hs_service_load_all_keys(void)
+{
+ /* Load v2 service keys if we have v2. */
+ if (rend_num_services() != 0) {
+ if (rend_service_load_all_keys(NULL) < 0) {
+ goto err;
+ }
+ }
+
+ /* Load or/and generate them for v3+. */
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) {
+ /* Ignore ephemeral service, they already have their keys set. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ log_info(LD_REND, "Loading v3 onion service keys from %s",
+ service_escaped_dir(service));
+ if (load_service_keys(service) < 0) {
+ goto err;
+ }
+ /* XXX: Load/Generate client authorization keys. (#20700) */
+ } SMARTLIST_FOREACH_END(service);
+
+ /* Final step, the staging list contains service in a quiescent state that
+ * is ready to be used. Register them to the global map. Once this is over,
+ * the staging list will be cleaned up. */
+ register_all_services();
+
+ /* All keys have been loaded successfully. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Put all service object in the given service list. After this, the caller
+ * looses ownership of every elements in the list and responsible to free the
+ * list pointer. */
+void
+hs_service_stage_services(const smartlist_t *service_list)
+{
+ tor_assert(service_list);
+ /* This list is freed at registration time but this function can be called
+ * multiple time. */
+ if (hs_service_staging_list == NULL) {
+ hs_service_staging_list = smartlist_new();
+ }
+ /* Add all service object to our staging list. Caller is responsible for
+ * freeing the service_list. */
+ smartlist_add_all(hs_service_staging_list, service_list);
+}
+
+/* Allocate and initilize a service object. The service configuration will
+ * contain the default values. Return the newly allocated object pointer. This
+ * function can't fail. */
+hs_service_t *
+hs_service_new(const or_options_t *options)
+{
+ hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
+ /* Set default configuration value. */
+ set_service_default_config(&service->config, options);
+ /* Set the default service version. */
+ service->config.version = HS_SERVICE_DEFAULT_VERSION;
+ /* Allocate the CLIENT_PK replay cache in service state. */
+ service->state.replay_cache_rend_cookie =
+ replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL);
+
+ return service;
+}
+
+/* Free the given <b>service</b> object and all its content. This function
+ * also takes care of wiping service keys from memory. It is safe to pass a
+ * NULL pointer. */
+void
+hs_service_free_(hs_service_t *service)
+{
+ if (service == NULL) {
+ return;
+ }
+
+ /* Free descriptors. Go over both descriptor with this loop. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ service_descriptor_free(desc);
+ } FOR_EACH_DESCRIPTOR_END;
+
+ /* Free service configuration. */
+ service_clear_config(&service->config);
+
+ /* Free replay cache from state. */
+ if (service->state.replay_cache_rend_cookie) {
+ replaycache_free(service->state.replay_cache_rend_cookie);
+ }
+
+ /* Wipe service keys. */
+ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
+
+ tor_free(service);
+}
+
+/* Periodic callback. Entry point from the main loop to the HS service
+ * subsystem. This is call every second. This is skipped if tor can't build a
+ * circuit or the network is disabled. */
+void
+hs_service_run_scheduled_events(time_t now)
+{
+ /* First thing we'll do here is to make sure our services are in a
+ * quiescent state for the scheduled events. */
+ run_housekeeping_event(now);
+
+ /* Order matters here. We first make sure the descriptor object for each
+ * service contains the latest data. Once done, we check if we need to open
+ * new introduction circuit. Finally, we try to upload the descriptor for
+ * each service. */
+
+ /* Make sure descriptors are up to date. */
+ run_build_descriptor_event(now);
+ /* Make sure services have enough circuits. */
+ run_build_circuit_event(now);
+ /* Upload the descriptors if needed/possible. */
+ run_upload_descriptor_event(now);
+}
+
+/* Initialize the service HS subsystem. */
+void
+hs_service_init(void)
+{
+ /* Should never be called twice. */
+ tor_assert(!hs_service_map);
+ tor_assert(!hs_service_staging_list);
+
+ /* v2 specific. */
+ rend_service_init();
+
+ hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht));
+ HT_INIT(hs_service_ht, hs_service_map);
+
+ hs_service_staging_list = smartlist_new();
+}
+
+/* Release all global storage of the hidden service subsystem. */
+void
+hs_service_free_all(void)
+{
+ rend_service_free_all();
+ service_free_all();
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global service map size. Only used by unit test. */
+STATIC unsigned int
+get_hs_service_map_size(void)
+{
+ return HT_SIZE(hs_service_map);
+}
+
+/* Return the staging list size. Only used by unit test. */
+STATIC int
+get_hs_service_staging_list_size(void)
+{
+ return smartlist_len(hs_service_staging_list);
+}
+
+STATIC hs_service_ht *
+get_hs_service_map(void)
+{
+ return hs_service_map;
+}
+
+STATIC hs_service_t *
+get_first_service(void)
+{
+ hs_service_t **obj = HT_START(hs_service_ht, hs_service_map);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return *obj;
+}
+
+#endif /* defined(TOR_UNIT_TESTS) */
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
new file mode 100644
index 0000000000..4676042b54
--- /dev/null
+++ b/src/feature/hs/hs_service.h
@@ -0,0 +1,378 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.h
+ * \brief Header file containing service data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_SERVICE_H
+#define TOR_HS_SERVICE_H
+
+#include "lib/crypt_ops/crypto_curve25519.h"
+#include "lib/crypt_ops/crypto_ed25519.h"
+#include "or/replaycache.h"
+
+#include "or/hs_common.h"
+#include "or/hs_descriptor.h"
+#include "or/hs_ident.h"
+#include "or/hs_intropoint.h"
+
+/* Trunnel */
+#include "trunnel/hs/cell_establish_intro.h"
+
+/* When loading and configuring a service, this is the default version it will
+ * be configured for as it is possible that no HiddenServiceVersion is
+ * present. */
+#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO
+
+/* As described in the specification, service publishes their next descriptor
+ * at a random time between those two values (in seconds). */
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60)
+#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
+
+/* Service side introduction point. */
+typedef struct hs_service_intro_point_t {
+ /* Top level intropoint "shared" data between client/service. */
+ hs_intropoint_t base;
+
+ /* Onion key of the introduction point used to extend to it for the ntor
+ * handshake. */
+ curve25519_public_key_t onion_key;
+
+ /* Authentication keypair used to create the authentication certificate
+ * which is published in the descriptor. */
+ ed25519_keypair_t auth_key_kp;
+
+ /* Encryption keypair for the "ntor" type. */
+ curve25519_keypair_t enc_key_kp;
+
+ /* Legacy key if that intro point doesn't support v3. This should be used if
+ * the base object legacy flag is set. */
+ crypto_pk_t *legacy_key;
+ /* Legacy key SHA1 public key digest. This should be used only if the base
+ * object legacy flag is set. */
+ uint8_t legacy_key_digest[DIGEST_LEN];
+
+ /* Amount of INTRODUCE2 cell accepted from this intro point. */
+ uint64_t introduce2_count;
+
+ /* Maximum number of INTRODUCE2 cell this intro point should accept. */
+ uint64_t introduce2_max;
+
+ /* The time at which this intro point should expire and stop being used. */
+ time_t time_to_expire;
+
+ /* The amount of circuit creation we've made to this intro point. This is
+ * incremented every time we do a circuit relaunch on this intro point which
+ * is triggered when the circuit dies but the node is still in the
+ * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */
+ uint32_t circuit_retries;
+
+ /* Set if this intro point has an established circuit. */
+ unsigned int circuit_established : 1;
+
+ /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the
+ * circuit associated with this intro point has received. This is used to
+ * prevent replay attacks. */
+ replaycache_t *replay_cache;
+} hs_service_intro_point_t;
+
+/* Object handling introduction points of a service. */
+typedef struct hs_service_intropoints_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t retry_period_started;
+
+ /* Number of circuit we've launched during a single retry period. */
+ unsigned int num_circuits_launched;
+
+ /* Contains the current hs_service_intro_point_t objects indexed by
+ * authentication public key. */
+ digest256map_t *map;
+
+ /* Contains node's identity key digest that were introduction point for this
+ * descriptor but were retried to many times. We keep those so we avoid
+ * re-picking them over and over for a circuit retry period.
+ * XXX: Once we have #22173, change this to only use ed25519 identity. */
+ digestmap_t *failed_id;
+} hs_service_intropoints_t;
+
+/* Representation of a service descriptor. */
+typedef struct hs_service_descriptor_t {
+ /* Decoded descriptor. This object is used for encoding when the service
+ * publishes the descriptor. */
+ hs_descriptor_t *desc;
+
+ /* Descriptor signing keypair. */
+ ed25519_keypair_t signing_kp;
+
+ /* Blinded keypair derived from the master identity public key. */
+ ed25519_keypair_t blinded_kp;
+
+ /* When is the next time when we should upload the descriptor. */
+ time_t next_upload_time;
+
+ /* Introduction points assign to this descriptor which contains
+ * hs_service_intropoints_t object indexed by authentication key (the RSA
+ * key if the node is legacy). */
+ hs_service_intropoints_t intro_points;
+
+ /* The time period number this descriptor has been created for. */
+ uint64_t time_period_num;
+
+ /* True iff we have missing intro points for this descriptor because we
+ * couldn't pick any nodes. */
+ unsigned int missing_intro_points : 1;
+
+ /** List of the responsible HSDirs (their b64ed identity digest) last time we
+ * uploaded this descriptor. If the set of responsible HSDirs is different
+ * from this list, this means we received new dirinfo and we need to
+ * reupload our descriptor. */
+ smartlist_t *previous_hsdirs;
+} hs_service_descriptor_t;
+
+/* Service key material. */
+typedef struct hs_service_keys_t {
+ /* Master identify public key. */
+ ed25519_public_key_t identity_pk;
+ /* Master identity private key. */
+ ed25519_secret_key_t identity_sk;
+ /* True iff the key is kept offline which means the identity_sk MUST not be
+ * used in that case. */
+ unsigned int is_identify_key_offline : 1;
+} hs_service_keys_t;
+
+/* Service configuration. The following are set from the torrc options either
+ * set by the configuration file or by the control port. Nothing else should
+ * change those values. */
+typedef struct hs_service_config_t {
+ /* Protocol version of the service. Specified by HiddenServiceVersion
+ * option. */
+ uint32_t version;
+
+ /* List of rend_service_port_config_t */
+ smartlist_t *ports;
+
+ /* Path on the filesystem where the service persistent data is stored. NULL
+ * if the service is ephemeral. Specified by HiddenServiceDir option. */
+ char *directory_path;
+
+ /* The maximum number of simultaneous streams per rendezvous circuit that
+ * are allowed to be created. No limit if 0. Specified by
+ * HiddenServiceMaxStreams option. */
+ uint64_t max_streams_per_rdv_circuit;
+
+ /* If true, we close circuits that exceed the max_streams_per_rdv_circuit
+ * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */
+ unsigned int max_streams_close_circuit : 1;
+
+ /* How many introduction points this service has. Specified by
+ * HiddenServiceNumIntroductionPoints option. */
+ unsigned int num_intro_points;
+
+ /* True iff we allow request made on unknown ports. Specified by
+ * HiddenServiceAllowUnknownPorts option. */
+ unsigned int allow_unknown_ports : 1;
+
+ /* If true, this service is a Single Onion Service. Specified by
+ * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */
+ unsigned int is_single_onion : 1;
+
+ /* If true, allow group read permissions on the directory_path. Specified by
+ * HiddenServiceDirGroupReadable option. */
+ unsigned int dir_group_readable : 1;
+
+ /* Is this service ephemeral? */
+ unsigned int is_ephemeral : 1;
+} hs_service_config_t;
+
+/* Service state. */
+typedef struct hs_service_state_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t intro_circ_retry_started_time;
+
+ /* Number of circuit we've launched during a single retry period. This
+ * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */
+ unsigned int num_intro_circ_launched;
+
+ /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect
+ * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point
+ * through two or more different introduction points; when they do, this
+ * keeps us from launching multiple simultaneous attempts to connect to the
+ * same rend point. */
+ replaycache_t *replay_cache_rend_cookie;
+
+ /* When is the next time we should rotate our descriptors. This is has to be
+ * done at the start time of the next SRV protocol run. */
+ time_t next_rotation_time;
+} hs_service_state_t;
+
+/* Representation of a service running on this tor instance. */
+typedef struct hs_service_t {
+ /* Onion address base32 encoded and NUL terminated. We keep it for logging
+ * purposes so we don't have to build it everytime. */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ /* Hashtable node: use to look up the service by its master public identity
+ * key in the service global map. */
+ HT_ENTRY(hs_service_t) hs_service_node;
+
+ /* Service state which contains various flags and counters. */
+ hs_service_state_t state;
+
+ /* Key material of the service. */
+ hs_service_keys_t keys;
+
+ /* Configuration of the service. */
+ hs_service_config_t config;
+
+ /* Current descriptor. */
+ hs_service_descriptor_t *desc_current;
+ /* Next descriptor. */
+ hs_service_descriptor_t *desc_next;
+
+ /* XXX: Credential (client auth.) #20700. */
+
+} hs_service_t;
+
+/* For the service global hash map, we define a specific type for it which
+ * will make it safe to use and specific to some controlled parameters such as
+ * the hashing function and how to compare services. */
+typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht;
+
+/* API */
+
+/* Global initializer and cleanup function. */
+void hs_service_init(void);
+void hs_service_free_all(void);
+
+/* Service new/free functions. */
+hs_service_t *hs_service_new(const or_options_t *options);
+void hs_service_free_(hs_service_t *service);
+#define hs_service_free(s) FREE_AND_NULL(hs_service_t, hs_service_free_, (s))
+
+unsigned int hs_service_get_num_services(void);
+void hs_service_stage_services(const smartlist_t *service_list);
+int hs_service_load_all_keys(void);
+void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
+ smartlist_t *dir_list);
+int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
+ edge_connection_t *conn);
+
+void hs_service_map_has_changed(void);
+void hs_service_dir_info_changed(void);
+void hs_service_run_scheduled_events(time_t now);
+void hs_service_circuit_has_opened(origin_circuit_t *circ);
+int hs_service_receive_intro_established(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+int hs_service_receive_introduce2(origin_circuit_t *circ,
+ const uint8_t *payload,
+ size_t payload_len);
+
+void hs_service_intro_circ_has_closed(origin_circuit_t *circ);
+
+char *hs_service_lookup_current_desc(const ed25519_public_key_t *pk);
+
+hs_service_add_ephemeral_status_t
+hs_service_add_ephemeral(ed25519_secret_key_t *sk, smartlist_t *ports,
+ int max_streams_per_rdv_circuit,
+ int max_streams_close_circuit, char **address_out);
+int hs_service_del_ephemeral(const char *address);
+
+/* Used outside of the HS subsystem by the control port command HSPOST. */
+void hs_service_upload_desc_to_dir(const char *encoded_desc,
+ const uint8_t version,
+ const ed25519_public_key_t *identity_pk,
+ const ed25519_public_key_t *blinded_pk,
+ const routerstatus_t *hsdir_rs);
+
+#ifdef HS_SERVICE_PRIVATE
+
+#ifdef TOR_UNIT_TESTS
+/* Useful getters for unit tests. */
+STATIC unsigned int get_hs_service_map_size(void);
+STATIC int get_hs_service_staging_list_size(void);
+STATIC hs_service_ht *get_hs_service_map(void);
+STATIC hs_service_t *get_first_service(void);
+STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
+ const hs_service_t *service,
+ const hs_ident_circuit_t *ident);
+#endif
+
+/* Service accessors. */
+STATIC hs_service_t *find_service(hs_service_ht *map,
+ const ed25519_public_key_t *pk);
+STATIC void remove_service(hs_service_ht *map, hs_service_t *service);
+STATIC int register_service(hs_service_ht *map, hs_service_t *service);
+/* Service introduction point functions. */
+STATIC hs_service_intro_point_t *service_intro_point_new(
+ const extend_info_t *ei,
+ unsigned int is_legacy);
+STATIC void service_intro_point_free_(hs_service_intro_point_t *ip);
+#define service_intro_point_free(ip) \
+ FREE_AND_NULL(hs_service_intro_point_t, \
+ service_intro_point_free_, (ip))
+STATIC void service_intro_point_add(digest256map_t *map,
+ hs_service_intro_point_t *ip);
+STATIC void service_intro_point_remove(const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+STATIC hs_service_intro_point_t *service_intro_point_find(
+ const hs_service_t *service,
+ const ed25519_public_key_t *auth_key);
+/* Service descriptor functions. */
+STATIC hs_service_descriptor_t *service_descriptor_new(void);
+STATIC hs_service_descriptor_t *service_desc_find_by_intro(
+ const hs_service_t *service,
+ const hs_service_intro_point_t *ip);
+/* Helper functions. */
+STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident,
+ hs_service_t **service,
+ hs_service_intro_point_t **ip,
+ hs_service_descriptor_t **desc);
+STATIC const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip);
+STATIC int can_service_launch_intro_circuit(hs_service_t *service,
+ time_t now);
+STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now);
+STATIC void run_housekeeping_event(time_t now);
+STATIC void rotate_all_descriptors(time_t now);
+STATIC void build_all_descriptors(time_t now);
+STATIC void update_all_descriptors(time_t now);
+STATIC void run_upload_descriptor_event(time_t now);
+
+STATIC char *
+encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc);
+
+STATIC void service_descriptor_free_(hs_service_descriptor_t *desc);
+#define service_descriptor_free(d) \
+ FREE_AND_NULL(hs_service_descriptor_t, \
+ service_descriptor_free_, (d))
+
+STATIC uint64_t
+check_state_line_for_service_rev_counter(const char *state_line,
+ const ed25519_public_key_t *blinded_pubkey,
+ int *service_found_out);
+
+STATIC int
+write_address_to_file(const hs_service_t *service, const char *fname_);
+
+STATIC void upload_descriptor_to_all(const hs_service_t *service,
+ hs_service_descriptor_t *desc);
+
+STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc,
+ time_t now,
+ int descriptor_changed);
+
+STATIC int service_desc_hsdirs_changed(const hs_service_t *service,
+ const hs_service_descriptor_t *desc);
+
+#endif /* defined(HS_SERVICE_PRIVATE) */
+
+#endif /* !defined(TOR_HS_SERVICE_H) */
+
diff --git a/src/feature/hs/hs_stats.c b/src/feature/hs/hs_stats.c
new file mode 100644
index 0000000000..c8a99b19d4
--- /dev/null
+++ b/src/feature/hs/hs_stats.c
@@ -0,0 +1,58 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_stats.c
+ * \brief Keeps stats about the activity of our onion service(s).
+ **/
+
+#include "or/or.h"
+#include "or/hs_stats.h"
+#include "or/hs_service.h"
+
+/** Number of v3 INTRODUCE2 cells received */
+static uint32_t n_introduce2_v3 = 0;
+/** Number of v2 INTRODUCE2 cells received */
+static uint32_t n_introduce2_v2 = 0;
+/** Number of attempts to make a circuit to a rendezvous point */
+static uint32_t n_rendezvous_launches = 0;
+
+/** Note that we received another INTRODUCE2 cell. */
+void
+hs_stats_note_introduce2_cell(int is_hsv3)
+{
+ if (is_hsv3) {
+ n_introduce2_v3++;
+ } else {
+ n_introduce2_v2++;
+ }
+}
+
+/** Return the number of v3 INTRODUCE2 cells we have received. */
+uint32_t
+hs_stats_get_n_introduce2_v3_cells(void)
+{
+ return n_introduce2_v3;
+}
+
+/** Return the number of v2 INTRODUCE2 cells we have received. */
+uint32_t
+hs_stats_get_n_introduce2_v2_cells(void)
+{
+ return n_introduce2_v2;
+}
+
+/** Note that we attempted to launch another circuit to a rendezvous point. */
+void
+hs_stats_note_service_rendezvous_launch(void)
+{
+ n_rendezvous_launches++;
+}
+
+/** Return the number of rendezvous circuits we have attempted to launch. */
+uint32_t
+hs_stats_get_n_rendezvous_launches(void)
+{
+ return n_rendezvous_launches;
+}
+
diff --git a/src/feature/hs/hs_stats.h b/src/feature/hs/hs_stats.h
new file mode 100644
index 0000000000..a946ad75e5
--- /dev/null
+++ b/src/feature/hs/hs_stats.h
@@ -0,0 +1,14 @@
+/* Copyright (c) 2016-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_stats.h
+ * \brief Header file for hs_stats.c
+ **/
+
+void hs_stats_note_introduce2_cell(int is_hsv3);
+uint32_t hs_stats_get_n_introduce2_v3_cells(void);
+uint32_t hs_stats_get_n_introduce2_v2_cells(void);
+void hs_stats_note_service_rendezvous_launch(void);
+uint32_t hs_stats_get_n_rendezvous_launches(void);
+
diff --git a/src/feature/hs/hsdir_index_st.h b/src/feature/hs/hsdir_index_st.h
new file mode 100644
index 0000000000..de5cc9bd16
--- /dev/null
+++ b/src/feature/hs/hsdir_index_st.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef HSDIR_INDEX_ST_H
+#define HSDIR_INDEX_ST_H
+
+/* Hidden service directory index used in a node_t which is set once we set
+ * the consensus. */
+struct hsdir_index_t {
+ /* HSDir index to use when fetching a descriptor. */
+ uint8_t fetch[DIGEST256_LEN];
+
+ /* HSDir index used by services to store their first and second
+ * descriptor. The first descriptor is chronologically older than the second
+ * one and uses older TP and SRV values. */
+ uint8_t store_first[DIGEST256_LEN];
+ uint8_t store_second[DIGEST256_LEN];
+};
+
+#endif
+