[tor-commits] [tor/master] prop224: Service v3 descriptor creation and logic

nickm at torproject.org nickm at torproject.org
Wed Aug 9 00:36:37 UTC 2017


commit 00a02a3a59f6e44619e6caed150b001acae37231
Author: David Goulet <dgoulet at torproject.org>
Date:   Fri Feb 3 15:30:46 2017 -0500

    prop224: Service v3 descriptor creation and logic
    
    This commit adds the functionality for a service to build its descriptor.
    Also, a global call to build all descriptors for all services is added to the
    service scheduled events.
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
---
 src/or/circuitlist.c   |  35 ++
 src/or/circuitlist.h   |   1 +
 src/or/circuituse.c    |   5 -
 src/or/hs_descriptor.c |  90 ++++-
 src/or/hs_descriptor.h |  12 +-
 src/or/hs_intropoint.c |  14 +
 src/or/hs_intropoint.h |  10 +-
 src/or/hs_service.c    | 960 ++++++++++++++++++++++++++++++++++++++++++++++---
 src/or/hs_service.h    |   8 +-
 src/or/rendservice.c   |   4 +-
 src/or/rendservice.h   |   2 +-
 11 files changed, 1077 insertions(+), 64 deletions(-)

diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index d11e12878..f44343e11 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -1532,6 +1532,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start)
   return NULL;
 }
 
+/** Return the first service rendezvous circuit originating from the global
+ * circuit list after <b>start</b> or at the start of the list if <b>start</b>
+ * is NULL. Return NULL if no circuit is found.
+ *
+ * A service rendezvous point circuit has a purpose of either
+ * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does
+ * not return a circuit marked for close and its state must be open. */
+origin_circuit_t *
+circuit_get_next_service_rp_circ(origin_circuit_t *start)
+{
+  int idx = 0;
+  smartlist_t *lst = circuit_get_global_list();
+
+  if (start) {
+    idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
+  }
+
+  for ( ; idx < smartlist_len(lst); ++idx) {
+    circuit_t *circ = smartlist_get(lst, idx);
+
+    /* Ignore a marked for close circuit or purpose not matching a service
+     * intro point or if the state is not open. */
+    if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN ||
+        (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND &&
+         circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) {
+      continue;
+    }
+    /* The purposes we are looking for are only for origin circuits so the
+     * following is valid. */
+    return TO_ORIGIN_CIRCUIT(circ);
+  }
+  /* Not found. */
+  return NULL;
+}
+
 /** Return the first circuit originating here in global_circuitlist after
  * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if
  * set) matches the private key digest of the rend data associated with the
diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h
index 2f7625256..048cd5f76 100644
--- a/src/or/circuitlist.h
+++ b/src/or/circuitlist.h
@@ -48,6 +48,7 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data(
 origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
                                      const uint8_t *digest, uint8_t purpose);
 origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start);
+origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start);
 origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start);
 origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose,
                                               extend_info_t *info, int flags);
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index a3b7066b1..af061527d 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -1280,11 +1280,6 @@ circuit_build_needed_circs(time_t now)
   if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
     connection_ap_rescan_and_attach_pending();
 
-  /* make sure any hidden services have enough intro points
-   * HS intro point streams only require an internal circuit */
-  if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
-    rend_consider_services_intro_points();
-
   circuit_expire_old_circs_as_needed(now);
 
   if (!options->DisablePredictedCircuits)
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
index 7908cd961..5a230759a 100644
--- a/src/or/hs_descriptor.c
+++ b/src/or/hs_descriptor.c
@@ -58,6 +58,7 @@
 #include "hs_descriptor.h"
 
 #include "or.h"
+#include "circuitbuild.h"
 #include "ed25519_cert.h" /* Trunnel interface. */
 #include "parsecommon.h"
 #include "rendcache.h"
@@ -362,6 +363,14 @@ encode_link_specifiers(const smartlist_t *specs)
       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(0);
     }
@@ -1143,6 +1152,15 @@ decode_link_specifiers(const char *encoded)
       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:
       goto err;
     }
@@ -2398,7 +2416,7 @@ hs_desc_intro_point_free(hs_desc_intro_point_t *ip)
   }
   if (ip->link_specifiers) {
     SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
-                      ls, tor_free(ls));
+                      ls, hs_desc_link_specifier_free(ls));
     smartlist_free(ip->link_specifiers);
   }
   tor_cert_free(ip->auth_key_cert);
@@ -2408,3 +2426,73 @@ hs_desc_intro_point_free(hs_desc_intro_point_t *ip)
   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:
+    memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id));
+    break;
+  case LS_ED25519_ID:
+    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_free_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);
+  }
+}
+
diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h
index 80ddf8978..2f1b0160e 100644
--- a/src/or/hs_descriptor.h
+++ b/src/or/hs_descriptor.h
@@ -23,6 +23,9 @@
 /* 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)
@@ -65,12 +68,14 @@ typedef struct hs_desc_link_specifier_t {
    * specification. */
   uint8_t type;
 
-  /* It's either an address/port or a legacy identity fingerprint. */
+  /* 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;
 
@@ -201,6 +206,11 @@ void hs_descriptor_free(hs_descriptor_t *desc);
 void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc);
 void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc);
 
+void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls);
+hs_desc_link_specifier_t *hs_desc_link_specifier_new(
+                                  const extend_info_t *info, uint8_t type);
+void hs_descriptor_free_intro_points(hs_descriptor_t *desc);
+
 int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
                               const ed25519_keypair_t *signing_kp,
                               char **encoded_out);
diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c
index 2abbfcd6c..25c43b3ad 100644
--- a/src/or/hs_intropoint.c
+++ b/src/or/hs_intropoint.c
@@ -24,6 +24,7 @@
 #include "hs/cell_introduce1.h"
 
 #include "hs_circuitmap.h"
+#include "hs_descriptor.h"
 #include "hs_intropoint.h"
 #include "hs_common.h"
 
@@ -594,3 +595,16 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
   return -1;
 }
 
+/* Free the given intropoint object ip. */
+void
+hs_intro_free_content(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);
+}
+
diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h
index bfb1331ba..2e11de1b5 100644
--- a/src/or/hs_intropoint.h
+++ b/src/or/hs_intropoint.h
@@ -13,11 +13,11 @@
 #include "torcert.h"
 
 /* Authentication key type in an ESTABLISH_INTRO cell. */
-enum hs_intro_auth_key_type {
+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 {
@@ -30,6 +30,9 @@ typedef enum {
 /* 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. */
@@ -47,6 +50,9 @@ 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_intro_free_content(hs_intropoint_t *ip);
+
 #ifdef HS_INTROPOINT_PRIVATE
 
 #include "hs/cell_establish_intro.h"
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 9114655ed..3cde2fe1b 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -10,6 +10,7 @@
 
 #include "or.h"
 #include "circpathbias.h"
+#include "circuitbuild.h"
 #include "circuitlist.h"
 #include "config.h"
 #include "networkstatus.h"
@@ -18,12 +19,18 @@
 #include "rendservice.h"
 #include "router.h"
 #include "routerkeys.h"
+#include "routerlist.h"
 
 #include "hs_common.h"
 #include "hs_config.h"
+#include "hs_circuit.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
 #include "hs_intropoint.h"
 #include "hs_service.h"
 
+/* Trunnel */
+#include "ed25519_cert.h"
 #include "hs/cell_establish_intro.h"
 #include "hs/cell_common.h"
 
@@ -36,6 +43,19 @@
       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";
@@ -213,13 +233,209 @@ service_free_all(void)
   }
 }
 
+/* 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_intro_free_content(&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 *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. */
+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);
+
+  /* XXX: These will be controlled by consensus params. (#20961) */
+  ip->introduce2_max =
+    crypto_rand_int_range(INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS,
+                          INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS);
+  ip->time_to_expire = time(NULL) +
+    crypto_rand_int_range(INTRO_POINT_LIFETIME_MIN_SECONDS,
+                          INTRO_POINT_LIFETIME_MAX_SECONDS);
+  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 (ei == NULL) {
+    goto done;
+  }
+
+  /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are
+   * mandatory. */
+  ls = hs_desc_link_specifier_new(ei, LS_IPV4);
+  /* It is impossible to have an extend info object without a v4. */
+  tor_assert(ls);
+  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. */
+  tor_assert(ls);
+  smartlist_add(ip->base.link_specifiers, ls);
+  ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID);
+  /* It is impossible to have an extend info object without an ed25519
+   * identity key. */
+  tor_assert(ls);
+  smartlist_add(ip->base.link_specifiers, ls);
+  /* IPv6 is optional. */
+  ls = hs_desc_link_specifier_new(ei, LS_IPV6);
+  if (ls) {
+    smartlist_add(ip->base.link_specifiers, ls);
+  }
+
+  /* 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)
+{
+  uint8_t key[DIGEST256_LEN] = {0};
+
+  tor_assert(map);
+  tor_assert(ip);
+
+  memcpy(key, ip->auth_key_kp.pubkey.pubkey, sizeof(key));
+  digest256map_set(map, key, 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);
+  /* Legacy ID is mandatory for an intro point object to have. */
+  tor_assert(ls);
+  /* XXX In the future, we want to only use the ed25519 ID (#22173). */
+  return node_get_by_id((const char *) ls->u.legacy_id);
+}
+
+/* Return an introduction point circuit matching the given intro point object.
+ * NULL is returned is no such circuit can be found. */
+static origin_circuit_t *
+get_intro_circuit(const hs_service_intro_point_t *ip)
+{
+  origin_circuit_t *circ = NULL;
+
+  tor_assert(ip);
+
+  if (ip->base.is_only_legacy) {
+    uint8_t digest[DIGEST_LEN];
+    if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+      goto end;
+    }
+    circ = hs_circuitmap_get_intro_circ_v2_service_side(digest);
+  } else {
+    circ = hs_circuitmap_get_intro_circ_v3_service_side(
+                                        &ip->auth_key_kp.pubkey);
+  }
+ end:
+  return circ;
+}
+
 /* 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);
-  /* XXX: To implement. */
-  return;
+
+  /* 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. */
@@ -230,13 +446,11 @@ close_intro_circuits(hs_service_intropoints_t *intro_points)
 
   DIGEST256MAP_FOREACH(intro_points->map, key,
                        const hs_service_intro_point_t *, ip) {
-    origin_circuit_t *ocirc =
-      hs_circuitmap_get_intro_circ_v3_service_side(
-                                      &ip->auth_key_kp.pubkey);
+    origin_circuit_t *ocirc = get_intro_circuit(ip);
     if (ocirc) {
-      hs_circuitmap_remove_circuit(TO_CIRCUIT(ocirc));
       /* Reason is FINISHED because service has been removed and thus the
-       * circuit is considered old/uneeded. */
+       * 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;
@@ -248,12 +462,9 @@ close_service_intro_circuits(hs_service_t *service)
 {
   tor_assert(service);
 
-  if (service->desc_current) {
-    close_intro_circuits(&service->desc_current->intro_points);
-  }
-  if (service->desc_next) {
-    close_intro_circuits(&service->desc_next->intro_points);
-  }
+  FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+    close_intro_circuits(&desc->intro_points);
+  } FOR_EACH_DESCRIPTOR_END;
 }
 
 /* Close any circuits related to the given service. */
@@ -281,7 +492,7 @@ move_descriptor_intro_points(hs_service_descriptor_t *src,
   tor_assert(src);
   tor_assert(dst);
 
-  /* XXX: Free dst introduction points. */
+  digest256map_free(dst->intro_points.map, service_intro_point_free_);
   dst->intro_points.map = src->intro_points.map;
   /* Nullify the source. */
   src->intro_points.map = NULL;
@@ -295,7 +506,6 @@ move_intro_points(hs_service_t *src, hs_service_t *dst)
   tor_assert(src);
   tor_assert(dst);
 
-  /* Cleanup destination. */
   if (src->desc_current && dst->desc_current) {
     move_descriptor_intro_points(src->desc_current, dst->desc_current);
   }
@@ -351,7 +561,6 @@ static void
 register_all_services(void)
 {
   struct hs_service_ht *new_service_map;
-  hs_service_t *s, **iter;
 
   tor_assert(hs_service_staging_list);
 
@@ -371,6 +580,8 @@ register_all_services(void)
   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);
@@ -398,9 +609,9 @@ register_all_services(void)
 
   /* Close any circuits associated with the non surviving services. Every
    * service in the current global map are roaming. */
-  HT_FOREACH(iter, hs_service_ht, hs_service_map) {
-    close_service_circuits(*iter);
-  }
+  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. */
@@ -522,13 +733,673 @@ load_service_keys(hs_service_t *service)
   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_);
+  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();
+  return sdesc;
+}
+
+#if 0
+/* 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 *dup = tor_malloc_zero(sizeof(*dup));
+    link_specifier_copy(dup, ls);
+    smartlist_add(desc_ip->link_specifiers, dup);
+  } 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_free_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;
+}
+
+#endif /* build_desc_intro_points is disabled because not used */
+
+/* Populate the descriptor encrypted section fomr 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;
+}
+
+/* Populare 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();
+
+  /* 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;
+  }
+
+  /* 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;
+  return;
+
+ err:
+  service_descriptor_free(desc);
+}
+
+/* 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) {
+    if (service->desc_current == NULL) {
+      /* This means we just booted up because else this descriptor will never
+       * be NULL as it should always point to the descriptor that was in
+       * desc_next after rotation. */
+      build_service_descriptor(service, now, hs_get_time_period_num(now),
+                               &service->desc_current);
+
+      log_info(LD_REND, "Hidden service %s current descriptor successfully "
+                        "built. Now scheduled for upload.",
+               safe_str_client(service->onion_address));
+    }
+    /* A next descriptor to NULL indicate that we need to build a fresh one if
+     * we are in the overlap period for the _next_ time period since it means
+     * we either just booted or we just rotated our descriptors. */
+    if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) {
+      build_service_descriptor(service, now, hs_get_next_time_period_num(now),
+                               &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 to directly connect to and if not,
+ * fallback to a normal 3-hop node. Return a newly allocated service intro
+ * point ready to be used for encoding. 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);
+  if (node == NULL && direct_conn) {
+    /* Unable to find a node for direct connection, let's fall back to a
+     * normal 3-hop node. */
+    node = router_choose_random_node(exclude_nodes,
+                                     get_options()->ExcludeNodes, flags);
+  }
+  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. */
+  info = extend_info_from_node(node, direct_conn);
+  if (BUG(info == NULL)) {
+    goto err;
+  }
+  /* 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;
+  }
+  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 NUM_INTRO_POINTS_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) {
+    /* XXX: Should a consensus param control that value? */
+    num_needed_ip += NUM_INTRO_POINTS_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) {
+    smartlist_add(exclude_nodes, (void *) get_node_from_intro_point(ip));
+  } DIGEST256MAP_FOREACH_END;
+
+  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);
+  }
+
+  /* Success. */
+ done:
+  /* We don't have ownership of the node_t object in this list. */
+  smartlist_free(exclude_nodes);
+  return i;
+}
+
+/* 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. 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,
+               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. */
+      desc->next_upload_time = now;
+    }
+  }
+}
+
+/* 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)
+{
+  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);
+      origin_circuit_t *ocirc = get_intro_circuit(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 ||
+          (ocirc == NULL &&
+           ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES)) {
+        MAP_DEL_CURRENT(key);
+        service_intro_point_free(ip);
+        /* XXX: Legacy code does NOT do that, 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?). */
+        if (ocirc) {
+          /* After this, no new cells will be handled on the circuit. */
+          circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+        }
+        continue;
+      }
+      if (ocirc == NULL) {
+        /* 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;
+      }
+    } DIGEST256MAP_FOREACH_END;
+  } FOR_EACH_DESCRIPTOR_END;
+}
+
+/** We just entered overlap period and we need to rotate our <b>service</b>
+ *  descriptors */
+static void
+rotate_service_descriptors(hs_service_t *service)
+{
+  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;
+}
+
+/* Rotate descriptors for each service if needed. If we are just entering
+ * the overlap period, rotate them that is point the previous descriptor to
+ * the current and cleanup the previous one. A non existing current
+ * descriptor will trigger a descriptor build for the next time period. */
+static void
+rotate_all_descriptors(time_t now)
+{
+  FOR_EACH_SERVICE_BEGIN(service) {
+    /* We are _not_ in the overlap period so skip rotation. */
+    if (!hs_overlap_mode_is_active(NULL, now)) {
+      service->state.in_overlap_period = 0;
+      continue;
+    }
+    /* We've entered the overlap period already so skip rotation. */
+    if (service->state.in_overlap_period) {
+      continue;
+    }
+    /* It's the first time the service encounters the overlap period so flag
+     * it in order to make sure we don't rotate at next check. */
+    service->state.in_overlap_period = 1;
+
+    /* If we have a next descriptor lined up, rotate the descriptors so that it
+     * becomes current. */
+    if (service->desc_next) {
+      rotate_service_descriptors(service);
+    }
+    log_info(LD_REND, "We've just entered the overlap period. Service %s "
+                      "descriptors have been rotated!",
+             safe_str_client(service->onion_address));
+  } 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)
 {
-  (void) now;
+  /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are
+   * simply moving things around or removing uneeded elements. */
+
+  FOR_EACH_SERVICE_BEGIN(service) {
+    /* Cleanup invalid intro points from the service descriptor. */
+    cleanup_intro_points(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
@@ -537,14 +1408,21 @@ run_housekeeping_event(time_t now)
 static void
 run_build_descriptor_event(time_t now)
 {
-  (void) now;
   /* For v2 services, this step happens in the upload event. */
 
   /* Run v3+ events. */
-  FOR_EACH_SERVICE_BEGIN(service) {
-    /* XXX: Actually build descriptors. */
-    (void) service;
-  } FOR_EACH_SERVICE_END;
+  /* 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);
 }
 
 /* Scheduled event run from the main loop. Make sure we have all the circuits
@@ -552,8 +1430,6 @@ run_build_descriptor_event(time_t now)
 static void
 run_build_circuit_event(time_t now)
 {
-  (void) now;
-
   /* Make sure we can actually have enough information to build internal
    * circuits as required by services. */
   if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
@@ -562,11 +1438,14 @@ run_build_circuit_event(time_t now)
 
   /* Run v2 check. */
   if (num_rend_services() > 0) {
-    rend_consider_services_intro_points();
+    rend_consider_services_intro_points(now);
   }
+
   /* Run v3+ check. */
   FOR_EACH_SERVICE_BEGIN(service) {
     /* XXX: Check every service for validity of circuits. */
+    /* XXX: Make sure we have a retry period so we don't stress circuit
+     * creation. */
     (void) service;
   } FOR_EACH_SERVICE_END;
 }
@@ -672,27 +1551,10 @@ hs_service_free(hs_service_t *service)
     return;
   }
 
-  /* Free descriptors. */
-  if (service->desc_current) {
-    hs_descriptor_free(service->desc_current->desc);
-    /* Wipe keys. */
-    memwipe(&service->desc_current->signing_kp, 0,
-            sizeof(service->desc_current->signing_kp));
-    memwipe(&service->desc_current->blinded_kp, 0,
-            sizeof(service->desc_current->blinded_kp));
-    /* XXX: Free intro points. */
-    tor_free(service->desc_current);
-  }
-  if (service->desc_next) {
-    hs_descriptor_free(service->desc_next->desc);
-    /* Wipe keys. */
-    memwipe(&service->desc_next->signing_kp, 0,
-            sizeof(service->desc_next->signing_kp));
-    memwipe(&service->desc_next->blinded_kp, 0,
-            sizeof(service->desc_next->blinded_kp));
-    /* XXX: Free intro points. */
-    tor_free(service->desc_next);
-  }
+  /* 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);
diff --git a/src/or/hs_service.h b/src/or/hs_service.h
index 493329e22..476fee72f 100644
--- a/src/or/hs_service.h
+++ b/src/or/hs_service.h
@@ -38,8 +38,12 @@ typedef struct hs_service_intro_point_t {
    * which is published in the descriptor. */
   ed25519_keypair_t auth_key_kp;
 
-  /* Encryption private key. */
-  curve25519_secret_key_t enc_key_sk;
+  /* 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;
 
   /* Amount of INTRODUCE2 cell accepted from this intro point. */
   uint64_t introduce2_count;
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index 98ed1100e..4641e110d 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -3983,10 +3983,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted)
  * This is called once a second by the main loop.
  */
 void
-rend_consider_services_intro_points(void)
+rend_consider_services_intro_points(time_t now)
 {
   int i;
-  time_t now;
   const or_options_t *options = get_options();
   /* Are we in single onion mode? */
   const int allow_direct = rend_service_allow_non_anonymous_connection(
@@ -4003,7 +4002,6 @@ rend_consider_services_intro_points(void)
 
   exclude_nodes = smartlist_new();
   retry_nodes = smartlist_new();
-  now = time(NULL);
 
   SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) {
     int r;
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index ffed21d14..4a06657ea 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -149,7 +149,7 @@ void rend_service_free_staging_list(void);
 int rend_service_load_all_keys(const smartlist_t *service_list);
 void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
                                           smartlist_t *stat_lst);
-void rend_consider_services_intro_points(void);
+void rend_consider_services_intro_points(time_t now);
 void rend_consider_services_upload(time_t now);
 void rend_hsdir_routers_changed(void);
 void rend_consider_descriptor_republication(void);





More information about the tor-commits mailing list