Browse Source

Merge branch 'ticket20657_nickm_bugfixes_squashed'

Nick Mathewson 6 years ago
parent
commit
34e4122025
63 changed files with 8509 additions and 1062 deletions
  1. 1 7
      src/ext/ed25519/donna/ed25519_tor.c
  2. 2 2
      src/ext/ed25519/ref10/blinding.c
  3. 43 0
      src/or/circuitlist.c
  4. 1 0
      src/or/circuitlist.h
  5. 32 16
      src/or/circuituse.c
  6. 2 1
      src/or/circuituse.h
  7. 86 51
      src/or/connection_edge.c
  8. 90 3
      src/or/directory.c
  9. 4 0
      src/or/directory.h
  10. 4 2
      src/or/hs_cache.c
  11. 584 0
      src/or/hs_cell.c
  12. 75 0
      src/or/hs_cell.h
  13. 832 0
      src/or/hs_circuit.c
  14. 34 0
      src/or/hs_circuit.h
  15. 813 7
      src/or/hs_common.c
  16. 119 3
      src/or/hs_common.h
  17. 222 30
      src/or/hs_descriptor.c
  18. 19 3
      src/or/hs_descriptor.h
  19. 10 3
      src/or/hs_ident.c
  20. 15 9
      src/or/hs_ident.h
  21. 16 0
      src/or/hs_intropoint.c
  22. 8 2
      src/or/hs_intropoint.h
  23. 2502 247
      src/or/hs_service.c
  24. 109 17
      src/or/hs_service.h
  25. 3 1
      src/or/include.am
  26. 26 7
      src/or/main.c
  27. 11 4
      src/or/networkstatus.c
  28. 1 0
      src/or/networkstatus.h
  29. 95 0
      src/or/nodelist.c
  30. 17 3
      src/or/or.h
  31. 1 1
      src/or/parsecommon.c
  32. 2 1
      src/or/parsecommon.h
  33. 2 2
      src/or/rendcommon.c
  34. 12 159
      src/or/rendservice.c
  35. 8 9
      src/or/rendservice.h
  36. 8 1
      src/or/routerparse.c
  37. 46 0
      src/or/shared_random.c
  38. 3 0
      src/or/shared_random.h
  39. 37 1
      src/or/shared_random_state.c
  40. 5 0
      src/or/shared_random_state.h
  41. 2 0
      src/or/statefile.c
  42. 2 4
      src/test/ed25519_exts_ref.py
  43. 16 16
      src/test/ed25519_vectors.inc
  44. 24 6
      src/test/hs_test_helpers.c
  45. 3 0
      src/test/hs_test_helpers.h
  46. 3 0
      src/test/include.am
  47. 3 0
      src/test/test.c
  48. 3 0
      src/test/test.h
  49. 6 2
      src/test/test_hs_cache.c
  50. 130 0
      src/test/test_hs_cell.c
  51. 507 0
      src/test/test_hs_common.c
  52. 1 1
      src/test/test_hs_config.c
  53. 12 6
      src/test/test_hs_descriptor.c
  54. 163 134
      src/test/test_hs_intropoint.c
  55. 114 0
      src/test/test_hs_ntor.c
  56. 1060 301
      src/test/test_hs_service.c
  57. 120 0
      src/test/test_shared_random.c
  58. 4 0
      src/trunnel/ed25519_cert.h
  59. 6 0
      src/trunnel/ed25519_cert.trunnel
  60. 292 0
      src/trunnel/hs/cell_rendezvous.c
  61. 118 0
      src/trunnel/hs/cell_rendezvous.h
  62. 18 0
      src/trunnel/hs/cell_rendezvous.trunnel
  63. 2 0
      src/trunnel/include.am

+ 1 - 7
src/ext/ed25519/donna/ed25519_tor.c

@@ -245,13 +245,7 @@ ed25519_donna_sign(unsigned char *sig, const unsigned char *m, size_t mlen,
 static void
 ed25519_donna_gettweak(unsigned char *out, const unsigned char *param)
 {
-  static const char str[] = "Derive temporary signing key";
-  ed25519_hash_context ctx;
-
-  ed25519_hash_init(&ctx);
-  ed25519_hash_update(&ctx, (const unsigned char*)str, strlen(str));
-  ed25519_hash_update(&ctx, param, 32);
-  ed25519_hash_final(&ctx, out);
+  memcpy(out, param, 32);
 
   out[0] &= 248;  /* Is this necessary ? */
   out[31] &= 63;

+ 2 - 2
src/ext/ed25519/ref10/blinding.c

@@ -12,8 +12,8 @@
 static void
 ed25519_ref10_gettweak(unsigned char *out, const unsigned char *param)
 {
-  const char str[] = "Derive temporary signing key";
-  crypto_hash_sha512_2(out, (const unsigned char*)str, strlen(str), param, 32);
+  memcpy(out, param, 32);
+
   out[0] &= 248;  /* Is this necessary necessary ? */
   out[31] &= 63;
   out[31] |= 64;

+ 43 - 0
src/or/circuitlist.c

@@ -65,6 +65,7 @@
 #include "control.h"
 #include "entrynodes.h"
 #include "main.h"
+#include "hs_circuit.h"
 #include "hs_circuitmap.h"
 #include "hs_common.h"
 #include "hs_ident.h"
@@ -1532,6 +1533,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
@@ -1913,6 +1949,13 @@ circuit_about_to_free(circuit_t *circ)
      orig_reason);
   }
 
+  /* Notify the HS subsystem for any 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));
+  }
+
   if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
     origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
     int timed_out = (reason == END_CIRC_REASON_TIMEOUT);

+ 1 - 0
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);

+ 32 - 16
src/or/circuituse.c

@@ -43,6 +43,7 @@
 #include "entrynodes.h"
 #include "hs_common.h"
 #include "hs_client.h"
+#include "hs_circuit.h"
 #include "hs_ident.h"
 #include "nodelist.h"
 #include "networkstatus.h"
@@ -782,7 +783,7 @@ circuit_expire_building(void)
                victim->state, circuit_state_to_string(victim->state),
                victim->purpose);
       TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
-      rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim));
+      hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim));
       continue;
     }
 
@@ -1113,11 +1114,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
 /* Return true if we need any more hidden service server circuits.
  * HS servers only need an internal circuit. */
 STATIC int
-needs_hs_server_circuits(int num_uptime_internal)
+needs_hs_server_circuits(time_t now, int num_uptime_internal)
 {
-  return (num_rend_services() &&
-          num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS &&
-          router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+  if (!rend_num_services() && !hs_service_get_num_services()) {
+    /* No services, we don't need anything. */
+    goto no_need;
+  }
+
+  if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) {
+    /* We have sufficient amount of internal circuit. */
+    goto no_need;
+  }
+
+  if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
+    /* Consensus hasn't been checked or might be invalid so requesting
+     * internal circuits is not wise. */
+    goto no_need;
+  }
+
+  /* At this point, we need a certain amount of circuits and we will most
+   * likely use them for rendezvous so we note down the use of internal
+   * circuit for our prediction for circuit needing uptime and capacity. */
+  rep_hist_note_used_internal(now, 1, 1);
+
+  return 1;
+ no_need:
+  return 0;
 }
 
 /* We need at least this many internal circuits for hidden service clients */
@@ -1216,7 +1238,7 @@ circuit_predict_and_launch_new(void)
     return;
   }
 
-  if (needs_hs_server_circuits(num_uptime_internal)) {
+  if (needs_hs_server_circuits(now, num_uptime_internal)) {
     flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
              CIRCLAUNCH_IS_INTERNAL);
 
@@ -1280,11 +1302,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)
@@ -1366,8 +1383,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
        * number of streams on the circuit associated with the rend service.
        */
       if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
-        tor_assert(origin_circ->rend_data);
-        origin_circ->rend_data->nr_streams--;
+        hs_dec_rdv_stream_counter(origin_circ);
       }
       return;
     }
@@ -1641,11 +1657,11 @@ circuit_has_opened(origin_circuit_t *circ)
       break;
     case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
       /* at the service, waiting for introductions */
-      rend_service_intro_has_opened(circ);
+      hs_service_circuit_has_opened(circ);
       break;
     case CIRCUIT_PURPOSE_S_CONNECT_REND:
       /* at the service, connecting to rend point */
-      rend_service_rendezvous_has_opened(circ);
+      hs_service_circuit_has_opened(circ);
       break;
     case CIRCUIT_PURPOSE_TESTING:
       circuit_testing_opened(circ);
@@ -1795,7 +1811,7 @@ circuit_build_failed(origin_circuit_t *circ)
                "(%s hop failed).",
                escaped(build_state_get_exit_nickname(circ->build_state)),
                failed_at_last_hop?"last":"non-last");
-      rend_service_relaunch_rendezvous(circ);
+      hs_circ_retry_service_rendezvous_point(circ);
       break;
     /* default:
      * This won't happen in normal operation, but might happen if the

+ 2 - 1
src/or/circuituse.h

@@ -68,7 +68,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ);
 STATIC int needs_exit_circuits(time_t now,
                                int *port_needs_uptime,
                                int *port_needs_capacity);
-STATIC int needs_hs_server_circuits(int num_uptime_internal);
+STATIC int needs_hs_server_circuits(time_t now,
+                                    int num_uptime_internal);
 
 STATIC int needs_hs_client_circuits(time_t now,
                                     int *needs_uptime,

+ 86 - 51
src/or/connection_edge.c

@@ -76,6 +76,7 @@
 #include "dirserv.h"
 #include "hibernate.h"
 #include "hs_common.h"
+#include "hs_circuit.h"
 #include "main.h"
 #include "nodelist.h"
 #include "policies.h"
@@ -3066,6 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
   return 0;
 }
 
+/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the
+ * connection, attach it to the circ and connect it. Return 0 on success
+ * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port
+ * where the caller should close the circuit. */
+static int
+handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
+{
+  int ret;
+  origin_circuit_t *origin_circ;
+
+  assert_circuit_ok(circ);
+  tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+  tor_assert(conn);
+
+  log_debug(LD_REND, "Connecting the hidden service rendezvous circuit "
+                     "to the service destination.");
+
+  origin_circ = TO_ORIGIN_CIRCUIT(circ);
+  conn->base_.address = tor_strdup("(rendezvous)");
+  conn->base_.state = EXIT_CONN_STATE_CONNECTING;
+
+  /* The circuit either has an hs identifier for v3+ or a rend_data for legacy
+   * service. */
+  if (origin_circ->rend_data) {
+    conn->rend_data = rend_data_dup(origin_circ->rend_data);
+    tor_assert(connection_edge_is_rendezvous_stream(conn));
+    ret = rend_service_set_connection_addr_port(conn, origin_circ);
+  } else if (origin_circ->hs_ident) {
+    /* Setup the identifier to be the one for the circuit service. */
+    conn->hs_ident =
+      hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk);
+    tor_assert(connection_edge_is_rendezvous_stream(conn));
+    ret = hs_service_set_conn_addr_port(origin_circ, conn);
+  } else {
+    /* We should never get here if the circuit's purpose is rendezvous. */
+    tor_assert_nonfatal_unreached();
+    return -1;
+  }
+  if (ret < 0) {
+    log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)",
+             fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port);
+    /* Send back reason DONE because we want to make hidden service port
+     * scanning harder thus instead of returning that the exit policy
+     * didn't match, which makes it obvious that the port is closed,
+     * return DONE and kill the circuit. That way, a user (malicious or
+     * not) needs one circuit per bad port unless it matches the policy of
+     * the hidden service. */
+    relay_send_end_cell_from_edge(conn->stream_id, circ,
+                                  END_STREAM_REASON_DONE,
+                                  origin_circ->cpath->prev);
+    connection_free(TO_CONN(conn));
+
+    /* Drop the circuit here since it might be someone deliberately
+     * scanning the hidden service ports. Note that this mitigates port
+     * scanning by adding more work on the attacker side to successfully
+     * scan but does not fully solve it. */
+    if (ret < -1) {
+      return END_CIRC_AT_ORIGIN;
+    } else {
+      return 0;
+    }
+  }
+
+  /* Link the circuit and the connection crypt path. */
+  conn->cpath_layer = origin_circ->cpath->prev;
+
+  /* Add it into the linked list of p_streams on this circuit */
+  conn->next_stream = origin_circ->p_streams;
+  origin_circ->p_streams = conn;
+  conn->on_circuit = circ;
+  assert_circuit_ok(circ);
+
+  hs_inc_rdv_stream_counter(origin_circ);
+
+  /* Connect tor to the hidden service destination. */
+  connection_exit_connect(conn);
+
+  /* For path bias: This circuit was used successfully */
+  pathbias_mark_use_success(origin_circ);
+  return 0;
+}
+
 /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are
  * an exit hop for the circuit, or we are the origin and it is a
  * rendezvous begin.
@@ -3217,58 +3300,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   n_stream->deliver_window = STREAMWINDOW_START;
 
   if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
-    tor_assert(origin_circ);
-    log_info(LD_REND,"begin is for rendezvous. configuring stream.");
-    n_stream->base_.address = tor_strdup("(rendezvous)");
-    n_stream->base_.state = EXIT_CONN_STATE_CONNECTING;
-    n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
-    tor_assert(connection_edge_is_rendezvous_stream(n_stream));
-    assert_circuit_ok(circ);
-
-    const int r = rend_service_set_connection_addr_port(n_stream, origin_circ);
-    if (r < 0) {
-      log_info(LD_REND,"Didn't find rendezvous service (port %d)",
-               n_stream->base_.port);
-      /* Send back reason DONE because we want to make hidden service port
-       * scanning harder thus instead of returning that the exit policy
-       * didn't match, which makes it obvious that the port is closed,
-       * return DONE and kill the circuit. That way, a user (malicious or
-       * not) needs one circuit per bad port unless it matches the policy of
-       * the hidden service. */
-      relay_send_end_cell_from_edge(rh.stream_id, circ,
-                                    END_STREAM_REASON_DONE,
-                                    layer_hint);
-      connection_free(TO_CONN(n_stream));
-      tor_free(address);
-
-      /* Drop the circuit here since it might be someone deliberately
-       * scanning the hidden service ports. Note that this mitigates port
-       * scanning by adding more work on the attacker side to successfully
-       * scan but does not fully solve it. */
-      if (r < -1)
-        return END_CIRC_AT_ORIGIN;
-      else
-        return 0;
-    }
-    assert_circuit_ok(circ);
-    log_debug(LD_REND,"Finished assigning addr/port");
-    n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */
-
-    /* add it into the linked list of p_streams on this circuit */
-    n_stream->next_stream = origin_circ->p_streams;
-    n_stream->on_circuit = circ;
-    origin_circ->p_streams = n_stream;
-    assert_circuit_ok(circ);
-
-    origin_circ->rend_data->nr_streams++;
-
-    connection_exit_connect(n_stream);
-
-    /* For path bias: This circuit was used successfully */
-    pathbias_mark_use_success(origin_circ);
-
     tor_free(address);
-    return 0;
+    /* We handle this circuit and stream in this function for all supported
+     * hidden service version. */
+    return handle_hs_exit_conn(circ, n_stream);
   }
   tor_strlower(address);
   n_stream->base_.address = address;

+ 90 - 3
src/or/directory.c

@@ -186,6 +186,8 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
     case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
     case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
+    case DIR_PURPOSE_FETCH_HSDESC:
+    case DIR_PURPOSE_UPLOAD_HSDESC:
       return 1;
     case DIR_PURPOSE_SERVER:
     default:
@@ -244,6 +246,10 @@ dir_conn_purpose_to_string(int purpose)
       return "hidden-service v2 descriptor fetch";
     case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
       return "hidden-service v2 descriptor upload";
+    case DIR_PURPOSE_FETCH_HSDESC:
+      return "hidden-service descriptor fetch";
+    case DIR_PURPOSE_UPLOAD_HSDESC:
+      return "hidden-service descriptor upload";
     case DIR_PURPOSE_FETCH_MICRODESC:
       return "microdescriptor fetch";
     }
@@ -1034,11 +1040,12 @@ struct directory_request_t {
   size_t payload_len;
   /** Value to send in an if-modified-since header, or 0 for none. */
   time_t if_modified_since;
-  /** Hidden-service-specific information */
+  /** Hidden-service-specific information v2. */
   const rend_data_t *rend_query;
   /** Extra headers to append to the request */
   config_line_t *additional_headers;
-  /** */
+  /** Hidden-service-specific information for v3+. */
+  const hs_ident_dir_conn_t *hs_ident;
   /** Used internally to directory.c: gets informed when the attempt to
    * connect to the directory succeeds or fails, if that attempt bears on the
    * directory's usability as a directory guard. */
@@ -1268,6 +1275,20 @@ directory_request_set_rend_query(directory_request_t *req,
   }
   req->rend_query = query;
 }
+/**
+ * Set an object containing HS connection identifier to be associated with
+ * this request. Note that only an alias to <b>ident</b> is stored, so the
+ * <b>ident</b> object must outlive the request.
+ */
+void
+directory_request_upload_set_hs_ident(directory_request_t *req,
+                                      const hs_ident_dir_conn_t *ident)
+{
+  if (ident) {
+    tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+  }
+  req->hs_ident = ident;
+}
 /** Set a static circuit_guard_state_t object to affliate with the request in
  * <b>req</b>.  This object will receive notification when the attempt to
  * connect to the guard either succeeds or fails. */
@@ -1389,6 +1410,7 @@ directory_initiate_request,(directory_request_t *request))
   const dir_indirection_t indirection = request->indirection;
   const char *resource = request->resource;
   const rend_data_t *rend_query = request->rend_query;
+  const hs_ident_dir_conn_t *hs_ident = request->hs_ident;
   circuit_guard_state_t *guard_state = request->guard_state;
 
   tor_assert(or_addr_port->port || dir_addr_port->port);
@@ -1476,8 +1498,16 @@ directory_initiate_request,(directory_request_t *request))
   conn->dirconn_direct = !anonymized_connection;
 
   /* copy rendezvous data, if any */
-  if (rend_query)
+  if (rend_query) {
+    /* We can't have both v2 and v3+ identifier. */
+    tor_assert_nonfatal(!hs_ident);
     conn->rend_data = rend_data_dup(rend_query);
+  }
+  if (hs_ident) {
+    /* We can't have both v2 and v3+ identifier. */
+    tor_assert_nonfatal(!rend_query);
+    conn->hs_ident = hs_ident_dir_conn_dup(hs_ident);
+  }
 
   if (!anonymized_connection && !use_begindir) {
     /* then we want to connect to dirport directly */
@@ -1835,6 +1865,12 @@ directory_send_command(dir_connection_t *conn,
       httpcommand = "POST";
       url = tor_strdup("/tor/rendezvous2/publish");
       break;
+    case DIR_PURPOSE_UPLOAD_HSDESC:
+      tor_assert(resource);
+      tor_assert(payload);
+      httpcommand = "POST";
+      tor_asprintf(&url, "/tor/hs/%s/publish", resource);
+      break;
     default:
       tor_assert(0);
       return;
@@ -2189,6 +2225,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *,
                                              const response_handler_args_t *);
 static int handle_response_upload_renddesc_v2(dir_connection_t *,
                                               const response_handler_args_t *);
+static int handle_response_upload_hsdesc(dir_connection_t *,
+                                         const response_handler_args_t *);
 
 static int
 dir_client_decompress_response_body(char **bodyp, size_t *bodylenp,
@@ -2489,6 +2527,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
     case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
       rv = handle_response_upload_renddesc_v2(conn, &args);
       break;
+    case DIR_PURPOSE_UPLOAD_HSDESC:
+      rv = handle_response_upload_hsdesc(conn, &args);
+      break;
     default:
       tor_assert_nonfatal_unreached();
       rv = -1;
@@ -3180,6 +3221,52 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn,
   return 0;
 }
 
+/**
+ * Handler function: processes a response to a POST request to upload an
+ * hidden service descriptor.
+ **/
+static int
+handle_response_upload_hsdesc(dir_connection_t *conn,
+                              const response_handler_args_t *args)
+{
+  const int status_code = args->status_code;
+  const char *reason = args->reason;
+
+  tor_assert(conn);
+  tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC);
+
+  log_info(LD_REND, "Uploaded hidden service descriptor (status %d "
+                    "(%s))",
+           status_code, escaped(reason));
+  /* For this directory response, it MUST have an hidden service identifier on
+   * this connection. */
+  tor_assert(conn->hs_ident);
+  switch (status_code) {
+  case 200:
+    log_info(LD_REND, "Uploading hidden service descriptor: "
+                      "finished with status 200 (%s)", escaped(reason));
+    /* XXX: Trigger control event. */
+    break;
+  case 400:
+    log_warn(LD_REND, "Uploading hidden service descriptor: http "
+                      "status 400 (%s) response from dirserver "
+                      "'%s:%d'. Malformed hidden service descriptor?",
+             escaped(reason), conn->base_.address, conn->base_.port);
+    /* XXX: Trigger control event. */
+    break;
+  default:
+    log_warn(LD_REND, "Uploading hidden service descriptor: http "
+                      "status %d (%s) response unexpected (server "
+                      "'%s:%d').",
+             status_code, escaped(reason), conn->base_.address,
+             conn->base_.port);
+    /* XXX: Trigger control event. */
+    break;
+  }
+
+  return 0;
+}
+
 /** Called when a directory connection reaches EOF. */
 int
 connection_dir_reached_eof(dir_connection_t *conn)

+ 4 - 0
src/or/directory.h

@@ -12,6 +12,8 @@
 #ifndef TOR_DIRECTORY_H
 #define TOR_DIRECTORY_H
 
+#include "hs_ident.h"
+
 int directories_have_accepted_server_descriptor(void);
 void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
                                   dirinfo_type_t type, const char *payload,
@@ -71,6 +73,8 @@ void directory_request_set_if_modified_since(directory_request_t *req,
                                              time_t if_modified_since);
 void directory_request_set_rend_query(directory_request_t *req,
                                       const rend_data_t *query);
+void directory_request_upload_set_hs_ident(directory_request_t *req,
+                                           const hs_ident_dir_conn_t *ident);
 
 void directory_request_set_routerstatus(directory_request_t *req,
                                         const routerstatus_t *rs);

+ 4 - 2
src/or/hs_cache.c

@@ -124,8 +124,10 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
     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. "
-                        "Rejecting!");
+               "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

+ 584 - 0
src/or/hs_cell.c

@@ -0,0 +1,584 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cell.c
+ * \brief Hidden service API for cell creation and handling.
+ **/
+
+#include "or.h"
+#include "config.h"
+#include "rendservice.h"
+#include "replaycache.h"
+
+#include "hs_cell.h"
+#include "hs_ntor.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+#include "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;
+}
+
+/* ========== */
+/* 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.", 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;
+}
+

+ 75 - 0
src/or/hs_cell.h

@@ -0,0 +1,75 @@
+/* Copyright (c) 2017, 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.h"
+#include "hs_service.h"
+
+/* 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 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);
+
+/* 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);
+
+#endif /* TOR_HS_CELL_H */
+

+ 832 - 0
src/or/hs_circuit.c

@@ -6,14 +6,27 @@
  **/
 
 #include "or.h"
+#include "circpathbias.h"
 #include "circuitbuild.h"
 #include "circuitlist.h"
 #include "circuituse.h"
 #include "config.h"
+#include "policies.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "rephist.h"
+#include "router.h"
 
+#include "hs_cell.h"
 #include "hs_circuit.h"
 #include "hs_ident.h"
 #include "hs_ntor.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.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
@@ -167,6 +180,825 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
   }
 }
 
+/* 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) {
+    uint8_t digest[DIGEST_LEN];
+    if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+      return;
+    }
+    hs_circuitmap_register_intro_circ_v2_service_side(circ, 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));
+}
+
+/* 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 checks the firewall policies and if we
+ * are allowed to extend to the chosen address.
+ *
+ *  if either IPv4 or legacy ID is missing, error.
+ *  if not direct_conn, IPv4 is prefered.
+ *  if direct_conn, IPv6 is prefered if we have one available.
+ *  if firewall does not allow the chosen address, error.
+ *
+ * Return NULL if we can't fulfill the conditions. */
+static extend_info_t *
+get_rp_extend_info(const smartlist_t *link_specifiers,
+                   const curve25519_public_key_t *onion_key, int direct_conn)
+{
+  int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+  char legacy_id[DIGEST_LEN] = {0};
+  uint16_t port_v4 = 0, port_v6 = 0, port = 0;
+  tor_addr_t addr_v4, addr_v6, *addr = NULL;
+  ed25519_public_key_t ed25519_pk;
+  extend_info_t *info = NULL;
+
+  tor_assert(link_specifiers);
+  tor_assert(onion_key);
+
+  SMARTLIST_FOREACH_BEGIN(link_specifiers, 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_IPV6:
+      /* Skip if we already seen a v6. */
+      if (have_v6) continue;
+      tor_addr_from_ipv6_bytes(&addr_v6,
+          (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
+      port_v6 = link_specifier_get_un_ipv6_port(ls);
+      have_v6 = 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);
+
+  /* IPv4, legacy ID are mandatory for rend points.
+   * ed25519 keys and ipv6 are optional for rend points */
+  if (!have_v4 || !have_legacy_id) {
+    goto done;
+  }
+  /* By default, we pick IPv4 but this might change to v6 if certain
+   * conditions are met. */
+  addr = &addr_v4; port = port_v4;
+
+  /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
+   * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
+   * address available so go to validation. */
+  if (!direct_conn) {
+    goto validate;
+  }
+
+  /* From this point on, we have a request for a direct connection to the
+   * rendezvous point so make sure we can actually connect through our
+   * firewall. We'll prefer IPv6. */
+
+  /* IPv6 test. */
+  if (have_v6 &&
+      fascist_firewall_allows_address_addr(&addr_v6, port_v6,
+                                           FIREWALL_OR_CONNECTION, 1, 1)) {
+    /* Direct connection and we can reach it in IPv6 so go for it. */
+    addr = &addr_v6; port = port_v6;
+    goto validate;
+  }
+  /* IPv4 test and we are sure we have a v4 because of the check above. */
+  if (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. */
+    addr = &addr_v4; port = port_v4;
+    goto validate;
+  }
+
+ 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)) {
+    log_warn(LD_REND, "Rendezvous point address is private and it is 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, port);
+ done:
+  return info;
+}
+
+/* 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 = get_rp_extend_info(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. */
+    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 rendezvous circuit to %s "
+                      "for service %s",
+             safe_str_client(extend_info_describe(info)),
+             safe_str_client(service->onion_address));
+    goto end;
+  }
+  log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
+                    "for service %s",
+           safe_str_client(extend_info_describe(info)),
+           safe_str_client(hex_str((const char *) data->rendezvous_cookie,
+                                   REND_COOKIE_LEN)),
+           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;
+  }
+
+  /* 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,
+             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++;
+  new_circ->build_state->expiry_time = bstate->expiry_time;
+  new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident);
+
+ done:
+  return;
+}
+
+/* ========== */
+/* 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)
+{
+  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;
+}
+
+/* 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 redenzvous 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
+ * - 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 so 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 service don't have an 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;
+    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);
+
+  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.", 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

+ 34 - 0
src/or/hs_circuit.h

@@ -10,6 +10,40 @@
 #define TOR_HS_CIRCUIT_H
 
 #include "or.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_service.h"
+
+/* 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);
 
 /* e2e circuit API. */
 

+ 813 - 7
src/or/hs_common.c

@@ -15,10 +15,119 @@
 
 #include "config.h"
 #include "networkstatus.h"
+#include "nodelist.h"
 #include "hs_cache.h"
 #include "hs_common.h"
 #include "hs_service.h"
 #include "rendcommon.h"
+#include "rendservice.h"
+#include "router.h"
+#include "shared_random.h"
+#include "shared_random_state.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 /* 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_current_hsdir_index(const void *_key, const void **_member)
+{
+  const char *key = _key;
+  const node_t *node = *_member;
+  return tor_memcmp(key, node->hsdir_index->current, 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_next_hsdir_index(const void *_key, const void **_member)
+{
+  const char *key = _key;
+  const node_t *node = *_member;
+  return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects current hsdir_index. */
+static int
+compare_node_current_hsdir_index(const void **a, const void **b)
+{
+  const node_t *node1= *a;
+  const node_t *node2 = *b;
+  return tor_memcmp(node1->hsdir_index->current,
+                    node2->hsdir_index->current,
+                    DIGEST256_LEN);
+}
+
+/* Helper function: Compare two node_t objects next hsdir_index. */
+static int
+compare_node_next_hsdir_index(const void **a, const void **b)
+{
+  const node_t *node1= *a;
+  const node_t *node2 = *b;
+  return tor_memcmp(node1->hsdir_index->next,
+                    node2->hsdir_index->next,
+                    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. */
@@ -72,6 +181,17 @@ hs_check_service_private_dir(const char *username, const char *path,
 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,
@@ -83,17 +203,22 @@ get_time_period_length(void)
 }
 
 /** Get the HS time period number at time <b>now</b> */
-STATIC uint64_t
-get_time_period_num(time_t now)
+uint64_t
+hs_get_time_period_num(time_t now)
 {
   uint64_t time_period_num;
+
+  /* Start by calculating minutes since the epoch */
   uint64_t time_period_length = get_time_period_length();
   uint64_t minutes_since_epoch = now / 60;
 
-  /* Now subtract half a day to fit the prop224 time period schedule (see
-   * section [TIME-PERIODS]). */
-  tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET);
-  minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET;
+  /* 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;
@@ -105,7 +230,22 @@ get_time_period_num(time_t now)
 uint64_t
 hs_get_next_time_period_num(time_t now)
 {
-  return get_time_period_num(now) + 1;
+  return hs_get_time_period_num(now) + 1;
+}
+
+/* Return the start time of the upcoming time period based on <b>now</b>. */
+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 start_of_next_tp_in_mins * 60 + time_period_rotation_offset;
 }
 
 /* Create a new rend_data_t for a specific given <b>version</b>.
@@ -360,6 +500,148 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
   }
 }
 
+/* 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 (in case of overlap mode).
+ */
+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
+
+/* 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)
@@ -442,6 +724,98 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
   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
@@ -541,6 +915,404 @@ hs_build_address(const ed25519_public_key_t *key, uint8_t version,
   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 *dup = link_specifier_new();
+  memcpy(dup, lspec, sizeof(*dup));
+  /* The unrecognized field is a dynamic array so make sure to copy its
+   * content and not the pointer. */
+  link_specifier_setlen_un_unrecognized(
+                        dup, link_specifier_getlen_un_unrecognized(lspec));
+  if (link_specifier_getlen_un_unrecognized(dup)) {
+    memcpy(link_specifier_getarray_un_unrecognized(dup),
+           link_specifier_getconstarray_un_unrecognized(lspec),
+           link_specifier_getlen_un_unrecognized(dup));
+  }
+  return dup;
+}
+
+/* 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 overlap mode is active given the date in consensus. If
+ * consensus is NULL, then we use the latest live consensus we can find. */
+MOCK_IMPL(int,
+hs_overlap_mode_is_active, (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;
+    }
+  }
+
+  /* We consider to be in overlap mode when we are in the period of time
+   * between a fresh SRV and the beginning of the new time period (in the
+   * normal network this is between 00:00 (inclusive) and 12:00 UTC
+   * (exclusive)) */
+  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 1;
+  }
+
+  return 0;
+}
+
+/* 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 */
+  if (!node_has_descriptor(node)) {
+    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(node->hsdir_index == NULL) ||
+      BUG(tor_mem_is_zero((const char*)node->hsdir_index->current,
+                          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
+ * is_next_period is true, the next hsdir_index of the node_t is used. If
+ * is_client 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 is_next_period,
+                          int is_client, 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();
+
+  /* Add every node_t that support HSDir v3 for which we do have a valid
+   * hsdir_index already computed for them for this consensus. */
+  {
+    networkstatus_t *c = networkstatus_get_latest_consensus();
+    if (!c || smartlist_len(c->routerstatus_list) == 0) {
+      log_warn(LD_REND, "No valid consensus so we can't get the responsible "
+                        "hidden service directories.");
+      goto done;
+    }
+    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 (is_next_period) {
+    smartlist_sort(sorted_nodes, compare_node_next_hsdir_index);
+    cmp_fct = compare_digest_to_next_hsdir_index;
+  } else {
+    smartlist_sort(sorted_nodes, compare_node_current_hsdir_index);
+    cmp_fct = compare_digest_to_current_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 = (is_client) ? 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);
+}
+
 /* Initialize the entire HS subsytem. This is called in tor_init() before any
  * torrc options are loaded. Only for >= v3. */
 void
@@ -561,3 +1333,37 @@ hs_free_all(void)
   hs_cache_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();
+  }
+}
+

+ 119 - 3
src/or/hs_common.h

@@ -11,6 +11,9 @@
 
 #include "or.h"
 
+/* Trunnel */
+#include "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
@@ -49,8 +52,6 @@
 #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 */
 
 /* Prefix of the onion address checksum. */
 #define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
@@ -76,12 +77,77 @@
 #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 3
+/* Default value of hsdir spread fetch (hsdir_spread_fetch). */
+#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3
+
 /* 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;
 
+/* 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;
+
+/* Hidden service directory index used in a node_t which is set once we set
+ * the consensus. */
+typedef struct hsdir_index_t {
+  /* The hsdir index for the current time period. */
+  uint8_t current[DIGEST256_LEN];
+  /* The hsdir index for the next time period. */
+  uint8_t next[DIGEST256_LEN];
+} hsdir_index_t;
+
 void hs_init(void);
 void hs_free_all(void);
 
@@ -95,6 +161,16 @@ int hs_address_is_valid(const char *address);
 int hs_parse_address(const char *address, ed25519_public_key_t *key_out,
                      uint8_t *checksum_out, uint8_t *version_out);
 
+void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey,
+                             const uint8_t *secret, size_t secret_len,
+                             uint64_t time_period_num,
+                             ed25519_public_key_t *pubkey_out);
+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 *kp_out);
+int hs_service_requires_uptime_circ(const smartlist_t *ports);
+
 void rend_data_free(rend_data_t *data);
 rend_data_t *rend_data_dup(const rend_data_t *data);
 rend_data_t *rend_data_client_create(const char *onion_address,
@@ -111,14 +187,54 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data,
 const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
                                        size_t *len_out);
 
+void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
+                          const ed25519_public_key_t *blinded_pk,
+                          uint8_t *subcred_out);
+
+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_overlap_mode_is_active,
+          (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 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 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 ed25519_public_key_t *blinded_pk,
+                               uint64_t time_period_num, int is_next_period,
+                               int is_client, smartlist_t *responsible_dirs);
+
+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);
 
 #ifdef HS_COMMON_PRIVATE
 
+STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
+
 #ifdef TOR_UNIT_TESTS
 
 STATIC uint64_t get_time_period_length(void);
-STATIC uint64_t get_time_period_num(time_t now);
+
+STATIC uint8_t *get_first_cached_disaster_srv(void);
+STATIC uint8_t *get_second_cached_disaster_srv(void);
 
 #endif /* TOR_UNIT_TESTS */
 

+ 222 - 30
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"
@@ -78,6 +79,7 @@
 #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"
@@ -136,6 +138,7 @@ static token_rule_t hs_desc_encrypted_v3_token_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),
@@ -144,29 +147,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = {
   END_OF_TABLE
 };
 
-/* Free a descriptor intro point object. */
-STATIC void
-desc_intro_point_free(hs_desc_intro_point_t *ip)
-{
-  if (!ip) {
-    return;
-  }
-  if (ip->link_specifiers) {
-    SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
-                      ls, tor_free(ls));
-    smartlist_free(ip->link_specifiers);
-  }
-  tor_cert_free(ip->auth_key_cert);
-  tor_cert_free(ip->enc_key_cert);
-  if (ip->legacy.key) {
-    crypto_pk_free(ip->legacy.key);
-  }
-  if (ip->legacy.cert.encoded) {
-    tor_free(ip->legacy.cert.encoded);
-  }
-  tor_free(ip);
-}
-
 /* Free the content of the plaintext section of a descriptor. */
 static void
 desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
@@ -197,7 +177,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
   }
   if (desc->intro_points) {
     SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
-                      desc_intro_point_free(ip));
+                      hs_desc_intro_point_free(ip));
     smartlist_free(desc->intro_points);
   }
   memwipe(desc, 0, sizeof(*desc));
@@ -256,7 +236,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
   memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
   offset += sizeof(desc->subcredential);
   /* Copy revision counter value. */
-  set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter));
+  set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
   offset += sizeof(uint64_t);
   tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
 }
@@ -383,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);
     }
@@ -479,6 +467,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip)
   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 *
@@ -498,6 +506,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key,
     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;
@@ -988,6 +1006,10 @@ desc_encode_v3(const hs_descriptor_t *desc,
   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;
@@ -1134,6 +1156,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;
     }
@@ -1626,6 +1657,50 @@ decode_intro_legacy_key(const directory_token_t *tok,
   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
@@ -1651,17 +1726,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
 
   /* Ok we seem to have a well formed section containing enough tokens to
    * parse. Allocate our IP object and try to populate it. */
-  ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t));
+  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);
@@ -1733,7 +1815,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
   goto done;
 
  err:
-  desc_intro_point_free(ip);
+  hs_desc_intro_point_free(ip);
   ip = NULL;
 
  done:
@@ -2215,7 +2297,7 @@ hs_desc_decode_descriptor(const char *encoded,
                           const uint8_t *subcredential,
                           hs_descriptor_t **desc_out)
 {
-  int ret;
+  int ret = -1;
   hs_descriptor_t *desc;
 
   tor_assert(encoded);
@@ -2223,10 +2305,13 @@ hs_desc_decode_descriptor(const char *encoded,
   desc = tor_malloc_zero(sizeof(hs_descriptor_t));
 
   /* Subcredentials are optional. */
-  if (subcredential) {
-    memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+  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;
@@ -2352,3 +2437,110 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
           data->superencrypted_blob_size);
 }
 
+/* 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);
+  }
+}
+

+ 19 - 3
src/or/hs_descriptor.h

@@ -23,12 +23,15 @@
 /* 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. */
-#define HS_DESC_CERT_LIFETIME (24 * 60 * 60)
+#define HS_DESC_CERT_LIFETIME (36 * 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
@@ -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;
 
@@ -80,6 +85,10 @@ typedef struct hs_desc_intro_point_t {
    * 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. */
@@ -197,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_clear_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);
@@ -211,6 +225,9 @@ int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
 
 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);
+
 #ifdef HS_DESCRIPTOR_PRIVATE
 
 /* Encoding. */
@@ -229,7 +246,6 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t 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 void desc_intro_point_free(hs_desc_intro_point_t *ip);
 STATIC size_t decode_superencrypted(const char *message, size_t message_len,
                                    uint8_t **encrypted_out);
 #endif /* HS_DESCRIPTOR_PRIVATE */

+ 10 - 3
src/or/hs_ident.c

@@ -30,13 +30,20 @@ hs_ident_circuit_free(hs_ident_circuit_t *ident)
   if (ident == NULL) {
     return;
   }
-  if (ident->auth_key_type == HS_AUTH_KEY_TYPE_LEGACY) {
-    crypto_pk_free(ident->auth_rsa_pk);
-  }
   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 *

+ 15 - 9
src/or/hs_ident.h

@@ -52,27 +52,32 @@ typedef struct hs_ident_circuit_t {
    * set when an object is initialized in its constructor. */
   hs_ident_circuit_type_t circuit_type;
 
-  /* (Only intro point circuit) Which type of authentication key this
-   * circuit identifier is using. */
-  hs_auth_key_type_t auth_key_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 intro point circuit) Introduction point authentication key. In
-   * legacy mode, we use an RSA key else an ed25519 public key. */
-  crypto_pk_t *auth_rsa_pk;
-  ed25519_public_key_t auth_ed25519_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 rendezvous circuit) The HANDSHAKE_INFO needed in the RENDEZVOUS1
-   * cell of the service. The construction is as follows:
+  /* (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];
@@ -110,6 +115,7 @@ 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);
+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);

+ 16 - 0
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"
 
@@ -591,3 +592,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
   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));
+}
+

+ 8 - 2
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_intropoint_clear(hs_intropoint_t *ip);
+
 #ifdef HS_INTROPOINT_PRIVATE
 
 #include "hs/cell_establish_intro.h"

+ 2502 - 247
src/or/hs_service.c

@@ -9,25 +9,62 @@
 #define HS_SERVICE_PRIVATE
 
 #include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
 #include "circuitlist.h"
+#include "circuituse.h"
 #include "config.h"
+#include "directory.h"
+#include "main.h"
+#include "networkstatus.h"
+#include "nodelist.h"
 #include "relay.h"
 #include "rendservice.h"
 #include "router.h"
 #include "routerkeys.h"
+#include "routerlist.h"
+#include "statefile.h"
 
+#include "hs_circuit.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"
 
-#include "hs/cell_establish_intro.h"
+/* Trunnel */
+#include "ed25519_cert.h"
 #include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* 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";
+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
@@ -35,6 +72,8 @@ static const char *address_tld = "onion";
  * loading keys requires that we are an actual running tor process. */
 static smartlist_t *hs_service_staging_list;
 
+static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc);
+
 /* 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
@@ -137,10 +176,10 @@ 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->descriptor_post_period = options->RendPostPeriod;
   c->max_streams_per_rdv_circuit = 0;
   c->max_streams_close_circuit = 0;
   c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
@@ -167,6 +206,77 @@ service_clear_config(hs_service_config_t *config)
   memset(config, 0, sizeof(*config));
 }
 
+/* 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 accomodate 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 accomodate 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 accomodate 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 accomodate 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
@@ -201,13 +311,357 @@ 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_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 *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);
+
+  { /* 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 (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. */
+  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 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)
+{
+  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;
+}
+
 /* 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. */
@@ -218,13 +672,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 = hs_circ_service_get_intro_circ(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;
@@ -236,12 +688,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. */
@@ -269,7 +718,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;
@@ -283,7 +732,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);
   }
@@ -339,7 +787,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);
 
@@ -359,6 +806,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);
@@ -386,9 +835,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. */
@@ -399,25 +848,22 @@ register_all_services(void)
 
 /* 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
+STATIC int
 write_address_to_file(const hs_service_t *service, const char *fname_)
 {
   int ret = -1;
   char *fname = NULL;
-  /* Length of an address plus the sizeof the address tld (onion) which counts
-   * the NUL terminated byte so we keep it for the "." and the newline. */
-  char buf[HS_SERVICE_ADDR_LEN_BASE32 + sizeof(address_tld) + 1];
+  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_snprintf(buf, sizeof(buf), "%s.%s\n", service->onion_address,
-               address_tld);
+  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, buf, 0) < 0) {
+  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;
@@ -437,6 +883,7 @@ write_address_to_file(const hs_service_t *service, const char *fname_)
   ret = 0;
  end:
   tor_free(fname);
+  tor_free(addr_buf);
   return ret;
 }
 
@@ -510,291 +957,2099 @@ load_service_keys(hs_service_t *service)
   return ret;
 }
 
-/* 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)
+/* Free a given service descriptor object and all key material is wiped. */
+STATIC void
+service_descriptor_free(hs_service_descriptor_t *desc)
 {
-  /* Load v2 service keys if we have v2. */
-  if (num_rend_services() != 0) {
-    if (rend_service_load_all_keys(NULL) < 0) {
-      goto err;
-    }
+  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));
+  SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+  smartlist_free(desc->hsdir_missing_info);
+  /* Cleanup all intro points. */
+  digest256map_free(desc->intro_points.map, service_intro_point_free_);
+  digestmap_free(desc->intro_points.failed_id, tor_free_);
+  tor_free(desc);
+}
 
-  /* 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);
+/* 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->hsdir_missing_info = smartlist_new();
+  return sdesc;
+}
 
-  /* 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();
+/* 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);
 
-  /* All keys have been loaded successfully. */
-  return 0;
- err:
-  return -1;
+  /* 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;
 }
 
-/* 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)
+/* 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(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);
+  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;
 }
 
-/* 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)
+/* 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)
 {
-  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;
-  return service;
+  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);
 }
 
-/* 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)
+/* 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)
 {
-  if (service == NULL) {
-    return;
-  }
+  tor_assert(dst);
+  tor_assert(src);
+  memcpy(dst, src, sizeof(hs_desc_link_specifier_t));
+}
 
-  /* 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);
+/* 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;
   }
 
-  /* Free service configuration. */
-  service_clear_config(&service->config);
+  /* 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;
+  }
 
-  /* Wipe service keys. */
-  memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
+  /* 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;
 
-  tor_free(service);
+ done:
+  return ret;
 }
 
-/* Initialize the service HS subsystem. */
-void
-hs_service_init(void)
+/* 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)
 {
-  /* Should never be called twice. */
-  tor_assert(!hs_service_map);
-  tor_assert(!hs_service_staging_list);
+  hs_desc_encrypted_data_t *encrypted;
 
-  /* v2 specific. */
-  rend_service_init();
+  tor_assert(service);
+  tor_assert(desc);
 
-  hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht));
-  HT_INIT(hs_service_ht, hs_service_map);
+  /* 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);
 
-  hs_service_staging_list = smartlist_new();
+  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;
 }
 
-/* Release all global storage of the hidden service subsystem. */
-void
-hs_service_free_all(void)
+/* 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)
 {
-  rend_service_free_all();
-  service_free_all();
-}
+  hs_desc_encrypted_data_t *encrypted;
+
+  tor_assert(service);
+  tor_assert(desc);
+
+  encrypted = &desc->desc->encrypted_data;
 
-/* XXX We don't currently use these functions, apart from generating unittest
-   data. When we start implementing the service-side support for prop224 we
-   should revisit these functions and use them. */
+  encrypted->create2_ntor = 1;
+  encrypted->single_onion_service = service->config.is_single_onion;
 
-/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in
- *  <b>buf_out</b> which has size <b>buf_out_len</b>. Return the number of
- *  bytes written, or a negative integer if there was an error. */
-ssize_t
-get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len,
-                            const trn_cell_establish_intro_t *cell)
+  /* 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)
 {
-  ssize_t bytes_used = 0;
+  int ret = -1;
+  hs_desc_plaintext_data_t *plaintext;
 
-  if (buf_out_len < RELAY_PAYLOAD_SIZE) {
-    return -1;
+  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;
   }
 
-  bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len,
-                                              cell);
-  return bytes_used;
+  return ret;
 }
 
-/* Set the cell extensions of <b>cell</b>. */
+/* 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
-set_trn_cell_extensions(trn_cell_establish_intro_t *cell)
+build_service_descriptor(hs_service_t *service, time_t now,
+                         uint64_t time_period_num,
+                         hs_service_descriptor_t **desc_out)
 {
-  trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new();
+  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);
 
-  /* For now, we don't use extensions at all. */
-  trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */
-  trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions);
+  /* 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;
 }
 
-/** Given the circuit handshake info in <b>circuit_key_material</b>, create and
- *  return an ESTABLISH_INTRO cell. Return NULL if something went wrong.  The
- *  returned cell is allocated on the heap and it's the responsibility of the
- *  caller to free it. */
-trn_cell_establish_intro_t *
-generate_establish_intro_cell(const uint8_t *circuit_key_material,
-                              size_t circuit_key_material_len)
+/* 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)
 {
-  trn_cell_establish_intro_t *cell = NULL;
-  ssize_t encoded_len;
+  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);
 
-  log_warn(LD_GENERAL,
-           "Generating ESTABLISH_INTRO cell (key_material_len: %u)",
-           (unsigned) circuit_key_material_len);
+  /* 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;
+  }
+
+  /* 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)) {
+    tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity));
+  }
 
-  /* Generate short-term keypair for use in ESTABLISH_INTRO */
-  ed25519_keypair_t key_struct;
-  if (ed25519_keypair_generate(&key_struct, 0) < 0) {
+  /* 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 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;
+}
+
+/* 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;
+    }
+    /* 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)
+{
+  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 = hs_circ_service_get_intro_circ(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) {
+        /* Remove intro point from descriptor map. We'll add it to the failed
+         * map if we retried it too many times. */
+        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 && !TO_CIRCUIT(ocirc)->marked_for_close) {
+          /* After this, no new cells will be handled on the circuit. */
+          circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+        }
+        continue;
+      }
+    } 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)
+{
+  /* 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) {
+    /* 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)
+{
+  /* 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);
+
+    /* 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) {
+        if (!direct_conn) {
+          /* In case of a multi-hop connection, it should never happen that we
+           * can't get the extend info from the node. Avoid connection and
+           * remove intro point from descriptor in order to recover from this
+           * potential bug. */
+          tor_assert_nonfatal(ei);
+        }
+        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_warn(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;
+  }
 
-  cell = trn_cell_establish_intro_new();
+  num_wanted_ip = service->config.num_intro_points;
 
-  /* Set AUTH_KEY_TYPE: 2 means ed25519 */
-  trn_cell_establish_intro_set_auth_key_type(cell,
-                                             HS_INTRO_AUTH_KEY_TYPE_ED25519);
+  /* 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());
 
-  /* Set AUTH_KEY_LEN field */
-  /* Must also set byte-length of AUTH_KEY to match */
-  int 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);
+  /* 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);
 
-  /* Set AUTH_KEY field */
-  uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
-  memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len);
+  /* 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;
 
-  /* No cell extensions needed */
-  set_trn_cell_extensions(cell);
+  return (count * multiplier);
+}
 
-  /* Set signature size.
-     We need to do this up here, because _encode() needs it and we need to call
-     _encode() to calculate the MAC and signature.
-  */
-  int sig_len = ED25519_SIG_LEN;
-  trn_cell_establish_intro_set_sig_len(cell, sig_len);
-  trn_cell_establish_intro_setlen_sig(cell, sig_len);
+/* 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);
 
-  /* XXX How to make this process easier and nicer? */
+  /* 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;
+  }
 
-  /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */
+  /* Rate limit log that we've reached our circuit creation limit. */
   {
-    /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive
-       the MAC from it. */
-    uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0};
-    uint8_t mac[TRUNNEL_SHA3_256_LEN];
-
-    encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp,
-                                                 sizeof(cell_bytes_tmp),
-                                                 cell);
-    if (encoded_len < 0) {
-      log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell.");
-      goto err;
+    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, elapsed_time,
+               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;
+}
 
-    /* sanity check */
-    tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN);
+/* 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 version_str[4] = {0}, *encoded_desc = NULL;
+  directory_request_t *dir_req;
+  hs_ident_dir_conn_t ident;
+
+  tor_assert(service);
+  tor_assert(desc);
+  tor_assert(hsdir);
+
+  memset(&ident, 0, sizeof(ident));
+
+  /* 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;
+  }
 
-    /* Calculate MAC of all fields before HANDSHAKE_AUTH */
-    crypto_mac_sha3_256(mac, sizeof(mac),
-                        circuit_key_material, circuit_key_material_len,
-                        cell_bytes_tmp,
-                        encoded_len -
-                          (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN));
-    /* Write the MAC to the cell */
-    uint8_t *handshake_ptr =
-      trn_cell_establish_intro_getarray_handshake_mac(cell);
-    memcpy(handshake_ptr, mac, sizeof(mac));
+  /* 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;
   }
 
-  /* Calculate the cell signature */
+  /* Setup the connection identifier. */
+  ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk);
+  /* 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",
+               service->config.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);
+
+  /* Logging so we know where it was sent. */
   {
-    /* To calculate the sig we follow the same procedure as above. We first
-       dump the cell up to the sig, and then calculate the sig */
-    uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0};
-    ed25519_signature_t sig;
-
-    encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp,
-                                                 sizeof(cell_bytes_tmp),
-                                                 cell);
-    if (encoded_len < 0) {
-      log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2).");
-      goto err;
+    int is_next_desc = (service->desc_next == desc);
+    const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next :
+                                            hsdir->hsdir_index->current;
+    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 *) index, 32)));
+  }
+
+  /* XXX: Inform control port of the upload event (#20699). */
+ 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:
+  if (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;
     }
+  }
 
-    tor_assert(encoded_len > ED25519_SIG_LEN);
+ done:
+  return final_rev_counter;
+}
 
-    if (ed25519_sign_prefixed(&sig,
-                              cell_bytes_tmp,
-                              encoded_len -
-                                (ED25519_SIG_LEN + sizeof(cell->sig_len)),
-                              ESTABLISH_INTRO_SIG_PREFIX,
-                              &key_struct)) {
-      log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell.");
-      goto err;
+/** 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, int for_next_period)
+{
+  smartlist_t *responsible_dirs = NULL;
+
+  tor_assert(service);
+  tor_assert(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,
+                            for_next_period, 0, responsible_dirs);
+
+  /* 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);
+    /* Do not upload to an HSDir we don't have a descriptor for. */
+    if (!node_has_descriptor(hsdir_node)) {
+      log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading "
+                        "descriptor. We'll try later once we have it.",
+               safe_str_client(node_describe(hsdir_node)));
+      /* Once we get new directory information, this HSDir will be retried if
+       * we ever get the descriptor. */
+      smartlist_add(desc->hsdir_missing_info,
+                    tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN));
+      continue;
     }
 
-    /* And write the signature to the cell */
-    uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
-    memcpy(sig_ptr, sig.sig, sig_len);
+    /* 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);
   }
 
-  /* We are done! Return the cell! */
-  return cell;
+  /* Update the revision counter of this descriptor */
+  increment_descriptor_revision_counter(desc->desc);
 
- err:
-  trn_cell_establish_intro_free(cell);
-  return NULL;
+  smartlist_free(responsible_dirs);
+  return;
+}
+
+/* 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;
+  }
+
+  /* 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) {
+      int for_next_period = 0;
+
+      /* Can this descriptor be uploaed? */
+      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);
+
+      /* If the service is in the overlap period and this descriptor is the
+       * next one, it has to be uploaded for the next time period meaning
+       * we'll use the next node_t hsdir_index to pick the HSDirs. */
+      if (desc == service->desc_next) {
+        for_next_period = 1;
+      }
+      upload_descriptor_to_all(service, desc, for_next_period);
+    } FOR_EACH_DESCRIPTOR_END;
+  } FOR_EACH_SERVICE_END;
+}
+
+/* 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 */
+  if (!TO_CIRCUIT(circ)->timestamp_dirty)
+    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;
+}
+
+/* For a given service and a descriptor of that service, consider retrying to
+ * upload the descriptor to any directories from which we had missing
+ * information when originally tried to be uploaded. This is called when our
+ * directory information has changed. */
+static void
+consider_hsdir_upload_retry(const hs_service_t *service,
+                            hs_service_descriptor_t *desc)
+{
+  smartlist_t *responsible_dirs = NULL;
+  smartlist_t *still_missing_dirs = NULL;
+
+  tor_assert(service);
+  tor_assert(desc);
+
+  responsible_dirs = smartlist_new();
+  still_missing_dirs = smartlist_new();
+
+  /* We first need to get responsible directories from the latest consensus so
+   * we can then make sure that the node that we were missing information for
+   * is still responsible for this descriptor. */
+  hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num,
+                            service->desc_next == desc, 0, responsible_dirs);
+
+  SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) {
+    const node_t *node;
+    const char *id = rs->identity_digest;
+    if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) {
+      continue;
+    }
+    /* We do need a node_t object and descriptor to perform an upload. If
+     * found, we remove the id from the missing dir list else we add it to the
+     * still missing dir list to keep track of id that are still missing. */
+    node = node_get_by_id(id);
+    if (node && node_has_descriptor(node)) {
+      upload_descriptor_to_hsdir(service, desc, node);
+      smartlist_remove(desc->hsdir_missing_info, id);
+    } else {
+      smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN));
+    }
+  } SMARTLIST_FOREACH_END(rs);
+
+  /* Switch the still missing dir list with the current missing dir list in
+   * the descriptor. It is possible that the list ends up empty which is what
+   * we want if we have no more missing dir. */
+  SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id));
+  smartlist_free(desc->hsdir_missing_info);
+  desc->hsdir_missing_info = still_missing_dirs;
+
+  /* No ownership of the routerstatus_t object in this list. */
+  smartlist_free(responsible_dirs);
+}
+
+/* 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 */
+/* ========== */
+
+/* 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) {
+    log_warn(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;
+
+  /* We've retried too many times, remember it as a failed intro point so we
+   * don't pick it up again. It will be retried in INTRO_CIRC_RETRY_PERIOD
+   * seconds. */
+  if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) {
+    remember_failing_intro_point(ip, desc, approx_time());
+    service_intro_point_remove(service, ip);
+    service_intro_point_free(ip);
+  }
+
+ 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
+   * succesful, 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
+ * new descriptors for hidden service directories that we didn't have before
+ * so try them if it's the case. */
+void
+hs_service_dir_info_changed(void)
+{
+  /* For each service we have, check every descriptor and consider retrying to
+   * upload it to directories that we might have had missing information
+   * previously that is missing a router descriptor. */
+  FOR_EACH_SERVICE_BEGIN(service) {
+    FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+      /* This cleans up the descriptor missing hsdir information list if a
+       * successful upload is made or if any of the directory aren't
+       * responsible anymore for the service descriptor. */
+      consider_hsdir_upload_retry(service, desc);
+    } FOR_EACH_DESCRIPTOR_END;
+  } FOR_EACH_SERVICE_END;
+}
+
+/* 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);
+  } else {
+    ret = rend_service_receive_introduction(circ, payload, payload_len);
+  }
+
+ 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

+ 109 - 17
src/or/hs_service.h

@@ -15,6 +15,7 @@
 
 #include "hs_common.h"
 #include "hs_descriptor.h"
+#include "hs_ident.h"
 #include "hs_intropoint.h"
 
 /* Trunnel */
@@ -25,17 +26,30 @@
  * 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 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;
@@ -74,6 +88,12 @@ typedef struct hs_service_intropoints_t {
   /* 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. */
@@ -95,6 +115,20 @@ typedef struct hs_service_descriptor_t {
    * 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 identity digests for hidden service directories to which we
+   * couldn't upload this descriptor because we didn't have its router
+   * descriptor at the time. If this list is non-empty, only the relays in this
+   * list are re-tried to upload this descriptor when our directory information
+   * have been updated. */
+  smartlist_t *hsdir_missing_info;
 } hs_service_descriptor_t;
 
 /* Service key material. */
@@ -123,10 +157,6 @@ typedef struct hs_service_config_t {
    * if the service is ephemeral. Specified by HiddenServiceDir option. */
   char *directory_path;
 
-  /* The time period after which a descriptor is uploaded to the directories
-   * in seconds. Specified by RendPostPeriod option. */
-  uint32_t descriptor_post_period;
-
   /* The maximum number of simultaneous streams per rendezvous circuit that
    * are allowed to be created. No limit if 0. Specified by
    * HiddenServiceMaxStreams option. */
@@ -170,6 +200,13 @@ typedef struct hs_service_state_t {
   /* Indicate that the service has entered the overlap period. We use this
    * flag to check for descriptor rotation. */
   unsigned int in_overlap_period : 1;
+
+  /* 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;
 } hs_service_state_t;
 
 /* Representation of a service running on this tor instance. */
@@ -216,19 +253,25 @@ void hs_service_free_all(void);
 hs_service_t *hs_service_new(const or_options_t *options);
 void hs_service_free(hs_service_t *service);
 
+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);
-
-/* These functions are only used by unit tests and we need to expose them else
- * hs_service.o ends up with no symbols in libor.a which makes clang throw a
- * warning at compile time. See #21825. */
-
-trn_cell_establish_intro_t *
-generate_establish_intro_cell(const uint8_t *circuit_key_material,
-                              size_t circuit_key_material_len);
-ssize_t
-get_establish_intro_payload(uint8_t *buf, size_t buf_len,
-                            const trn_cell_establish_intro_t *cell);
+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_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);
 
 #ifdef HS_SERVICE_PRIVATE
 
@@ -245,6 +288,55 @@ 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);
+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);
+STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
+                                         const hs_service_t *service,
+                                         const hs_ident_circuit_t *ident);
+/* 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);
+
+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_);
 
 #endif /* TOR_UNIT_TESTS */
 

+ 3 - 1
src/or/include.am

@@ -54,6 +54,7 @@ LIBTOR_A_SOURCES = \
 	src/or/ext_orport.c				\
 	src/or/hibernate.c				\
 	src/or/hs_cache.c				\
+	src/or/hs_cell.c				\
 	src/or/hs_circuit.c				\
 	src/or/hs_circuitmap.c				\
 	src/or/hs_client.c				\
@@ -184,11 +185,12 @@ ORHEADERS = \
 	src/or/entrynodes.h				\
 	src/or/hibernate.h				\
 	src/or/hs_cache.h				\
+	src/or/hs_cell.h				\
+	src/or/hs_config.h				\
 	src/or/hs_circuit.h				\
 	src/or/hs_circuitmap.h				\
 	src/or/hs_client.h				\
 	src/or/hs_common.h				\
-	src/or/hs_config.h				\
 	src/or/hs_descriptor.h				\
 	src/or/hs_ident.h				\
 	src/or/hs_intropoint.h				\

+ 26 - 7
src/or/main.c

@@ -1194,6 +1194,7 @@ CALLBACK(heartbeat);
 CALLBACK(clean_consdiffmgr);
 CALLBACK(reset_padding_counts);
 CALLBACK(check_canonical_channels);
+CALLBACK(hs_service);
 
 #undef CALLBACK
 
@@ -1229,6 +1230,7 @@ static periodic_event_item_t periodic_events[] = {
   CALLBACK(clean_consdiffmgr),
   CALLBACK(reset_padding_counts),
   CALLBACK(check_canonical_channels),
+  CALLBACK(hs_service),
   END_OF_PERIODIC_EVENTS
 };
 #undef CALLBACK
@@ -1461,12 +1463,6 @@ run_scheduled_events(time_t now)
   /* 6. And remove any marked circuits... */
   circuit_close_all_marked();
 
-  /* 7. And upload service descriptors if necessary. */
-  if (have_completed_a_circuit() && !net_is_disabled()) {
-    rend_consider_services_upload(now);
-    rend_consider_descriptor_republication();
-  }
-
   /* 8. and blow away any connections that need to die. have to do this now,
    * because if we marked a conn for close and left its socket -1, then
    * we'll pass it to poll/select and bad things will happen.
@@ -2101,6 +2097,28 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options)
   return CDM_CLEAN_CALLBACK_INTERVAL;
 }
 
+/*
+ * Periodic callback: Run scheduled events for HS service. This is called
+ * every second.
+ */
+static int
+hs_service_callback(time_t now, const or_options_t *options)
+{
+  (void) options;
+
+  /* We need to at least be able to build circuits and that we actually have
+   * a working network. */
+  if (!have_completed_a_circuit() || net_is_disabled()) {
+    goto end;
+  }
+
+  hs_service_run_scheduled_events(now);
+
+ end:
+  /* Every 1 second. */
+  return 1;
+}
+
 /** Timer: used to invoke second_elapsed_callback() once per second. */
 static periodic_timer_t *second_timer = NULL;
 /** Number of libevent errors in the last second: we die if we get too many. */
@@ -3554,7 +3572,7 @@ sandbox_init_filter(void)
   {
     smartlist_t *files = smartlist_new();
     smartlist_t *dirs = smartlist_new();
-    rend_services_add_filenames_to_lists(files, dirs);
+    hs_service_lists_fnames_for_sandbox(files, dirs);
     SMARTLIST_FOREACH(files, char *, file_name, {
       char *tmp_name = NULL;
       tor_asprintf(&tmp_name, "%s.tmp", file_name);
@@ -3563,6 +3581,7 @@ sandbox_init_filter(void)
       /* steals references */
       sandbox_cfg_allow_open_filename(&cfg, file_name);
       sandbox_cfg_allow_open_filename(&cfg, tmp_name);
+      tor_free(file_name);
     });
     SMARTLIST_FOREACH(dirs, char *, dir, {
       /* steals reference */

+ 11 - 4
src/or/networkstatus.c

@@ -1393,14 +1393,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f))
 MOCK_IMPL(networkstatus_t *,
 networkstatus_get_live_consensus,(time_t now))
 {
-  if (networkstatus_get_latest_consensus() &&
-      networkstatus_get_latest_consensus()->valid_after <= now &&
-      now <= networkstatus_get_latest_consensus()->valid_until)
-    return networkstatus_get_latest_consensus();
+  networkstatus_t *ns = networkstatus_get_latest_consensus();
+  if (ns && networkstatus_is_live(ns, now))
+    return ns;
   else
     return NULL;
 }
 
+/** Given a consensus in <b>ns</b>, return true iff currently live and
+ *  unexpired. */
+int
+networkstatus_is_live(const networkstatus_t *ns, time_t now)
+{
+  return (ns->valid_after <= now && now <= ns->valid_until);
+}
+
 /** Determine if <b>consensus</b> is valid or expired recently enough that
  * we can still use it.
  *

+ 1 - 0
src/or/networkstatus.h

@@ -81,6 +81,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void));
 MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor,
           (consensus_flavor_t f));
 MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now));
+int networkstatus_is_live(const networkstatus_t *ns, time_t now);
 int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus,
                                             time_t now);
 int networkstatus_valid_until_is_reasonably_live(time_t valid_until,

+ 95 - 0
src/or/nodelist.c

@@ -45,6 +45,7 @@
 #include "dirserv.h"
 #include "entrynodes.h"
 #include "geoip.h"
+#include "hs_common.h"
 #include "main.h"
 #include "microdesc.h"
 #include "networkstatus.h"
@@ -54,6 +55,7 @@
 #include "rendservice.h"
 #include "router.h"
 #include "routerlist.h"
+#include "routerparse.h"
 #include "routerset.h"
 #include "torcert.h"
 
@@ -164,12 +166,78 @@ node_get_or_create(const char *identity_digest)
 
   smartlist_add(the_nodelist->nodes, node);
   node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1;
+  node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t));
 
   node->country = -1;
 
   return node;
 }
 
+/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index
+ * for the node, both current and next if possible. This can only fails if the
+ * node_t ed25519 identity key can't be found which would be a bug. */
+static void
+node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
+{
+  time_t now = approx_time();
+  const ed25519_public_key_t *node_identity_pk;
+  uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL;
+  uint64_t next_time_period_num, current_time_period_num;
+
+  tor_assert(node);
+  tor_assert(ns);
+
+  if (!networkstatus_is_live(ns, now)) {
+    log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus.");
+    goto done;
+  }
+
+  node_identity_pk = node_get_ed25519_id(node);
+  if (node_identity_pk == NULL) {
+    log_debug(LD_GENERAL, "ed25519 identity public key not found when "
+              "trying to build the hsdir indexes for node %s",
+              node_describe(node));
+    goto done;
+  }
+
+  /* Get the current and next time period number, we might use them both. */
+  current_time_period_num = hs_get_time_period_num(now);
+  next_time_period_num = hs_get_next_time_period_num(now);
+
+  if (hs_overlap_mode_is_active(ns, now)) {
+    /* We are in overlap mode, this means that our consensus has just cycled
+     * from current SRV to previous SRV so for the _next_ upcoming time
+     * period, we have to use the current SRV and use the previous SRV for the
+     * current time period. If the current or previous SRV can't be found, the
+     * disaster one is returned. */
+    next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns);
+    /* The following can be confusing so again, in overlap mode, we use our
+     * previous SRV for our _current_ hsdir index. */
+    current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns);
+  } else {
+    /* If NOT in overlap mode, we only need to compute the current hsdir index
+     * for the ongoing time period and thus the current SRV. If it can't be
+     * found, the disaster one is returned. */
+    current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns);
+  }
+
+  /* Build the current hsdir index. */
+  hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv,
+                       current_time_period_num, node->hsdir_index->current);
+  if (next_hsdir_index_srv) {
+    /* Build the next hsdir index if we have a next SRV that we can use. */
+    hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv,
+                         next_time_period_num, node->hsdir_index->next);
+  } else {
+    memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next));
+  }
+
+ done:
+  tor_free(current_hsdir_index_srv);
+  tor_free(next_hsdir_index_srv);
+  return;
+}
+
 /** Called when a node's address changes. */
 static void
 node_addrs_changed(node_t *node)
@@ -216,6 +284,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out)
     dirserv_set_node_flags_from_authoritative_status(node, status);
   }
 
+  /* Setting the HSDir index requires the ed25519 identity key which can
+   * only be found either in the ri or md. This is why this is called here.
+   * Only nodes supporting HSDir=2 protocol version needs this index. */
+  if (node->rs && node->rs->supports_v3_hsdir) {
+    node_set_hsdir_index(node,
+                         networkstatus_get_latest_consensus());
+  }
+
   return node;
 }
 
@@ -246,6 +322,12 @@ nodelist_add_microdesc(microdesc_t *md)
       node->md->held_by_nodes--;
     node->md = md;
     md->held_by_nodes++;
+    /* Setting the HSDir index requires the ed25519 identity key which can
+     * only be found either in the ri or md. This is why this is called here.
+     * Only nodes supporting HSDir=2 protocol version needs this index. */
+    if (rs->supports_v3_hsdir) {
+      node_set_hsdir_index(node, ns);
+    }
   }
   return node;
 }
@@ -283,6 +365,9 @@ nodelist_set_consensus(networkstatus_t *ns)
       }
     }
 
+    if (rs->supports_v3_hsdir) {
+      node_set_hsdir_index(node, ns);
+    }
     node_set_country(node);
 
     /* If we're not an authdir, believe others. */
@@ -410,6 +495,7 @@ node_free(node_t *node)
   if (node->md)
     node->md->held_by_nodes--;
   tor_assert(node->nodelist_idx == -1);
+  tor_free(node->hsdir_index);
   tor_free(node);
 }
 
@@ -719,6 +805,15 @@ node_supports_v3_hsdir(const node_t *node)
     if (node->ri->protocol_list == NULL) {
       return 0;
     }
+    /* Bug #22447 forces us to filter on tor version:
+     * If platform is a Tor version, and older than 0.3.0.8, return False.
+     * Else, obey the protocol list. */
+    if (node->ri->platform) {
+      if (!strcmpstart(node->ri->platform, "Tor ") &&
+          !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) {
+        return 0;
+      }
+    }
     return protocol_list_supports_protocol(node->ri->protocol_list,
                                            PRT_HSDIR, PROTOVER_HSDIR_V3);
   }

+ 17 - 3
src/or/or.h

@@ -421,15 +421,20 @@ typedef enum {
 #define DIR_PURPOSE_FETCH_RENDDESC_V2 18
 /** A connection to a directory server: download a microdescriptor. */
 #define DIR_PURPOSE_FETCH_MICRODESC 19
-#define DIR_PURPOSE_MAX_ 19
+/** A connection to a hidden service directory: upload a v3 descriptor. */
+#define DIR_PURPOSE_UPLOAD_HSDESC 20
+/** A connection to a hidden service directory: fetch a v3 descriptor. */
+#define DIR_PURPOSE_FETCH_HSDESC 21
+#define DIR_PURPOSE_MAX_ 21
 
 /** True iff <b>p</b> is a purpose corresponding to uploading
  * data to a directory server. */
 #define DIR_PURPOSE_IS_UPLOAD(p)                \
   ((p)==DIR_PURPOSE_UPLOAD_DIR ||               \
    (p)==DIR_PURPOSE_UPLOAD_VOTE ||              \
-   (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
-   (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2)
+   (p)==DIR_PURPOSE_UPLOAD_SIGNATURES ||        \
+   (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 ||       \
+   (p)==DIR_PURPOSE_UPLOAD_HSDESC)
 
 #define EXIT_PURPOSE_MIN_ 1
 /** This exit stream wants to do an ordinary connect. */
@@ -850,6 +855,8 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
 struct hs_ident_edge_conn_t;
 struct hs_ident_dir_conn_t;
 struct hs_ident_circuit_t;
+/* Stub because we can't include hs_common.h. */
+struct hsdir_index_t;
 
 /** Time interval for tracking replays of DH public keys received in
  * INTRODUCE2 cells.  Used only to avoid launching multiple
@@ -2490,6 +2497,10 @@ typedef struct node_t {
   time_t last_reachable;        /* IPv4. */
   time_t last_reachable6;       /* IPv6. */
 
+  /* Hidden service directory index data. This is used by a service or client
+   * in order to know what's the hs directory index for this node at the time
+   * the consensus is set. */
+  struct hsdir_index_t *hsdir_index;
 } node_t;
 
 /** Linked list of microdesc hash lines for a single router in a directory
@@ -4616,6 +4627,9 @@ typedef struct {
 
   config_line_t *TransportProxies;
 
+  /** Cached revision counters for active hidden services on this host */
+  config_line_t *HidServRevCounter;
+
   /** These fields hold information on the history of bandwidth usage for
    * servers.  The "Ends" fields hold the time when we last updated the
    * bandwidth usage. The "Interval" fields hold the granularity, in seconds,

+ 1 - 1
src/or/parsecommon.c

@@ -436,7 +436,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
  * in the same order in which they occur in <b>s</b>.  Otherwise return
  * NULL. */
 smartlist_t *
-find_all_by_keyword(smartlist_t *s, directory_keyword k)
+find_all_by_keyword(const smartlist_t *s, directory_keyword k)
 {
   smartlist_t *out = NULL;
   SMARTLIST_FOREACH(s, directory_token_t *, t,

+ 2 - 1
src/or/parsecommon.h

@@ -160,6 +160,7 @@ typedef enum {
   R3_INTRO_AUTH_REQUIRED,
   R3_SINGLE_ONION_SERVICE,
   R3_INTRODUCTION_POINT,
+  R3_INTRO_ONION_KEY,
   R3_INTRO_AUTH_KEY,
   R3_INTRO_ENC_KEY,
   R3_INTRO_ENC_KEY_CERT,
@@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s,
 
 directory_token_t *find_opt_by_keyword(smartlist_t *s,
                                        directory_keyword keyword);
-smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k);
+smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k);
 
 #endif /* TOR_PARSECOMMON_H */
 

+ 2 - 2
src/or/rendcommon.c

@@ -777,7 +777,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       break;
     case RELAY_COMMAND_INTRODUCE2:
       if (origin_circ)
-        r = rend_service_receive_introduction(origin_circ,payload,length);
+        r = hs_service_receive_introduce2(origin_circ,payload,length);
       break;
     case RELAY_COMMAND_INTRODUCE_ACK:
       if (origin_circ)
@@ -793,7 +793,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       break;
     case RELAY_COMMAND_INTRO_ESTABLISHED:
       if (origin_circ)
-        r = rend_service_intro_established(origin_circ,payload,length);
+        r = hs_service_receive_intro_established(origin_circ,payload,length);
       break;
     case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
       if (origin_circ)

+ 12 - 159
src/or/rendservice.c

@@ -83,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable(
                                   smartlist_t* substitute_service_list);
 static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted);
 
-/** Represents the mapping from a virtual port of a rendezvous service to
- * a real port on some IP.
- */
-struct rend_service_port_config_s {
-  /* 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];
-};
-
 /* Hidden service directory file names:
  * new file names should be added to rend_service_add_filenames_to_list()
  * for sandboxing purposes. */
@@ -164,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s)
 
 /** Return the number of rendezvous services we have configured. */
 int
-num_rend_services(void)
+rend_num_services(void)
 {
   if (!rend_service_list)
     return 0;
@@ -1694,24 +1678,6 @@ rend_service_get_by_service_id(const char *id)
   return NULL;
 }
 
-/** Return 1 if any virtual port in <b>service</b> wants a circuit
- * to have good uptime. Else return 0.
- */
-static int
-rend_service_requires_uptime(rend_service_t *service)
-{
-  int i;
-  rend_service_port_config_t *p;
-
-  for (i=0; i < smartlist_len(service->ports); ++i) {
-    p = smartlist_get(service->ports, i);
-    if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
-                                  p->virtual_port))
-      return 1;
-  }
-  return 0;
-}
-
 /** Check client authorization of a given <b>descriptor_cookie</b> of
  * length <b>cookie_len</b> for <b>service</b>. Return 1 for success
  * and 0 for failure. */
@@ -2029,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
     goto err;
   }
 
-  circ_needs_uptime = rend_service_requires_uptime(service);
+  circ_needs_uptime = hs_service_requires_uptime_circ(service->ports);
 
   /* help predict this next time */
   rep_hist_note_used_internal(now, circ_needs_uptime, 1);
@@ -2926,29 +2892,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
 
   tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
 
-  /* Don't relaunch the same rend circ twice. */
-  if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) {
-    log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; "
-             "not relaunching it again.",
-             oldcirc->build_state ?
-             safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
-             : "*unknown*");
-    return;
-  }
-  oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1;
-
-  if (!oldcirc->build_state ||
-      oldcirc->build_state->failure_count > MAX_REND_FAILURES ||
-      oldcirc->build_state->expiry_time < time(NULL)) {
-    log_info(LD_REND,
-             "Attempt to build circuit to %s for rendezvous has failed "
-             "too many times or expired; giving up.",
-             oldcirc->build_state ?
-             safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
-             : "*unknown*");
-    return;
-  }
-
   oldstate = oldcirc->build_state;
   tor_assert(oldstate);
 
@@ -3116,10 +3059,11 @@ count_intro_point_circuits(const rend_service_t *service)
    crypto material. On success, fill <b>cell_body_out</b> and return the number
    of bytes written. On fail, return -1.
  */
-STATIC ssize_t
-encode_establish_intro_cell_legacy(char *cell_body_out,
-                                 size_t cell_body_out_len,
-                                 crypto_pk_t *intro_key, char *rend_circ_nonce)
+ssize_t
+rend_service_encode_establish_intro_cell(char *cell_body_out,
+                                         size_t cell_body_out_len,
+                                         crypto_pk_t *intro_key,
+                                         const char *rend_circ_nonce)
 {
   int retval = -1;
   int r;
@@ -3256,7 +3200,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
   /* Send the ESTABLISH_INTRO cell */
   {
     ssize_t len;
-    len = encode_establish_intro_cell_legacy(buf, sizeof(buf),
+    len = rend_service_encode_establish_intro_cell(buf, sizeof(buf),
                                       circuit->intro_key,
                                       circuit->cpath->prev->rend_circ_nonce);
     if (len < 0) {
@@ -3983,10 +3927,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 +3946,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;
@@ -4281,60 +4223,6 @@ rend_service_dump_stats(int severity)
   }
 }
 
-#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 /* HAVE_SYS_UN_H */
-
 /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for
  * 'circ', and look up the port and address based on conn-\>port.
  * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure
@@ -4347,9 +4235,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
 {
   rend_service_t *service;
   char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
-  smartlist_t *matching_ports;
-  rend_service_port_config_t *chosen_port;
-  unsigned int warn_once = 0;
   const char *rend_pk_digest;
 
   tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
@@ -4385,41 +4270,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
       return service->max_streams_close_circuit ? -2 : -1;
     }
   }
-  matching_ports = smartlist_new();
-  SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
-  {
-    if (conn->base_.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 on service "
-              "%s, which is unsupported on this platform. Ignoring it.",
-              conn->base_.port, serviceid);
-        }
-        warn_once++;
-      }
-    }
-  });
-  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(&conn->base_.addr, &chosen_port->real_addr);
-      conn->base_.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);
-      }
-    }
+
+  if (hs_set_conn_addr_port(service->ports, conn) == 0) {
+    /* Successfully set the port to the connection. We are done. */
     return 0;
   }
 

+ 8 - 9
src/or/rendservice.h

@@ -16,9 +16,6 @@
 #include "hs_service.h"
 
 typedef struct rend_intro_cell_s rend_intro_cell_t;
-typedef struct rend_service_port_config_s rend_service_port_config_t;
-
-#ifdef RENDSERVICE_PRIVATE
 
 /* This can be used for both INTRODUCE1 and INTRODUCE2 */
 
@@ -64,6 +61,8 @@ struct rend_intro_cell_s {
   uint8_t dh[DH_KEY_LEN];
 };
 
+#ifdef RENDSERVICE_PRIVATE
+
 /** Represents a single hidden service running at this OP. */
 typedef struct rend_service_t {
   /* Fields specified in config file */
@@ -126,10 +125,6 @@ STATIC int rend_service_verify_single_onion_poison(
 STATIC int rend_service_poison_new_single_onion_dir(
                                                   const rend_service_t *s,
                                                   const or_options_t* options);
-STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out,
-                                                  size_t cell_body_out_len,
-                                                  crypto_pk_t *intro_key,
-                                                  char *rend_circ_nonce);
 #ifdef TOR_UNIT_TESTS
 
 STATIC void set_rend_service_list(smartlist_t *new_list);
@@ -140,7 +135,7 @@ STATIC void rend_service_prune_list_impl_(void);
 
 #endif /* RENDSERVICE_PRIVATE */
 
-int num_rend_services(void);
+int rend_num_services(void);
 int rend_config_service(const config_line_t *line_,
                         const or_options_t *options,
                         hs_service_config_t *config);
@@ -149,7 +144,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);
@@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request,
                                                    char **err_msg_out);
 int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro,
                                        char **err_msg_out);
+ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out,
+                                                 size_t cell_body_out_len,
+                                                 crypto_pk_t *intro_key,
+                                                 const char *rend_circ_nonce);
 int rend_service_validate_intro_late(const rend_intro_cell_t *intro,
                                      char **err_msg_out);
 void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);

+ 8 - 1
src/or/routerparse.c

@@ -2706,7 +2706,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
     rs->supports_ed25519_hs_intro =
       protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4);
     rs->supports_v3_hsdir =
-      protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2);
+      protocol_list_supports_protocol(tok->args[0], PRT_HSDIR,
+                                      PROTOVER_HSDIR_V3);
   }
   if ((tok = find_opt_by_keyword(tokens, K_V))) {
     tor_assert(tok->n_args == 1);
@@ -2718,6 +2719,12 @@ routerstatus_parse_entry_from_string(memarea_t *area,
         tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha");
       rs->protocols_known = 1;
     }
+    if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) {
+      /* Bug #22447 forces us to filter on this version. */
+      if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) {
+        rs->supports_v3_hsdir = 0;
+      }
+    }
     if (vote_rs) {
       vote_rs->version = tor_strdup(tok->args[0]);
     }

+ 46 - 0
src/or/shared_random.c

@@ -1390,6 +1390,52 @@ sr_get_previous_for_control(void)
   return srv_str;
 }
 
+/* Return current shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_current(const networkstatus_t *ns)
+{
+  const networkstatus_t *consensus;
+
+  /* Use provided ns else get a live one */
+  if (ns) {
+    consensus = ns;
+  } else {
+    consensus = networkstatus_get_live_consensus(approx_time());
+  }
+  /* Ideally we would never be asked for an SRV without a live consensus. Make
+   * sure this assumption is correct. */
+  tor_assert_nonfatal(consensus);
+
+  if (consensus) {
+    return consensus->sr_info.current_srv;
+  }
+  return NULL;
+}
+
+/* Return previous shared random value from the latest consensus. Caller can
+ * NOT keep a reference to the returned pointer. Return NULL if none. */
+const sr_srv_t *
+sr_get_previous(const networkstatus_t *ns)
+{
+  const networkstatus_t *consensus;
+
+  /* Use provided ns else get a live one */
+  if (ns) {
+    consensus = ns;
+  } else {
+    consensus = networkstatus_get_live_consensus(approx_time());
+  }
+  /* Ideally we would never be asked for an SRV without a live consensus. Make
+   * sure this assumption is correct. */
+  tor_assert_nonfatal(consensus);
+
+  if (consensus) {
+    return consensus->sr_info.previous_srv;
+  }
+  return NULL;
+}
+
 #ifdef TOR_UNIT_TESTS
 
 /* Set the global value of number of SRV agreements so the test can play

+ 3 - 0
src/or/shared_random.h

@@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp,
 char *sr_get_current_for_control(void);
 char *sr_get_previous_for_control(void);
 
+const sr_srv_t *sr_get_current(const networkstatus_t *ns);
+const sr_srv_t *sr_get_previous(const networkstatus_t *ns);
+
 #ifdef SHARED_RANDOM_PRIVATE
 
 /* Encode */

+ 37 - 1
src/or/shared_random_state.c

@@ -133,7 +133,7 @@ get_voting_interval(void)
 /* Given the time <b>now</b>, return the start time of the current round of
  * the SR protocol. For example, if it's 23:47:08, the current round thus
  * started at 23:47:00 for a voting interval of 10 seconds. */
-static time_t
+STATIC time_t
 get_start_time_of_current_round(time_t now)
 {
   const or_options_t *options = get_options();
@@ -156,6 +156,42 @@ get_start_time_of_current_round(time_t now)
   return curr_start;
 }
 
+/** Return the start time of the current SR protocol run. For example, if the
+ *  time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this
+ *  function should return 23/06/2017 00:00:00. */
+time_t
+sr_state_get_start_time_of_current_protocol_run(time_t now)
+{
+  int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+  int voting_interval = get_voting_interval();
+  /* Find the time the current round started. */
+  time_t beginning_of_current_round = get_start_time_of_current_round(now);
+
+  /* Get current SR protocol round */
+  int current_round = (now / voting_interval) % total_rounds;
+
+  /* Get start time by subtracting the time elapsed from the beginning of the
+     protocol run */
+  time_t time_elapsed_since_start_of_run = current_round * voting_interval;
+  return beginning_of_current_round - time_elapsed_since_start_of_run;
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol phase
+ *  (e.g. the commit phase). */
+unsigned int
+sr_state_get_phase_duration(void)
+{
+  return SHARED_RANDOM_N_ROUNDS * get_voting_interval();
+}
+
+/** Return the time (in seconds) it takes to complete a full SR protocol run */
+unsigned int
+sr_state_get_protocol_run_duration(void)
+{
+  int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+  return total_protocol_rounds * get_voting_interval();
+}
+
 /* Return the time we should expire the state file created at <b>now</b>.
  * We expire the state file in the beginning of the next protocol run. */
 STATIC time_t

+ 5 - 0
src/or/shared_random_state.h

@@ -121,11 +121,16 @@ int sr_state_is_initialized(void);
 void sr_state_save(void);
 void sr_state_free(void);
 
+time_t sr_state_get_start_time_of_current_protocol_run(time_t now);
+unsigned int sr_state_get_phase_duration(void);
+unsigned int sr_state_get_protocol_run_duration(void);
+
 #ifdef SHARED_RANDOM_STATE_PRIVATE
 
 STATIC int disk_state_load_from_disk_impl(const char *fname);
 
 STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after);
+STATIC time_t get_start_time_of_current_round(time_t now);
 
 STATIC time_t get_state_valid_until_time(time_t now);
 STATIC const char *get_phase_str(sr_phase_t phase);

+ 2 - 0
src/or/statefile.c

@@ -85,6 +85,8 @@ static config_var_t state_vars_[] = {
   VAR("TransportProxy",               LINELIST_S, TransportProxies, NULL),
   V(TransportProxies,                 LINELIST_V, NULL),
 
+  V(HidServRevCounter,            LINELIST, NULL),
+
   V(BWHistoryReadEnds,                ISOTIME,  NULL),
   V(BWHistoryReadInterval,            UINT,     "900"),
   V(BWHistoryReadValues,              CSV,      ""),

+ 2 - 4
src/test/ed25519_exts_ref.py

@@ -32,8 +32,7 @@ def curve25519ToEd25519(c, sign):
     return encodepoint([x,y])
 
 def blindESK(esk, param):
-    h = H("Derive temporary signing key" + param)
-    mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
+    mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
     s = decodeint(esk[:32])
     s_prime = (s * mult) % ell
     k = esk[32:]
@@ -42,8 +41,7 @@ def blindESK(esk, param):
     return encodeint(s_prime) + k_prime
 
 def blindPK(pk, param):
-    h = H("Derive temporary signing key" + param)
-    mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
+    mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
     P = decodepoint(pk)
     return encodepoint(scalarmult(P, mult))
 

+ 16 - 16
src/test/ed25519_vectors.inc

@@ -91,21 +91,21 @@ static const char *ED25519_BLINDING_PARAMS[] = {
  * blinding parameter.
  */
 static const char *ED25519_BLINDED_SECRET_KEYS[] = {
-  "014e83abadb2ca9a27e0ffe23920333d817729f48700e97656ec2823d694050e171d43"
+  "293c3acff4e902f6f63ddc5d5caa2a57e771db4f24de65d4c28df3232f47fa01171d43"
       "f24e3f53e70ec7ac280044ac77d4942dee5d6807118a59bdf3ee647e89",
-  "fad8cca0b4335847795288b1452508752b253e64e6c7c78d4a02dbbd7d46aa0eb8ceff"
+  "38b88f9f9440358da544504ee152fb475528f7c51c285bd1c68b14ade8e29a07b8ceff"
       "20dfcf53eb52b891fc078c934efbf0353af7242e7dc51bb32a093afa29",
-  "116eb0ae0a4a91763365bdf86db427b00862db448487808788cc339ac10e5e089217f5"
+  "4d03ce16a3f3249846aac9de0a0075061495c3b027248eeee47da4ddbaf9e0049217f5"
       "2e92797462bd890fc274672e05c98f2c82970d640084781334aae0f940",
-  "bd1fbb0ee5acddc4adbcf5f33e95d9445f40326ce579fdd764a24483a9ccb20f509ece"
+  "51d7db01aaa0d937a9fd7c8c7381445a14d8fa61f43347af5460d7cd8fda9904509ece"
       "e77082ce088f7c19d5a00e955eeef8df6fa41686abc1030c2d76807733",
-  "237f5345cefe8573ce9fa7e216381a1172796c9e3f70668ab503b1352952530fb57b95"
+  "1f76cab834e222bd2546efa7e073425680ab88df186ff41327d3e40770129b00b57b95"
       "a440570659a440a3e4771465022a8e67af86bdf2d0990c54e7bb87ff9a",
-  "ba8ff23bc4ad2b739e1ccffc9fbc7837053ea81cdfdb15073f56411cfbae1d0ec492fc"
+  "c23588c23ee76093419d07b27c6df5922a03ac58f96c53671456a7d1bdbf560ec492fc"
       "87d5ec2a1b185ca5a40541fdef0b1e128fd5c2380c888bfa924711bcab",
-  "0fa68f969de038c7a90a4a74ee6167c77582006f2dedecc1956501ba6b6fb10391b476"
+  "3ed249c6932d076e1a2f6916975914b14e8c739da00992358b8f37d3e790650691b476"
       "8f8e556d78f4bdcb9a13b6f6066fe81d3134ae965dc48cd0785b3af2b8",
-  "deaa3456d1c21944d5dcd361a646858c6cf9336b0a6851d925717eb1ae186902053d9c"
+  "288cbfd923cb286d48c084555b5bdd06c05e92fb81acdb45271367f57515380e053d9c"
       "00c81e1331c06ab50087be8cfc7dc11691b132614474f1aa9c2503cccd",
 };
 
@@ -115,14 +115,14 @@ static const char *ED25519_BLINDED_SECRET_KEYS[] = {
  * blinding parameter.
  */
 static const char *ED25519_BLINDED_PUBLIC_KEYS[] = {
-  "722d6da6348e618967ef782e71061e27163a8b35f21856475d9d2023f65b6495",
-  "1dffa0586da6cbfcff2024eedf4fc6c818242d9a82dbbe635d6da1b975a1160d",
-  "5ed81f98fed5a6acda4ea6da2c34fab0ab359d950c510c256473f1f33ff438b4",
-  "6e6f92a54fb282120c46d9603df41135f025bc1f58f283809d04be96aeb04040",
-  "cda236f28edc4c7e02d18007b8dab49d669265b0f7aefb1824d7cc8e73a2cd63",
-  "367b03b17b67ca7329b89a520bdab91782402a41cd67264e34b5541a4b3f875b",
-  "8d486b03ac4e3b486b7a1d563706c7fdac75aee789a7cf6f22789eedeff61a31",
-  "9f297ff0aa2ceda91c5ab1b6446f12533d145940de6d850dc323417afde0cb78",
+  "1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41",
+  "1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e",
+  "c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028",
+  "3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f",
+  "59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d",
+  "2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9",
+  "c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e",
+  "0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac",
 };
 
 /**

+ 24 - 6
src/test/hs_test_helpers.c

@@ -6,6 +6,7 @@
 #include "test.h"
 #include "torcert.h"
 
+#include "hs_common.h"
 #include "hs_test_helpers.h"
 
 hs_desc_intro_point_t *
@@ -15,8 +16,7 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
   int ret;
   ed25519_keypair_t auth_kp;
   hs_desc_intro_point_t *intro_point = NULL;
-  hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
-  ip->link_specifiers = smartlist_new();
+  hs_desc_intro_point_t *ip = hs_desc_intro_point_new();
 
   {
     hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls));
@@ -94,8 +94,7 @@ static hs_descriptor_t *
 hs_helper_build_hs_desc_impl(unsigned int no_ip,
                              const ed25519_keypair_t *signing_kp)
 {
-  int ret;
-  time_t now = time(NULL);
+  time_t now = approx_time();
   ed25519_keypair_t blinded_kp;
   hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc));
 
@@ -105,8 +104,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
   memcpy(&desc->plaintext_data.signing_pubkey, &signing_kp->pubkey,
          sizeof(ed25519_public_key_t));
 
-  ret = ed25519_keypair_generate(&blinded_kp, 0);
-  tt_int_op(ret, ==, 0);
+  uint64_t current_time_period = hs_get_time_period_num(approx_time());
+  hs_build_blinded_keypair(signing_kp, NULL, 0,
+                           current_time_period, &blinded_kp);
   /* Copy only the public key into the descriptor. */
   memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey,
          sizeof(ed25519_public_key_t));
@@ -119,6 +119,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
   desc->plaintext_data.revision_counter = 42;
   desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
 
+  hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
+                    desc->subcredential);
+
   /* Setup encrypted data section. */
   desc->encrypted_data.create2_ntor = 1;
   desc->encrypted_data.intro_auth_types = smartlist_new();
@@ -142,6 +145,21 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
   return descp;
 }
 
+/** Helper function to get the HS subcredential using the identity keypair of
+ *  an HS. Used to decrypt descriptors in unittests. */
+void
+hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
+                                            uint8_t *subcred_out)
+{
+  ed25519_keypair_t blinded_kp;
+  uint64_t current_time_period = hs_get_time_period_num(approx_time());
+  hs_build_blinded_keypair(signing_kp, NULL, 0,
+                           current_time_period, &blinded_kp);
+
+  hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
+                       subcred_out);
+}
+
 /* Build a descriptor with introduction points. */
 hs_descriptor_t *
 hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp)

+ 3 - 0
src/test/hs_test_helpers.h

@@ -17,6 +17,9 @@ hs_descriptor_t *hs_helper_build_hs_desc_with_ip(
                                  const ed25519_keypair_t *signing_kp);
 void hs_helper_desc_equal(const hs_descriptor_t *desc1,
                           const hs_descriptor_t *desc2);
+void
+hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
+                                            uint8_t *subcred_out);
 
 #endif /* TOR_HS_TEST_HELPERS_H */
 

+ 3 - 0
src/test/include.am

@@ -115,7 +115,10 @@ src_test_test_SOURCES = \
 	src/test/test_guardfraction.c \
 	src/test/test_extorport.c \
 	src/test/test_hs.c \
+	src/test/test_hs_common.c \
 	src/test/test_hs_config.c \
+	src/test/test_hs_cell.c \
+	src/test/test_hs_ntor.c \
 	src/test/test_hs_service.c \
 	src/test/test_hs_client.c  \
 	src/test/test_hs_intropoint.c \

+ 3 - 0
src/test/test.c

@@ -1214,8 +1214,11 @@ struct testgroup_t testgroups[] = {
   { "extorport/", extorport_tests },
   { "legacy_hs/", hs_tests },
   { "hs_cache/", hs_cache },
+  { "hs_cell/", hs_cell_tests },
+  { "hs_common/", hs_common_tests },
   { "hs_config/", hs_config_tests },
   { "hs_descriptor/", hs_descriptor },
+  { "hs_ntor/", hs_ntor_tests },
   { "hs_service/", hs_service_tests },
   { "hs_client/", hs_client_tests },
   { "hs_intropoint/", hs_intropoint_tests },

+ 3 - 0
src/test/test.h

@@ -207,8 +207,11 @@ extern struct testcase_t guardfraction_tests[];
 extern struct testcase_t extorport_tests[];
 extern struct testcase_t hs_tests[];
 extern struct testcase_t hs_cache[];
+extern struct testcase_t hs_cell_tests[];
+extern struct testcase_t hs_common_tests[];
 extern struct testcase_t hs_config_tests[];
 extern struct testcase_t hs_descriptor[];
+extern struct testcase_t hs_ntor_tests[];
 extern struct testcase_t hs_service_tests[];
 extern struct testcase_t hs_client_tests[];
 extern struct testcase_t hs_intropoint_tests[];

+ 6 - 2
src/test/test_hs_cache.c

@@ -342,6 +342,7 @@ test_hsdir_revision_counter_check(void *arg)
   hs_descriptor_t *published_desc = NULL;
   char *published_desc_str = NULL;
 
+  uint8_t subcredential[DIGEST256_LEN];
   char *received_desc_str = NULL;
   hs_descriptor_t *received_desc = NULL;
 
@@ -378,9 +379,11 @@ test_hsdir_revision_counter_check(void *arg)
     const ed25519_public_key_t *blinded_key;
 
     blinded_key = &published_desc->plaintext_data.blinded_pubkey;
+    hs_get_subcredential(&signing_kp.pubkey, blinded_key, subcredential);
     received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
 
-    retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc);
+    retval = hs_desc_decode_descriptor(received_desc_str,
+                                       subcredential, &received_desc);
     tt_int_op(retval, ==, 0);
     tt_assert(received_desc);
 
@@ -412,7 +415,8 @@ test_hsdir_revision_counter_check(void *arg)
     blinded_key = &published_desc->plaintext_data.blinded_pubkey;
     received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
 
-    retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc);
+    retval = hs_desc_decode_descriptor(received_desc_str,
+                                       subcredential, &received_desc);
     tt_int_op(retval, ==, 0);
     tt_assert(received_desc);
 

+ 130 - 0
src/test/test_hs_cell.c

@@ -0,0 +1,130 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_cell.c
+ * \brief Test hidden service cell functionality.
+ */
+
+#define HS_INTROPOINT_PRIVATE
+#define HS_SERVICE_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+
+#include "crypto_ed25519.h"
+#include "hs_cell.h"
+#include "hs_intropoint.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "hs/cell_establish_intro.h"
+
+/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we
+ *  parse it from the receiver side. */
+static void
+test_gen_establish_intro_cell(void *arg)
+{
+  (void) arg;
+  ssize_t ret;
+  char circ_nonce[DIGEST_LEN] = {0};
+  uint8_t buf[RELAY_PAYLOAD_SIZE];
+  trn_cell_establish_intro_t *cell_in = NULL;
+
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+
+  /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
+     attempt to parse it. */
+  {
+    /* We only need the auth key pair here. */
+    hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0);
+    /* Auth key pair is generated in the constructor so we are all set for
+     * using this IP object. */
+    ret = hs_cell_build_establish_intro(circ_nonce, ip, buf);
+    service_intro_point_free(ip);
+    tt_u64_op(ret, OP_GT, 0);
+  }
+
+  /* Check the contents of the cell */
+  {
+    /* First byte is the auth key type: make sure its correct */
+    tt_int_op(buf[0], OP_EQ, HS_INTRO_AUTH_KEY_TYPE_ED25519);
+    /* Next two bytes is auth key len */
+    tt_int_op(ntohs(get_uint16(buf+1)), OP_EQ, ED25519_PUBKEY_LEN);
+    /* Skip to the number of extensions: no extensions */
+    tt_int_op(buf[35], OP_EQ, 0);
+    /* Skip to the sig len. Make sure it's the size of an ed25519 sig */
+    tt_int_op(ntohs(get_uint16(buf+35+1+32)), OP_EQ, ED25519_SIG_LEN);
+  }
+
+  /* Parse it as the receiver */
+  {
+    ret = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf));
+    tt_u64_op(ret, OP_GT, 0);
+
+    ret = verify_establish_intro_cell(cell_in,
+                                      (const uint8_t *) circ_nonce,
+                                      sizeof(circ_nonce));
+    tt_u64_op(ret, OP_EQ, 0);
+  }
+
+ done:
+  trn_cell_establish_intro_free(cell_in);
+}
+
+/* Mocked ed25519_sign_prefixed() function that always fails :) */
+static int
+mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out,
+                           const uint8_t *msg, size_t msg_len,
+                           const char *prefix_str,
+                           const ed25519_keypair_t *keypair) {
+  (void) signature_out;
+  (void) msg;
+  (void) msg_len;
+  (void) prefix_str;
+  (void) keypair;
+  return -1;
+}
+
+/** We simulate a failure to create an ESTABLISH_INTRO cell */
+static void
+test_gen_establish_intro_cell_bad(void *arg)
+{
+  (void) arg;
+  ssize_t cell_len = 0;
+  trn_cell_establish_intro_t *cell = NULL;
+  char circ_nonce[DIGEST_LEN] = {0};
+  hs_service_intro_point_t *ip = NULL;
+
+  MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
+
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+
+  setup_full_capture_of_logs(LOG_WARN);
+  /* Easiest way to make that function fail is to mock the
+     ed25519_sign_prefixed() function and make it fail. */
+  cell = trn_cell_establish_intro_new();
+  tt_assert(cell);
+  ip = service_intro_point_new(NULL, 0);
+  cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL);
+  service_intro_point_free(ip);
+  expect_log_msg_containing("Unable to make signature for "
+                            "ESTABLISH_INTRO cell.");
+  teardown_capture_of_logs();
+  tt_i64_op(cell_len, OP_EQ, -1);
+
+ done:
+  trn_cell_establish_intro_free(cell);
+  UNMOCK(ed25519_sign_prefixed);
+}
+
+struct testcase_t hs_cell_tests[] = {
+  { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
+    NULL, NULL },
+  { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK,
+    NULL, NULL },
+
+  END_OF_TESTCASES
+};
+

+ 507 - 0
src/test/test_hs_common.c

@@ -0,0 +1,507 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_common.c
+ * \brief Test hidden service common functionalities.
+ */
+
+#define HS_COMMON_PRIVATE
+#define HS_SERVICE_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+#include "hs_test_helpers.h"
+
+#include "hs_common.h"
+#include "hs_service.h"
+#include "config.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+
+/** Test the validation of HS v3 addresses */
+static void
+test_validate_address(void *arg)
+{
+  int ret;
+
+  (void) arg;
+
+  /* Address too short and too long. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_address_is_valid("blah");
+  tt_int_op(ret, OP_EQ, 0);
+  expect_log_msg_containing("has an invalid length");
+  teardown_capture_of_logs();
+
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_address_is_valid(
+           "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb");
+  tt_int_op(ret, OP_EQ, 0);
+  expect_log_msg_containing("has an invalid length");
+  teardown_capture_of_logs();
+
+  /* Invalid checksum (taken from prop224) */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_address_is_valid(
+           "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad");
+  tt_int_op(ret, OP_EQ, 0);
+  expect_log_msg_containing("invalid checksum");
+  teardown_capture_of_logs();
+
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_address_is_valid(
+           "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad");
+  tt_int_op(ret, OP_EQ, 0);
+  expect_log_msg_containing("invalid checksum");
+  teardown_capture_of_logs();
+
+  /* Non base32 decodable string. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_address_is_valid(
+           "????????????????????????????????????????????????????????");
+  tt_int_op(ret, OP_EQ, 0);
+  expect_log_msg_containing("can't be decoded");
+  teardown_capture_of_logs();
+
+  /* Valid address. */
+  ret = hs_address_is_valid(
+           "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
+  tt_int_op(ret, OP_EQ, 1);
+
+ done:
+  ;
+}
+
+static int
+mock_write_str_to_file(const char *path, const char *str, int bin)
+{
+  (void)bin;
+  tt_str_op(path, OP_EQ, "/double/five/squared");
+  tt_str_op(str, OP_EQ,
+           "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid.onion\n");
+
+ done:
+  return 0;
+}
+
+/** Test building HS v3 onion addresses */
+static void
+test_build_address(void *arg)
+{
+  int ret;
+  char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+  ed25519_public_key_t pubkey;
+  hs_service_t *service = NULL;
+
+  (void) arg;
+
+  MOCK(write_str_to_file, mock_write_str_to_file);
+
+  /* The following has been created with hs_build_address.py script that
+   * follows proposal 224 specification to build an onion address. */
+  static const char *test_addr =
+    "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid";
+
+  /* Let's try to build the same onion address that the script can do. Key is
+   * a long set of very random \x42 :). */
+  memset(&pubkey, '\x42', sizeof(pubkey));
+  hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr);
+  tt_str_op(test_addr, OP_EQ, onion_addr);
+  /* Validate that address. */
+  ret = hs_address_is_valid(onion_addr);
+  tt_int_op(ret, OP_EQ, 1);
+
+  service = tor_malloc_zero(sizeof(hs_service_t));
+  memcpy(service->onion_address, onion_addr, sizeof(service->onion_address));
+  tor_asprintf(&service->config.directory_path, "/double/five");
+  ret = write_address_to_file(service, "squared");
+  tt_int_op(ret, OP_EQ, 0);
+
+ done:
+  hs_service_free(service);
+}
+
+/** Test that our HS time period calculation functions work properly */
+static void
+test_time_period(void *arg)
+{
+  (void) arg;
+  uint64_t tn;
+  int retval;
+  time_t fake_time, correct_time, start_time;
+
+  /* Let's do the example in prop224 section [TIME-PERIODS] */
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC",
+                              &fake_time);
+  tt_int_op(retval, ==, 0);
+
+  /* Check that the time period number is right */
+  tn = hs_get_time_period_num(fake_time);
+  tt_u64_op(tn, ==, 16903);
+
+  /* Increase current time to 11:59:59 UTC and check that the time period
+     number is still the same */
+  fake_time += 3599;
+  tn = hs_get_time_period_num(fake_time);
+  tt_u64_op(tn, ==, 16903);
+
+  { /* Check start time of next time period */
+    retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC",
+                                &correct_time);
+    tt_int_op(retval, ==, 0);
+
+    start_time = hs_get_start_time_of_next_time_period(fake_time);
+    tt_int_op(start_time, OP_EQ, correct_time);
+  }
+
+  /* Now take time to 12:00:00 UTC and check that the time period rotated */
+  fake_time += 1;
+  tn = hs_get_time_period_num(fake_time);
+  tt_u64_op(tn, ==, 16904);
+
+  /* Now also check our hs_get_next_time_period_num() function */
+  tn = hs_get_next_time_period_num(fake_time);
+  tt_u64_op(tn, ==, 16905);
+
+  { /* Check start time of next time period again */
+    retval = parse_rfc1123_time("Wed, 14 Apr 2016 12:00:00 UTC",
+                                &correct_time);
+    tt_int_op(retval, ==, 0);
+
+    start_time = hs_get_start_time_of_next_time_period(fake_time);
+    tt_int_op(start_time, OP_EQ, correct_time);
+  }
+
+  /* Now do another sanity check: The time period number at the start of the
+   * next time period, must be the same time period number as the one returned
+   * from hs_get_next_time_period_num() */
+  {
+    time_t next_tp_start = hs_get_start_time_of_next_time_period(fake_time);
+    tt_int_op(hs_get_time_period_num(next_tp_start), OP_EQ,
+              hs_get_next_time_period_num(fake_time));
+  }
+
+ done:
+  ;
+}
+
+/** Test that we can correctly find the start time of the next time period */
+static void
+test_start_time_of_next_time_period(void *arg)
+{
+  (void) arg;
+  int retval;
+  time_t fake_time;
+  char tbuf[ISO_TIME_LEN + 1];
+  time_t next_tp_start_time;
+
+  /* Do some basic tests */
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC",
+                              &fake_time);
+  tt_int_op(retval, ==, 0);
+  next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
+  /* Compare it with the correct result */
+  format_iso_time(tbuf, next_tp_start_time);
+  tt_str_op("2016-04-13 12:00:00", OP_EQ, tbuf);
+
+  /* Another test with an edge-case time (start of TP) */
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC",
+                              &fake_time);
+  tt_int_op(retval, ==, 0);
+  next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
+  format_iso_time(tbuf, next_tp_start_time);
+  tt_str_op("2016-04-14 12:00:00", OP_EQ, tbuf);
+
+  {
+    /* Now pretend we are on a testing network and alter the voting schedule to
+       be every 10 seconds. This means that a time period has length 10*24
+       seconds (4 minutes). It also means that we apply a rotational offset of
+       120 seconds to the time period, so that it starts at 00:02:00 instead of
+       00:00:00. */
+    or_options_t *options = get_options_mutable();
+    options->TestingTorNetwork = 1;
+    options->V3AuthVotingInterval = 10;
+    options->TestingV3AuthInitialVotingInterval = 10;
+
+    retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
+                                &fake_time);
+    tt_int_op(retval, ==, 0);
+    next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, next_tp_start_time);
+    tt_str_op("2016-04-13 00:02:00", OP_EQ, tbuf);
+
+    retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC",
+                                &fake_time);
+    tt_int_op(retval, ==, 0);
+    next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, next_tp_start_time);
+    tt_str_op("2016-04-13 00:06:00", OP_EQ, tbuf);
+  }
+
+ done:
+  ;
+}
+
+/** Test that our HS overlap period functions work properly. */
+static void
+test_desc_overlap_period(void *arg)
+{
+  (void) arg;
+  int retval;
+  time_t now = time(NULL);
+  networkstatus_t *dummy_consensus = NULL;
+
+  /* First try with a consensus just inside the overlap period */
+  dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t));
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap
+     period is still active. */
+  dummy_consensus->valid_after += 3600*11;
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  /* Now increase the valid_after so that it goes to 11:59:59 UTC. Overlap
+     period is still active. */
+  dummy_consensus->valid_after += 3599;
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  /* Now increase the valid_after so that it drifts to noon, and check that
+     overlap mode is not active anymore. */
+  dummy_consensus->valid_after += 1;
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 0);
+
+  /* Check that overlap mode is also inactive at 23:59:59 UTC */
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 23:59:59 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 0);
+
+ done:
+  tor_free(dummy_consensus);
+}
+
+/* Test the overlap period functions on a testnet with altered voting
+ * schedule */
+static void
+test_desc_overlap_period_testnet(void *arg)
+{
+  int retval;
+  time_t now = approx_time();
+  networkstatus_t *dummy_consensus = NULL;
+  or_options_t *options = get_options_mutable();
+
+  (void) arg;
+
+  /* Set the testnet option and a 10-second voting interval */
+  options->TestingTorNetwork = 1;
+  options->V3AuthVotingInterval = 10;
+  options->TestingV3AuthInitialVotingInterval = 10;
+
+  dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t));
+
+  /* A 10-second voting interval means that the lengths of an SRV run and of a
+   * time period are both 10*24 seconds (4 minutes). The SRV gets published at
+   * 00:00:00 and the TP starts at 00:02:00 (rotation offset: 2 mins). Those
+   * two minutes between SRV publish and TP start is the overlap period
+   * window. Let's test it: */
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:01:59 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 0);
+
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:04:00 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:05:59 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 1);
+
+  retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:06:00 UTC",
+                              &dummy_consensus->valid_after);
+  tt_int_op(retval, ==, 0);
+  retval = hs_overlap_mode_is_active(dummy_consensus, now);
+  tt_int_op(retval, ==, 0);
+
+ done:
+  tor_free(dummy_consensus);
+}
+
+static networkstatus_t *mock_ns = NULL;
+
+static networkstatus_t *
+mock_networkstatus_get_latest_consensus(void)
+{
+  time_t now = approx_time();
+
+  /* If initialized, return it */
+  if (mock_ns) {
+    return mock_ns;
+  }
+
+  /* Initialize fake consensus */
+  mock_ns = tor_malloc_zero(sizeof(networkstatus_t));
+
+  /* This consensus is live */
+  mock_ns->valid_after = now-1;
+  mock_ns->fresh_until = now+1;
+  mock_ns->valid_until = now+2;
+  /* Create routerstatus list */
+  mock_ns->routerstatus_list = smartlist_new();
+
+  return mock_ns;
+}
+
+/** Test the responsible HSDirs calculation function */
+static void
+test_responsible_hsdirs(void *arg)
+{
+  time_t now = approx_time();
+  smartlist_t *responsible_dirs = smartlist_new();
+  networkstatus_t *ns = NULL;
+  routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
+
+  (void) arg;
+
+  hs_init();
+
+  MOCK(networkstatus_get_latest_consensus,
+       mock_networkstatus_get_latest_consensus);
+
+  ns = networkstatus_get_latest_consensus();
+
+  { /* First router: HSdir */
+    tor_addr_t ipv4_addr;
+    memset(rs->identity_digest, 'A', DIGEST_LEN);
+    rs->is_hs_dir = 1;
+    rs->supports_v3_hsdir = 1;
+    routerinfo_t ri;
+    memset(&ri, 0 ,sizeof(routerinfo_t));
+    tor_addr_parse(&ipv4_addr, "127.0.0.1");
+    ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
+    ri.nickname = tor_strdup("fatal");
+    ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3";
+    memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
+    tt_assert(nodelist_set_routerinfo(&ri, NULL));
+    node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest);
+    memset(node->hsdir_index->current, 'Z',
+           sizeof(node->hsdir_index->current));
+    smartlist_add(ns->routerstatus_list, rs);
+  }
+
+  ed25519_public_key_t blinded_pk;
+  uint64_t time_period_num = hs_get_time_period_num(now);
+  hs_get_responsible_hsdirs(&blinded_pk, time_period_num,
+                            0, 0, responsible_dirs);
+  tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1);
+
+  /** TODO: Build a bigger network and do more tests here */
+
+ done:
+  routerstatus_free(rs);
+  smartlist_free(responsible_dirs);
+  smartlist_clear(ns->routerstatus_list);
+  networkstatus_vote_free(mock_ns);
+}
+
+/** Test disaster SRV computation and caching */
+static void
+test_disaster_srv(void *arg)
+{
+  uint8_t *cached_disaster_srv_one = NULL;
+  uint8_t *cached_disaster_srv_two = NULL;
+  uint8_t srv_one[DIGEST256_LEN] = {0};
+  uint8_t srv_two[DIGEST256_LEN] = {0};
+  uint8_t srv_three[DIGEST256_LEN] = {0};
+  uint8_t srv_four[DIGEST256_LEN] = {0};
+  uint8_t srv_five[DIGEST256_LEN] = {0};
+
+  (void) arg;
+
+  /* Get the cached SRVs: we gonna use them later for verification */
+  cached_disaster_srv_one = get_first_cached_disaster_srv();
+  cached_disaster_srv_two = get_second_cached_disaster_srv();
+
+  /* Compute some srvs */
+  get_disaster_srv(1, srv_one);
+  get_disaster_srv(2, srv_two);
+
+  /* Check that the cached ones where updated */
+  tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN);
+  tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
+
+  /* Ask for an SRV that has already been computed */
+  get_disaster_srv(2, srv_two);
+  /* and check that the cache entries have not changed */
+  tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN);
+  tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
+
+  /* Ask for a new SRV */
+  get_disaster_srv(3, srv_three);
+  tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN);
+  tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
+
+  /* Ask for another SRV: none of the original SRVs should now be cached */
+  get_disaster_srv(4, srv_four);
+  tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN);
+  tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN);
+
+  /* Ask for yet another SRV */
+  get_disaster_srv(5, srv_five);
+  tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_five, DIGEST256_LEN);
+  tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN);
+
+ done:
+  ;
+}
+
+struct testcase_t hs_common_tests[] = {
+  { "build_address", test_build_address, TT_FORK,
+    NULL, NULL },
+  { "validate_address", test_validate_address, TT_FORK,
+    NULL, NULL },
+  { "time_period", test_time_period, TT_FORK,
+    NULL, NULL },
+  { "start_time_of_next_time_period", test_start_time_of_next_time_period,
+    TT_FORK, NULL, NULL },
+  { "desc_overlap_period", test_desc_overlap_period, TT_FORK,
+    NULL, NULL },
+  { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK,
+    NULL, NULL },
+  { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
+    NULL, NULL },
+  { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL },
+
+  END_OF_TESTCASES
+};
+

+ 1 - 1
src/test/test_hs_config.c

@@ -453,7 +453,7 @@ test_staging_service_v3(void *arg)
   /* Ok, we have a service in our map! Registration went well. */
   tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
   /* Make sure we don't have a magic v2 service out of this. */
-  tt_int_op(num_rend_services(), OP_EQ, 0);
+  tt_int_op(rend_num_services(), OP_EQ, 0);
 
  done:
   hs_free_all();

+ 12 - 6
src/test/test_hs_descriptor.c

@@ -296,6 +296,7 @@ test_decode_descriptor(void *arg)
   hs_descriptor_t *desc = NULL;
   hs_descriptor_t *decoded = NULL;
   hs_descriptor_t *desc_no_ip = NULL;
+  uint8_t subcredential[DIGEST256_LEN];
 
   (void) arg;
 
@@ -303,15 +304,18 @@ test_decode_descriptor(void *arg)
   tt_int_op(ret, ==, 0);
   desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
 
+  hs_helper_get_subcred_from_identity_keypair(&signing_kp,
+                                              subcredential);
+
   /* Give some bad stuff to the decoding function. */
-  ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded);
+  ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded);
   tt_int_op(ret, OP_EQ, -1);
 
   ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
   tt_int_op(ret, ==, 0);
   tt_assert(encoded);
 
-  ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
+  ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
   tt_int_op(ret, ==, 0);
   tt_assert(decoded);
 
@@ -322,6 +326,8 @@ test_decode_descriptor(void *arg)
     ed25519_keypair_t signing_kp_no_ip;
     ret = ed25519_keypair_generate(&signing_kp_no_ip, 0);
     tt_int_op(ret, ==, 0);
+    hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip,
+                                                subcredential);
     desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip);
     tt_assert(desc_no_ip);
     tor_free(encoded);
@@ -329,7 +335,7 @@ test_decode_descriptor(void *arg)
     tt_int_op(ret, ==, 0);
     tt_assert(encoded);
     hs_descriptor_free(decoded);
-    ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
+    ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
     tt_int_op(ret, ==, 0);
     tt_assert(decoded);
   }
@@ -427,7 +433,7 @@ test_decode_invalid_intro_point(void *arg)
     const char *junk = "this is not a descriptor";
     ip = decode_introduction_point(desc, junk);
     tt_assert(!ip);
-    desc_intro_point_free(ip);
+    hs_desc_intro_point_free(ip);
     ip = NULL;
   }
 
@@ -445,7 +451,7 @@ test_decode_invalid_intro_point(void *arg)
     tt_assert(!ip);
     tor_free(encoded_ip);
     smartlist_free(lines);
-    desc_intro_point_free(ip);
+    hs_desc_intro_point_free(ip);
     ip = NULL;
   }
 
@@ -545,7 +551,7 @@ test_decode_invalid_intro_point(void *arg)
 
  done:
   hs_descriptor_free(desc);
-  desc_intro_point_free(ip);
+  hs_desc_intro_point_free(ip);
 }
 
 static void

+ 163 - 134
src/test/test_hs_intropoint.c

@@ -17,21 +17,66 @@
 #include "log_test_helpers.h"
 
 #include "or.h"
+#include "circuitlist.h"
+#include "circuituse.h"
 #include "ht.h"
+#include "relay.h"
+#include "rendservice.h"
+
+#include "hs_cell.h"
+#include "hs_circuitmap.h"
+#include "hs_common.h"
+#include "hs_intropoint.h"
+#include "hs_service.h"
 
 /* Trunnel. */
 #include "hs/cell_establish_intro.h"
 #include "hs/cell_introduce1.h"
 #include "hs/cell_common.h"
-#include "hs_service.h"
-#include "hs_common.h"
-#include "hs_circuitmap.h"
-#include "hs_intropoint.h"
 
-#include "circuitlist.h"
-#include "circuituse.h"
-#include "rendservice.h"
-#include "relay.h"
+static size_t
+new_establish_intro_cell(const char *circ_nonce,
+                         trn_cell_establish_intro_t **cell_out)
+{
+  ssize_t cell_len = 0;
+  uint8_t buf[RELAY_PAYLOAD_SIZE] = {0};
+  trn_cell_establish_intro_t *cell = NULL;
+  hs_service_intro_point_t *ip = NULL;
+
+  /* Auth key pair is generated in the constructor so we are all set for
+   * using this IP object. */
+  ip = service_intro_point_new(NULL, 0);
+  tt_assert(ip);
+  cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf);
+  tt_u64_op(cell_len, OP_GT, 0);
+
+  cell_len = trn_cell_establish_intro_parse(&cell, buf, sizeof(buf));
+  tt_int_op(cell_len, OP_GT, 0);
+  tt_assert(cell);
+  *cell_out = cell;
+
+ done:
+  service_intro_point_free(ip);
+  return cell_len;
+}
+
+static ssize_t
+new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out)
+{
+  ssize_t cell_len = 0;
+  hs_service_intro_point_t *ip = NULL;
+
+  /* Auth key pair is generated in the constructor so we are all set for
+   * using this IP object. */
+  ip = service_intro_point_new(NULL, 0);
+  tt_assert(ip);
+  cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out);
+  tt_u64_op(cell_len, OP_GT, 0);
+
+ done:
+  service_intro_point_free(ip);
+  return cell_len;
+}
 
 /* Mock function to avoid networking in unittests */
 static int
@@ -122,29 +167,24 @@ static void
 test_establish_intro_wrong_purpose(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
-  uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  char circ_nonce[DIGEST_LEN] = {0};
+  uint8_t cell_body[RELAY_PAYLOAD_SIZE];
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
   (void)arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  memcpy(intro_circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  memcpy(intro_circ->rend_circ_nonce, circ_nonce, DIGEST_LEN);
 
   /* Set a bad circuit purpose!! :) */
   circuit_change_purpose(TO_CIRCUIT(intro_circ), CIRCUIT_PURPOSE_INTRO_POINT);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
      attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                           sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+  cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
+  tt_u64_op(cell_len, OP_GT, 0);
 
   /* Receive the cell. Should fail. */
   setup_full_capture_of_logs(LOG_INFO);
@@ -154,18 +194,16 @@ test_establish_intro_wrong_purpose(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
 /* Prepare a circuit for accepting an ESTABLISH_INTRO cell */
 static void
-helper_prepare_circ_for_intro(or_circuit_t *circ,
-                              uint8_t *circuit_key_material)
+helper_prepare_circ_for_intro(or_circuit_t *circ, const char *circ_nonce)
 {
   /* Prepare the circuit for the incoming ESTABLISH_INTRO */
   circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
-  memcpy(circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN);
+  memcpy(circ->rend_circ_nonce, circ_nonce, DIGEST_LEN);
 }
 
 /* Send an empty ESTABLISH_INTRO cell. Should fail. */
@@ -174,17 +212,17 @@ test_establish_intro_wrong_keytype(void *arg)
 {
   int retval;
   or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  char circ_nonce[DIGEST_LEN] = {0};
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Receive the cell. Should fail. */
   setup_full_capture_of_logs(LOG_INFO);
-  retval = hs_intro_received_establish_intro(intro_circ, (uint8_t*)"", 0);
+  retval = hs_intro_received_establish_intro(intro_circ, (uint8_t *) "", 0);
   expect_log_msg_containing("Empty ESTABLISH_INTRO cell.");
   teardown_capture_of_logs();
   tt_int_op(retval, ==, -1);
@@ -198,26 +236,21 @@ static void
 test_establish_intro_wrong_keytype2(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
+  char circ_nonce[DIGEST_LEN] = {0};
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                           sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+   * attempt to parse it. */
+  cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
+  tt_u64_op(cell_len, OP_GT, 0);
 
   /* Mutate the auth key type! :) */
   cell_body[0] = 42;
@@ -230,7 +263,6 @@ test_establish_intro_wrong_keytype2(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
@@ -239,26 +271,27 @@ static void
 test_establish_intro_wrong_mac(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
-  uint8_t cell_body[RELAY_PAYLOAD_SIZE];
+  char circ_nonce[DIGEST_LEN] = {0};
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  uint8_t cell_body[RELAY_PAYLOAD_SIZE];
+  trn_cell_establish_intro_t *cell = NULL;
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                                sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
+   * attempt to parse it. */
+  cell_len = new_establish_intro_cell(circ_nonce, &cell);
+  tt_u64_op(cell_len, OP_GT, 0);
+  tt_assert(cell);
+
   /* Mangle one byte of the MAC. */
   uint8_t *handshake_ptr =
-    trn_cell_establish_intro_getarray_handshake_mac(establish_intro_cell);
+    trn_cell_establish_intro_getarray_handshake_mac(cell);
   handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++;
   /* We need to resign the payload with that change. */
   {
@@ -269,26 +302,26 @@ test_establish_intro_wrong_mac(void *arg)
     retval = ed25519_keypair_generate(&key_struct, 0);
     tt_int_op(retval, OP_EQ, 0);
     uint8_t *auth_key_ptr =
-      trn_cell_establish_intro_getarray_auth_key(establish_intro_cell);
+      trn_cell_establish_intro_getarray_auth_key(cell);
     memcpy(auth_key_ptr, key_struct.pubkey.pubkey, ED25519_PUBKEY_LEN);
     /* Encode payload so we can sign it. */
-    cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                           establish_intro_cell);
-    tt_int_op(cell_len, >, 0);
+    cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
+                                               cell);
+    tt_int_op(cell_len, OP_GT, 0);
 
     retval = ed25519_sign_prefixed(&sig, cell_body,
                                    cell_len -
-                                   (ED25519_SIG_LEN +
-                                    sizeof(establish_intro_cell->sig_len)),
+                                   (ED25519_SIG_LEN + sizeof(cell->sig_len)),
                                    ESTABLISH_INTRO_SIG_PREFIX, &key_struct);
     tt_int_op(retval, OP_EQ, 0);
     /* And write the signature to the cell */
     uint8_t *sig_ptr =
-      trn_cell_establish_intro_getarray_sig(establish_intro_cell);
-    memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len);
+      trn_cell_establish_intro_getarray_sig(cell);
+    memcpy(sig_ptr, sig.sig, cell->sig_len);
     /* Re-encode with the new signature. */
-    cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                           establish_intro_cell);
+    cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
+                                               cell);
+    tt_int_op(cell_len, OP_GT, 0);
   }
 
   /* Receive the cell. Should fail because our MAC is wrong. */
@@ -299,7 +332,7 @@ test_establish_intro_wrong_mac(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
+  trn_cell_establish_intro_free(cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
@@ -309,32 +342,32 @@ static void
 test_establish_intro_wrong_auth_key_len(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
+  char circ_nonce[DIGEST_LEN] = {0};
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
   size_t bad_auth_key_len = ED25519_PUBKEY_LEN - 1;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  trn_cell_establish_intro_t *cell = NULL;
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                               sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
+   * attempt to parse it. */
+  cell_len = new_establish_intro_cell(circ_nonce, &cell);
+  tt_u64_op(cell_len, OP_GT, 0);
+  tt_assert(cell);
+
   /* Mangle the auth key length. */
-  trn_cell_establish_intro_set_auth_key_len(establish_intro_cell,
-                                           bad_auth_key_len);
-  trn_cell_establish_intro_setlen_auth_key(establish_intro_cell,
-                                          bad_auth_key_len);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+  trn_cell_establish_intro_set_auth_key_len(cell, bad_auth_key_len);
+  trn_cell_establish_intro_setlen_auth_key(cell, bad_auth_key_len);
+  /* Encode cell. */
+  cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
+                                             cell);
+  tt_int_op(cell_len, OP_GT, 0);
 
   /* Receive the cell. Should fail. */
   setup_full_capture_of_logs(LOG_INFO);
@@ -344,7 +377,7 @@ test_establish_intro_wrong_auth_key_len(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
+  trn_cell_establish_intro_free(cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
@@ -354,30 +387,32 @@ static void
 test_establish_intro_wrong_sig_len(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
+  char circ_nonce[DIGEST_LEN] = {0};
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
   size_t bad_sig_len = ED25519_SIG_LEN - 1;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  trn_cell_establish_intro_t *cell = NULL;
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                               sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
+   * attempt to parse it. */
+  cell_len = new_establish_intro_cell(circ_nonce, &cell);
+  tt_u64_op(cell_len, OP_GT, 0);
+  tt_assert(cell);
+
   /* Mangle the signature length. */
-  trn_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len);
-  trn_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+  trn_cell_establish_intro_set_sig_len(cell, bad_sig_len);
+  trn_cell_establish_intro_setlen_sig(cell, bad_sig_len);
+  /* Encode cell. */
+  cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
+                                             cell);
+  tt_int_op(cell_len, OP_GT, 0);
 
   /* Receive the cell. Should fail. */
   setup_full_capture_of_logs(LOG_INFO);
@@ -387,7 +422,7 @@ test_establish_intro_wrong_sig_len(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
+  trn_cell_establish_intro_free(cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
@@ -397,29 +432,24 @@ static void
 test_establish_intro_wrong_sig(void *arg)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
-  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
+  char circ_nonce[DIGEST_LEN] = {0};
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
 
-  (void)arg;
+  (void) arg;
 
   /* Get the auth key of the intro point */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
      attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                           sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+  cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
+  tt_u64_op(cell_len, OP_GT, 0);
 
   /* Mutate the last byte (signature)! :) */
-  cell_body[cell_len-1]++;
+  cell_body[cell_len - 1]++;
 
   /* Receive the cell. Should fail. */
   setup_full_capture_of_logs(LOG_INFO);
@@ -429,7 +459,6 @@ test_establish_intro_wrong_sig(void *arg)
   tt_int_op(retval, ==, -1);
 
  done:
-  trn_cell_establish_intro_free(establish_intro_cell);
   circuit_free(TO_CIRCUIT(intro_circ));
 }
 
@@ -439,32 +468,32 @@ static trn_cell_establish_intro_t *
 helper_establish_intro_v3(or_circuit_t *intro_circ)
 {
   int retval;
-  trn_cell_establish_intro_t *establish_intro_cell = NULL;
+  char circ_nonce[DIGEST_LEN] = {0};
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  trn_cell_establish_intro_t *cell = NULL;
 
   tt_assert(intro_circ);
 
   /* Prepare the circuit for the incoming ESTABLISH_INTRO */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
-                                           sizeof(circuit_key_material));
-  tt_assert(establish_intro_cell);
-  cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
-                                         establish_intro_cell);
-  tt_int_op(cell_len, >, 0);
+   * attempt to parse it. */
+  cell_len = new_establish_intro_cell(circ_nonce, &cell);
+  tt_u64_op(cell_len, OP_GT, 0);
+  tt_assert(cell);
+  cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
+                                             cell);
+  tt_int_op(cell_len, OP_GT, 0);
 
   /* Receive the cell */
   retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len);
   tt_int_op(retval, ==, 0);
 
  done:
-  return establish_intro_cell;
+  return cell;
 }
 
 /* Helper function: Send a well-formed v2 ESTABLISH_INTRO cell to
@@ -476,22 +505,22 @@ helper_establish_intro_v2(or_circuit_t *intro_circ)
   int retval;
   uint8_t cell_body[RELAY_PAYLOAD_SIZE];
   ssize_t cell_len = 0;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
+  char circ_nonce[DIGEST_LEN] = {0};
 
   tt_assert(intro_circ);
 
   /* Prepare the circuit for the incoming ESTABLISH_INTRO */
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-  helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
+  crypto_rand(circ_nonce, sizeof(circ_nonce));
+  helper_prepare_circ_for_intro(intro_circ, circ_nonce);
 
   /* Send legacy establish_intro */
   key1 = pk_generate(0);
 
-  /* Use old circuit_key_material why not */
-  cell_len = encode_establish_intro_cell_legacy((char*)cell_body,
-                                                sizeof(cell_body),
-                                                key1,
-                                                (char *) circuit_key_material);
+  /* Use old circ_nonce why not */
+  cell_len = rend_service_encode_establish_intro_cell(
+                                           (char*)cell_body,
+                                           sizeof(cell_body), key1,
+                                           circ_nonce);
   tt_int_op(cell_len, >, 0);
 
   /* Receive legacy establish_intro */

+ 114 - 0
src/test/test_hs_ntor.c

@@ -0,0 +1,114 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_ntor.c
+ * \brief Test hidden service ntor functionality.
+ */
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+
+#include "hs_ntor.h"
+
+/* Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1
+ * cell, and verify the proper derivation of decryption keys on the other end.
+ * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify
+ * the proper verification on the other end. */
+static void
+test_hs_ntor(void *arg)
+{
+  int retval;
+
+  uint8_t subcredential[DIGEST256_LEN];
+
+  ed25519_keypair_t service_intro_auth_keypair;
+  curve25519_keypair_t service_intro_enc_keypair;
+  curve25519_keypair_t service_ephemeral_rend_keypair;
+
+  curve25519_keypair_t client_ephemeral_enc_keypair;
+
+  hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys;
+  hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys;
+
+  hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys;
+  hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys;
+
+  (void) arg;
+
+  /* Generate fake data for this unittest */
+  {
+    /* Generate fake subcredential */
+    memset(subcredential, 'Z', DIGEST256_LEN);
+
+    /* service */
+    curve25519_keypair_generate(&service_intro_enc_keypair, 0);
+    ed25519_keypair_generate(&service_intro_auth_keypair, 0);
+    curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0);
+    /* client */
+    curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0);
+  }
+
+  /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */
+  retval =
+    hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
+                                       &service_intro_enc_keypair.pubkey,
+                                       &client_ephemeral_enc_keypair,
+                                       subcredential,
+                                       &client_hs_ntor_intro_cell_keys);
+  tt_int_op(retval, ==, 0);
+
+  /* Service: Simulate the decryption of the received INTRODUCE1 */
+  retval =
+    hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
+                                        &service_intro_enc_keypair,
+                                        &client_ephemeral_enc_keypair.pubkey,
+                                        subcredential,
+                                        &service_hs_ntor_intro_cell_keys);
+  tt_int_op(retval, ==, 0);
+
+  /* Test that the INTRODUCE1 encryption/mac keys match! */
+  tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ,
+            service_hs_ntor_intro_cell_keys.enc_key,
+            CIPHER256_KEY_LEN);
+  tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ,
+            service_hs_ntor_intro_cell_keys.mac_key,
+            DIGEST256_LEN);
+
+  /* Service: Simulate creation of RENDEZVOUS1 key material. */
+  retval =
+    hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
+                                         &service_intro_enc_keypair,
+                                         &service_ephemeral_rend_keypair,
+                                         &client_ephemeral_enc_keypair.pubkey,
+                                         &service_hs_ntor_rend_cell_keys);
+  tt_int_op(retval, ==, 0);
+
+  /* Client: Simulate the verification of a received RENDEZVOUS1 cell */
+  retval =
+    hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
+                                        &client_ephemeral_enc_keypair,
+                                        &service_intro_enc_keypair.pubkey,
+                                        &service_ephemeral_rend_keypair.pubkey,
+                                        &client_hs_ntor_rend_cell_keys);
+  tt_int_op(retval, ==, 0);
+
+  /* Test that the RENDEZVOUS1 key material match! */
+  tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ,
+            service_hs_ntor_rend_cell_keys.rend_cell_auth_mac,
+            DIGEST256_LEN);
+  tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ,
+            service_hs_ntor_rend_cell_keys.ntor_key_seed,
+            DIGEST256_LEN);
+ done:
+  ;
+}
+
+struct testcase_t hs_ntor_tests[] = {
+  { "hs_ntor", test_hs_ntor, TT_FORK,
+    NULL, NULL },
+
+  END_OF_TESTCASES
+};
+

+ 1060 - 301
src/test/test_hs_service.c

@@ -15,341 +15,114 @@
 #define HS_SERVICE_PRIVATE
 #define HS_INTROPOINT_PRIVATE
 #define MAIN_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+#define STATEFILE_PRIVATE
 #define TOR_CHANNEL_INTERNAL_
 
 #include "test.h"
 #include "test_helpers.h"
 #include "log_test_helpers.h"
 #include "rend_test_helpers.h"
+#include "hs_test_helpers.h"
 
 #include "or.h"
-#include "channeltls.h"
+#include "config.h"
 #include "circuitbuild.h"
 #include "circuitlist.h"
 #include "circuituse.h"
-#include "config.h"
-#include "connection.h"
 #include "crypto.h"
-#include "hs_circuit.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+#include "relay.h"
+
 #include "hs_common.h"
 #include "hs_config.h"
+#include "hs_circuit.h"
 #include "hs_ident.h"
 #include "hs_intropoint.h"
 #include "hs_ntor.h"
 #include "hs_service.h"
 #include "main.h"
 #include "rendservice.h"
+#include "statefile.h"
 
 /* Trunnel */
 #include "hs/cell_establish_intro.h"
 
-/* Helper: from a set of options in conf, configure a service which will add
- * it to the staging list of the HS subsytem. */
-static int
-helper_config_service(const char *conf)
-{
-  int ret = 0;
-  or_options_t *options = NULL;
-  tt_assert(conf);
-  options = helper_parse_options(conf);
-  tt_assert(options);
-  ret = hs_config_service_all(options, 0);
- done:
-  or_options_free(options);
-  return ret;
-}
+static networkstatus_t mock_ns;
 
-/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we
- *  parse it from the receiver side. */
-static void
-test_gen_establish_intro_cell(void *arg)
+static networkstatus_t *
+mock_networkstatus_get_live_consensus(time_t now)
 {
-  (void) arg;
-  ssize_t retval;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
-  uint8_t buf[RELAY_PAYLOAD_SIZE];
-  trn_cell_establish_intro_t *cell_out = NULL;
-  trn_cell_establish_intro_t *cell_in = NULL;
-
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-
-  /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
-     attempt to parse it. */
-  {
-    cell_out = generate_establish_intro_cell(circuit_key_material,
-                                             sizeof(circuit_key_material));
-    tt_assert(cell_out);
-
-    retval = get_establish_intro_payload(buf, sizeof(buf), cell_out);
-    tt_int_op(retval, >=, 0);
-  }
-
-  /* Parse it as the receiver */
-  {
-    ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in,
-                                                         buf, sizeof(buf));
-    tt_int_op(parse_result, >=, 0);
-
-    retval = verify_establish_intro_cell(cell_in,
-                                         circuit_key_material,
-                                         sizeof(circuit_key_material));
-    tt_int_op(retval, >=, 0);
-  }
-
- done:
-  trn_cell_establish_intro_free(cell_out);
-  trn_cell_establish_intro_free(cell_in);
+  (void) now;
+  return &mock_ns;
 }
 
-/* Mocked ed25519_sign_prefixed() function that always fails :) */
-static int
-mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out,
-                           const uint8_t *msg, size_t msg_len,
-                           const char *prefix_str,
-                           const ed25519_keypair_t *keypair) {
-  (void) signature_out;
-  (void) msg;
-  (void) msg_len;
-  (void) prefix_str;
-  (void) keypair;
-  return -1;
-}
+static or_state_t *dummy_state = NULL;
 
-/** We simulate a failure to create an ESTABLISH_INTRO cell */
-static void
-test_gen_establish_intro_cell_bad(void *arg)
+/* Mock function to get fake or state (used for rev counters) */
+static or_state_t *
+get_or_state_replacement(void)
 {
-  (void) arg;
-  trn_cell_establish_intro_t *cell = NULL;
-  uint8_t circuit_key_material[DIGEST_LEN] = {0};
-
-  MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
-
-  crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
-
-  setup_full_capture_of_logs(LOG_WARN);
-  /* Easiest way to make that function fail is to mock the
-     ed25519_sign_prefixed() function and make it fail. */
-  cell = generate_establish_intro_cell(circuit_key_material,
-                                       sizeof(circuit_key_material));
-  expect_log_msg_containing("Unable to gen signature for "
-                            "ESTABLISH_INTRO cell.");
-  teardown_capture_of_logs();
-  tt_assert(!cell);
-
- done:
-  trn_cell_establish_intro_free(cell);
-  UNMOCK(ed25519_sign_prefixed);
+  return dummy_state;
 }
 
-/** Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1
- *  cell, and verify the proper derivation of decryption keys on the other end.
- *  Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify
- *  the proper verification on the other end. */
+/* Mock function because we are not trying to test the close circuit that does
+ * an awful lot of checks on the circuit object. */
 static void
-test_hs_ntor(void *arg)
+mock_circuit_mark_for_close(circuit_t *circ, int reason, int line,
+                            const char *file)
 {
-  int retval;
-
-  uint8_t subcredential[DIGEST256_LEN];
-
-  ed25519_keypair_t service_intro_auth_keypair;
-  curve25519_keypair_t service_intro_enc_keypair;
-  curve25519_keypair_t service_ephemeral_rend_keypair;
-
-  curve25519_keypair_t client_ephemeral_enc_keypair;
-
-  hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys;
-  hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys;
-
-  hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys;
-  hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys;
-
-  (void) arg;
-
-  /* Generate fake data for this unittest */
-  {
-    /* Generate fake subcredential */
-    memset(subcredential, 'Z', DIGEST256_LEN);
-
-    /* service */
-    curve25519_keypair_generate(&service_intro_enc_keypair, 0);
-    ed25519_keypair_generate(&service_intro_auth_keypair, 0);
-    curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0);
-    /* client */
-    curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0);
-  }
-
-  /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */
-  retval =
-    hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
-                                       &service_intro_enc_keypair.pubkey,
-                                       &client_ephemeral_enc_keypair,
-                                       subcredential,
-                                       &client_hs_ntor_intro_cell_keys);
-  tt_int_op(retval, ==, 0);
-
-  /* Service: Simulate the decryption of the received INTRODUCE1 */
-  retval =
-    hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
-                                        &service_intro_enc_keypair,
-                                        &client_ephemeral_enc_keypair.pubkey,
-                                        subcredential,
-                                        &service_hs_ntor_intro_cell_keys);
-  tt_int_op(retval, ==, 0);
-
-  /* Test that the INTRODUCE1 encryption/mac keys match! */
-  tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ,
-            service_hs_ntor_intro_cell_keys.enc_key,
-            CIPHER256_KEY_LEN);
-  tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ,
-            service_hs_ntor_intro_cell_keys.mac_key,
-            DIGEST256_LEN);
-
-  /* Service: Simulate creation of RENDEZVOUS1 key material. */
-  retval =
-    hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
-                                         &service_intro_enc_keypair,
-                                         &service_ephemeral_rend_keypair,
-                                         &client_ephemeral_enc_keypair.pubkey,
-                                         &service_hs_ntor_rend_cell_keys);
-  tt_int_op(retval, ==, 0);
-
-  /* Client: Simulate the verification of a received RENDEZVOUS1 cell */
-  retval =
-    hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
-                                        &client_ephemeral_enc_keypair,
-                                        &service_intro_enc_keypair.pubkey,
-                                        &service_ephemeral_rend_keypair.pubkey,
-                                        &client_hs_ntor_rend_cell_keys);
-  tt_int_op(retval, ==, 0);
-
-  /* Test that the RENDEZVOUS1 key material match! */
-  tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ,
-            service_hs_ntor_rend_cell_keys.rend_cell_auth_mac,
-            DIGEST256_LEN);
-  tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ,
-            service_hs_ntor_rend_cell_keys.ntor_key_seed,
-            DIGEST256_LEN);
- done:
-  ;
+  (void) circ;
+  (void) reason;
+  (void) line;
+  (void) file;
+  return;
 }
 
-static void
-test_validate_address(void *arg)
+static int
+mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
+                                  uint8_t relay_command, const char *payload,
+                                  size_t payload_len,
+                                  crypt_path_t *cpath_layer,
+                                  const char *filename, int lineno)
 {
-  int ret;
-
-  (void) arg;
-
-  /* Address too short and too long. */
-  setup_full_capture_of_logs(LOG_WARN);
-  ret = hs_address_is_valid("blah");
-  tt_int_op(ret, OP_EQ, 0);
-  expect_log_msg_containing("has an invalid length");
-  teardown_capture_of_logs();
-
-  setup_full_capture_of_logs(LOG_WARN);
-  ret = hs_address_is_valid(
-           "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb");
-  tt_int_op(ret, OP_EQ, 0);
-  expect_log_msg_containing("has an invalid length");
-  teardown_capture_of_logs();
-
-  /* Invalid checksum (taken from prop224) */
-  setup_full_capture_of_logs(LOG_WARN);
-  ret = hs_address_is_valid(
-           "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad");
-  tt_int_op(ret, OP_EQ, 0);
-  expect_log_msg_containing("invalid checksum");
-  teardown_capture_of_logs();
-
-  setup_full_capture_of_logs(LOG_WARN);
-  ret = hs_address_is_valid(
-           "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad");
-  tt_int_op(ret, OP_EQ, 0);
-  expect_log_msg_containing("invalid checksum");
-  teardown_capture_of_logs();
-
-  /* Non base32 decodable string. */
-  setup_full_capture_of_logs(LOG_WARN);
-  ret = hs_address_is_valid(
-           "????????????????????????????????????????????????????????");
-  tt_int_op(ret, OP_EQ, 0);
-  expect_log_msg_containing("can't be decoded");
-  teardown_capture_of_logs();
-
-  /* Valid address. */
-  ret = hs_address_is_valid(
-           "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
-  tt_int_op(ret, OP_EQ, 1);
-
- done:
-  ;
+  (void) stream_id;
+  (void) circ;
+  (void) relay_command;
+  (void) payload;
+  (void) payload_len;
+  (void) cpath_layer;
+  (void) filename;
+  (void) lineno;
+  return 0;
 }
 
-static void
-test_build_address(void *arg)
+/* Mock function that always return true so we can test the descriptor
+ * creation of the next time period deterministically. */
+static int
+mock_hs_overlap_mode_is_active_true(const networkstatus_t *consensus,
+                                    time_t now)
 {
-  int ret;
-  char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
-  ed25519_public_key_t pubkey;
-
-  (void) arg;
-
-  /* The following has been created with hs_build_address.py script that
-   * follows proposal 224 specification to build an onion address. */
-  static const char *test_addr =
-    "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid";
-
-  /* Let's try to build the same onion address that the script can do. Key is
-   * a long set of very random \x42 :). */
-  memset(&pubkey, '\x42', sizeof(pubkey));
-  hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr);
-  tt_str_op(test_addr, OP_EQ, onion_addr);
-  /* Validate that address. */
-  ret = hs_address_is_valid(onion_addr);
-  tt_int_op(ret, OP_EQ, 1);
-
- done:
-  ;
+  (void) consensus;
+  (void) now;
+  return 1;
 }
 
-/** Test that our HS time period calculation functions work properly */
-static void
-test_time_period(void *arg)
+/* Helper: from a set of options in conf, configure a service which will add
+ * it to the staging list of the HS subsytem. */
+static int
+helper_config_service(const char *conf)
 {
-  (void) arg;
-  uint64_t tn;
-  int retval;
-  time_t fake_time;
-
-  /* Let's do the example in prop224 section [TIME-PERIODS] */
-  retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC",
-                              &fake_time);
-  tt_int_op(retval, ==, 0);
-
-  /* Check that the time period number is right */
-  tn = get_time_period_num(fake_time);
-  tt_u64_op(tn, ==, 16903);
-
-  /* Increase current time to 11:59:59 UTC and check that the time period
-     number is still the same */
-  fake_time += 3599;
-  tn = get_time_period_num(fake_time);
-  tt_u64_op(tn, ==, 16903);
-
-  /* Now take time to 12:00:00 UTC and check that the time period rotated */
-  fake_time += 1;
-  tn = get_time_period_num(fake_time);
-  tt_u64_op(tn, ==, 16904);
-
-  /* Now also check our hs_get_next_time_period_num() function */
-  tn = hs_get_next_time_period_num(fake_time);
-  tt_u64_op(tn, ==, 16905);
-
+  int ret = 0;
+  or_options_t *options = NULL;
+  tt_assert(conf);
+  options = helper_parse_options(conf);
+  tt_assert(options);
+  ret = hs_config_service_all(options, 0);
  done:
-  ;
+  or_options_free(options);
+  return ret;
 }
 
 /* Test: Ensure that setting up rendezvous circuits works correctly. */
@@ -418,6 +191,77 @@ test_e2e_rend_circuit_setup(void *arg)
   circuit_free(TO_CIRCUIT(or_circ));
 }
 
+/* Helper: Return a newly allocated and initialized origin circuit with
+ * purpose and flags. A default HS identifier is set to an ed25519
+ * authentication key for introduction point. */
+static origin_circuit_t *
+helper_create_origin_circuit(int purpose, int flags)
+{
+  origin_circuit_t *circ = NULL;
+
+  circ = origin_circuit_init(purpose, flags);
+  tt_assert(circ);
+  circ->cpath = tor_malloc_zero(sizeof(crypt_path_t));
+  circ->cpath->magic = CRYPT_PATH_MAGIC;
+  circ->cpath->state = CPATH_STATE_OPEN;
+  circ->cpath->package_window = circuit_initial_package_window();
+  circ->cpath->deliver_window = CIRCWINDOW_START;
+  circ->cpath->prev = circ->cpath;
+  /* Random nonce. */
+  crypto_rand(circ->cpath->prev->rend_circ_nonce, DIGEST_LEN);
+  /* Create a default HS identifier. */
+  circ->hs_ident = tor_malloc_zero(sizeof(hs_ident_circuit_t));
+
+ done:
+  return circ;
+}
+
+/* Helper: Return a newly allocated service object with the identity keypair
+ * sets and the current descriptor. Then register it to the global map.
+ * Caller should us hs_free_all() to free this service or remove it from the
+ * global map before freeing. */
+static hs_service_t *
+helper_create_service(void)
+{
+  /* Set a service for this circuit. */
+  hs_service_t *service = hs_service_new(get_options());
+  tt_assert(service);
+  service->config.version = HS_VERSION_THREE;
+  ed25519_secret_key_generate(&service->keys.identity_sk, 0);
+  ed25519_public_key_generate(&service->keys.identity_pk,
+                              &service->keys.identity_sk);
+  service->desc_current = service_descriptor_new();
+  tt_assert(service->desc_current);
+  /* Register service to global map. */
+  int ret = register_service(get_hs_service_map(), service);
+  tt_int_op(ret, OP_EQ, 0);
+
+ done:
+  return service;
+}
+
+/* Helper: Return a newly allocated service intro point with two link
+ * specifiers, one IPv4 and one legacy ID set to As. */
+static hs_service_intro_point_t *
+helper_create_service_ip(void)
+{
+  hs_desc_link_specifier_t *ls;
+  hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0);
+  tt_assert(ip);
+  /* Add a first unused link specifier. */
+  ls = tor_malloc_zero(sizeof(*ls));
+  ls->type = LS_IPV4;
+  smartlist_add(ip->base.link_specifiers, ls);
+  /* Add a second link specifier used by a test. */
+  ls = tor_malloc_zero(sizeof(*ls));
+  ls->type = LS_LEGACY_ID;
+  memset(ls->u.legacy_id, 'A', sizeof(ls->u.legacy_id));
+  smartlist_add(ip->base.link_specifiers, ls);
+
+ done:
+  return ip;
+}
+
 static void
 test_load_keys(void *arg)
 {
@@ -446,7 +290,7 @@ test_load_keys(void *arg)
   tt_int_op(ret, OP_EQ, 0);
   /* This one should now be registered into the v2 list. */
   tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 0);
-  tt_int_op(num_rend_services(), OP_EQ, 1);
+  tt_int_op(rend_num_services(), OP_EQ, 1);
 
   /* v3 service. */
   tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE);
@@ -553,24 +397,939 @@ test_access_service(void *arg)
   hs_free_all();
 }
 
+/** Test that we can create intro point objects, index them and find them */
+static void
+test_service_intro_point(void *arg)
+{
+  hs_service_t *service = NULL;
+  hs_service_intro_point_t *ip = NULL;
+
+  (void) arg;
+
+  /* Test simple creation of an object. */
+  {
+    time_t now = time(NULL);
+    ip = helper_create_service_ip();
+    tt_assert(ip);
+    /* Make sure the authentication keypair is not zeroes. */
+    tt_int_op(tor_mem_is_zero((const char *) &ip->auth_key_kp,
+                              sizeof(ed25519_keypair_t)), OP_EQ, 0);
+    /* The introduce2_max MUST be in that range. */
+    tt_u64_op(ip->introduce2_max, OP_GE,
+              INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS);
+    tt_u64_op(ip->introduce2_max, OP_LE,
+              INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS);
+    /* Time to expire MUST also be in that range. We add 5 seconds because
+     * there could be a gap between setting now and the time taken in
+     * service_intro_point_new. On ARM, it can be surprisingly slow... */
+    tt_u64_op(ip->time_to_expire, OP_GE,
+              now + INTRO_POINT_LIFETIME_MIN_SECONDS + 5);
+    tt_u64_op(ip->time_to_expire, OP_LE,
+              now + INTRO_POINT_LIFETIME_MAX_SECONDS + 5);
+    tt_assert(ip->replay_cache);
+    tt_assert(ip->base.link_specifiers);
+    /* By default, this is NOT a legacy object. */
+    tt_int_op(ip->base.is_only_legacy, OP_EQ, 0);
+  }
+
+  /* Test functions that uses a service intropoints map with that previously
+   * created object (non legacy). */
+  {
+    uint8_t garbage[DIGEST256_LEN] = {0};
+    hs_service_intro_point_t *query;
+
+    service = hs_service_new(get_options());
+    tt_assert(service);
+    service->desc_current = service_descriptor_new();
+    tt_assert(service->desc_current);
+    /* Add intropoint to descriptor map. */
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    query = service_intro_point_find(service, &ip->auth_key_kp.pubkey);
+    tt_mem_op(query, OP_EQ, ip, sizeof(hs_service_intro_point_t));
+    query = service_intro_point_find(service,
+                                     (const ed25519_public_key_t *) garbage);
+    tt_assert(query == NULL);
+
+    /* While at it, can I find the descriptor with the intro point? */
+    hs_service_descriptor_t *desc_lookup =
+      service_desc_find_by_intro(service, ip);
+    tt_mem_op(service->desc_current, OP_EQ, desc_lookup,
+              sizeof(hs_service_descriptor_t));
+
+    /* Remove object from service descriptor and make sure it is out. */
+    service_intro_point_remove(service, ip);
+    query = service_intro_point_find(service, &ip->auth_key_kp.pubkey);
+    tt_assert(query == NULL);
+  }
+
+ done:
+  /* If the test succeed, this object is no longer referenced in the service
+   * so we can free it without use after free. Else, it might explode because
+   * it's still in the service descriptor map. */
+  service_intro_point_free(ip);
+  hs_service_free(service);
+}
+
+static node_t mock_node;
+static const node_t *
+mock_node_get_by_id(const char *digest)
+{
+  (void) digest;
+  memset(mock_node.identity, 'A', DIGEST_LEN);
+  /* Only return the matchin identity of As */
+  if (!tor_memcmp(mock_node.identity, digest, DIGEST_LEN)) {
+    return &mock_node;
+  }
+  return NULL;
+}
+
+static void
+test_helper_functions(void *arg)
+{
+  int ret;
+  hs_service_t *service = NULL;
+  hs_service_intro_point_t *ip = NULL;
+  hs_ident_circuit_t ident;
+
+  (void) arg;
+
+  MOCK(node_get_by_id, mock_node_get_by_id);
+
+  hs_service_init();
+
+  service = helper_create_service();
+
+  ip = helper_create_service_ip();
+  /* Immediately add the intro point to the service so the free service at the
+   * end cleans it as well. */
+  service_intro_point_add(service->desc_current->intro_points.map, ip);
+
+  /* Setup the circuit identifier. */
+  ed25519_pubkey_copy(&ident.intro_auth_pk, &ip->auth_key_kp.pubkey);
+  ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk);
+
+  /* Testing get_objects_from_ident(). */
+  {
+    hs_service_t *s_lookup = NULL;
+    hs_service_intro_point_t *ip_lookup = NULL;
+    hs_service_descriptor_t *desc_lookup = NULL;
+
+    get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup);
+    tt_mem_op(s_lookup, OP_EQ, service, sizeof(hs_service_t));
+    tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t));
+    tt_mem_op(desc_lookup, OP_EQ, service->desc_current,
+              sizeof(hs_service_descriptor_t));
+    /* Reset */
+    s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL;
+
+    /* NULL parameter should work. */
+    get_objects_from_ident(&ident, NULL, &ip_lookup, &desc_lookup);
+    tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t));
+    tt_mem_op(desc_lookup, OP_EQ, service->desc_current,
+              sizeof(hs_service_descriptor_t));
+    /* Reset. */
+    s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL;
+
+    /* Break the ident and we should find nothing. */
+    memset(&ident, 0, sizeof(ident));
+    get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup);
+    tt_assert(s_lookup == NULL);
+    tt_assert(ip_lookup == NULL);
+    tt_assert(desc_lookup == NULL);
+  }
+
+  /* Testing get_node_from_intro_point() */
+  {
+    const node_t *node = get_node_from_intro_point(ip);
+    tt_ptr_op(node, OP_EQ, &mock_node);
+    SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+                            hs_desc_link_specifier_t *, ls) {
+      if (ls->type == LS_LEGACY_ID) {
+        /* Change legacy id in link specifier which is not the mock node. */
+        memset(ls->u.legacy_id, 'B', sizeof(ls->u.legacy_id));
+      }
+    } SMARTLIST_FOREACH_END(ls);
+    node = get_node_from_intro_point(ip);
+    tt_assert(node == NULL);
+  }
+
+  /* Testing can_service_launch_intro_circuit() */
+  {
+    time_t now = time(NULL);
+    /* Put the start of the retry period back in time, we should be allowed.
+     * to launch intro circuit. */
+    service->state.num_intro_circ_launched = 2;
+    service->state.intro_circ_retry_started_time =
+      (now - INTRO_CIRC_RETRY_PERIOD - 1);
+    ret = can_service_launch_intro_circuit(service, now);
+    tt_int_op(ret, OP_EQ, 1);
+    tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now);
+    tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0);
+    /* Call it again, we should still be allowed because we are under
+     * MAX_INTRO_CIRCS_PER_PERIOD which been set to 0 previously. */
+    ret = can_service_launch_intro_circuit(service, now);
+    tt_int_op(ret, OP_EQ, 1);
+    tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now);
+    tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0);
+    /* Too many intro circuit launched means we are not allowed. */
+    service->state.num_intro_circ_launched = 20;
+    ret = can_service_launch_intro_circuit(service, now);
+    tt_int_op(ret, OP_EQ, 0);
+  }
+
+  /* Testing intro_point_should_expire(). */
+  {
+    time_t now = time(NULL);
+    /* Just some basic test of the current state. */
+    tt_u64_op(ip->introduce2_max, OP_GE,
+              INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS);
+    tt_u64_op(ip->introduce2_max, OP_LE,
+              INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS);
+    tt_u64_op(ip->time_to_expire, OP_GE,
+              now + INTRO_POINT_LIFETIME_MIN_SECONDS);
+    tt_u64_op(ip->time_to_expire, OP_LE,
+              now + INTRO_POINT_LIFETIME_MAX_SECONDS);
+
+    /* This newly created IP from above shouldn't expire now. */
+    ret = intro_point_should_expire(ip, now);
+    tt_int_op(ret, OP_EQ, 0);
+    /* Maximum number of INTRODUCE2 cell reached, it should expire. */
+    ip->introduce2_count = INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS + 1;
+    ret = intro_point_should_expire(ip, now);
+    tt_int_op(ret, OP_EQ, 1);
+    ip->introduce2_count = 0;
+    /* It should expire if time to expire has been reached. */
+    ip->time_to_expire = now - 1000;
+    ret = intro_point_should_expire(ip, now);
+    tt_int_op(ret, OP_EQ, 1);
+  }
+
+ done:
+  /* This will free the service and all objects associated to it. */
+  hs_service_free_all();
+  UNMOCK(node_get_by_id);
+}
+
+/** Test that we do the right operations when an intro circuit opens */
+static void
+test_intro_circuit_opened(void *arg)
+{
+  int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  hs_service_t *service;
+  origin_circuit_t *circ = NULL;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+  MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
+
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+                                      flags);
+
+  /* No service associated with this circuit. */
+  setup_full_capture_of_logs(LOG_WARN);
+  hs_service_circuit_has_opened(circ);
+  expect_log_msg_containing("Unknown service identity key");
+  teardown_capture_of_logs();
+
+  /* Set a service for this circuit. */
+  {
+    service = helper_create_service();
+    ed25519_pubkey_copy(&circ->hs_ident->identity_pk,
+                        &service->keys.identity_pk);
+
+    /* No intro point associated with this circuit. */
+    setup_full_capture_of_logs(LOG_WARN);
+    hs_service_circuit_has_opened(circ);
+    expect_log_msg_containing("Unknown introduction point auth key");
+    teardown_capture_of_logs();
+  }
+
+  /* Set an IP object now for this circuit. */
+  {
+    hs_service_intro_point_t *ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    /* Update ident to contain the intro point auth key. */
+    ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+                        &ip->auth_key_kp.pubkey);
+  }
+
+  /* This one should go all the way. */
+  setup_full_capture_of_logs(LOG_INFO);
+  hs_service_circuit_has_opened(circ);
+  expect_log_msg_containing("Introduction circuit 0 established for service");
+  teardown_capture_of_logs();
+
+ done:
+  circuit_free(TO_CIRCUIT(circ));
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+  UNMOCK(relay_send_command_from_edge_);
+}
+
+/** Test the operations we do on a circuit after we learn that we successfuly
+ *  established an intro point on it */
+static void
+test_intro_established(void *arg)
+{
+  int ret;
+  int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+  origin_circuit_t *circ = NULL;
+  hs_service_t *service;
+  hs_service_intro_point_t *ip = NULL;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
+                                      flags);
+  /* Test a wrong purpose. */
+  TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO;
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_intro_established(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Received an INTRO_ESTABLISHED cell on a "
+                            "non introduction circuit of purpose");
+  teardown_capture_of_logs();
+
+  /* Back to normal. */
+  TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO;
+
+  /* No service associated to it. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_intro_established(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Unknown service identity key");
+  teardown_capture_of_logs();
+
+  /* Set a service for this circuit. */
+  service = helper_create_service();
+  ed25519_pubkey_copy(&circ->hs_ident->identity_pk,
+                      &service->keys.identity_pk);
+  /* No introduction point associated to it. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_intro_established(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Introduction circuit established without an "
+                            "intro point object on circuit");
+  teardown_capture_of_logs();
+
+  /* Set an IP object now for this circuit. */
+  {
+    ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    /* Update ident to contain the intro point auth key. */
+    ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+                        &ip->auth_key_kp.pubkey);
+  }
+
+  /* Send an empty payload. INTRO_ESTABLISHED cells are basically zeroes. */
+  ret = hs_service_receive_intro_established(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, 0);
+  tt_u64_op(ip->circuit_established, OP_EQ, 1);
+  tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_INTRO);
+
+ done:
+  circuit_free(TO_CIRCUIT(circ));
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+}
+
+/** Check the operations we do on a rendezvous circuit after we learn it's
+ *  open */
+static void
+test_rdv_circuit_opened(void *arg)
+{
+  int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  origin_circuit_t *circ = NULL;
+  hs_service_t *service;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+  MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
+
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_CONNECT_REND, flags);
+  crypto_rand((char *) circ->hs_ident->rendezvous_cookie, REND_COOKIE_LEN);
+  crypto_rand((char *) circ->hs_ident->rendezvous_handshake_info,
+              sizeof(circ->hs_ident->rendezvous_handshake_info));
+
+  /* No service associated with this circuit. */
+  setup_full_capture_of_logs(LOG_WARN);
+  hs_service_circuit_has_opened(circ);
+  expect_log_msg_containing("Unknown service identity key");
+  teardown_capture_of_logs();
+  /* This should be set to a non zero timestamp. */
+  tt_u64_op(TO_CIRCUIT(circ)->timestamp_dirty, OP_NE, 0);
+
+  /* Set a service for this circuit. */
+  service = helper_create_service();
+  ed25519_pubkey_copy(&circ->hs_ident->identity_pk,
+                      &service->keys.identity_pk);
+  /* Should be all good. */
+  hs_service_circuit_has_opened(circ);
+  tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED);
+
+ done:
+  circuit_free(TO_CIRCUIT(circ));
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+  UNMOCK(relay_send_command_from_edge_);
+}
+
+/** Test sending and receiving introduce2 cells */
+static void
+test_introduce2(void *arg)
+{
+  int ret;
+  int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
+  origin_circuit_t *circ = NULL;
+  hs_service_t *service;
+  hs_service_intro_point_t *ip = NULL;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+  MOCK(get_or_state,
+       get_or_state_replacement);
+
+  dummy_state = tor_malloc_zero(sizeof(or_state_t));
+
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags);
+
+  /* Test a wrong purpose. */
+  TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO;
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_introduce2(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Received an INTRODUCE2 cell on a "
+                            "non introduction circuit of purpose");
+  teardown_capture_of_logs();
+
+  /* Back to normal. */
+  TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO;
+
+  /* No service associated to it. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_introduce2(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Unknown service identity key");
+  teardown_capture_of_logs();
+
+  /* Set a service for this circuit. */
+  service = helper_create_service();
+  ed25519_pubkey_copy(&circ->hs_ident->identity_pk,
+                      &service->keys.identity_pk);
+  /* No introduction point associated to it. */
+  setup_full_capture_of_logs(LOG_WARN);
+  ret = hs_service_receive_introduce2(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  expect_log_msg_containing("Unknown introduction auth key when handling "
+                            "an INTRODUCE2 cell on circuit");
+  teardown_capture_of_logs();
+
+  /* Set an IP object now for this circuit. */
+  {
+    ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    /* Update ident to contain the intro point auth key. */
+    ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+                        &ip->auth_key_kp.pubkey);
+  }
+
+  /* This will fail because receiving an INTRODUCE2 cell implies a valid cell
+   * and then launching circuits so let's not do that and instead test that
+   * behaviour differently. */
+  ret = hs_service_receive_introduce2(circ, payload, sizeof(payload));
+  tt_int_op(ret, OP_EQ, -1);
+  tt_u64_op(ip->introduce2_count, OP_EQ, 0);
+
+ done:
+  or_state_free(dummy_state);
+  dummy_state = NULL;
+  circuit_free(TO_CIRCUIT(circ));
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+}
+
+/** Test basic hidden service housekeeping operations (maintaining intro
+ *  points, etc) */
+static void
+test_service_event(void *arg)
+{
+  int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+  time_t now = time(NULL);
+  hs_service_t *service;
+  origin_circuit_t *circ = NULL;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags);
+
+  /* Set a service for this circuit. */
+  service = helper_create_service();
+  ed25519_pubkey_copy(&circ->hs_ident->identity_pk,
+                      &service->keys.identity_pk);
+
+  /* Currently this consists of cleaning invalid intro points. So adding IPs
+   * here that should get cleaned up. */
+  {
+    hs_service_intro_point_t *ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    /* This run will remove the IP because we have no circuits nor node_t
+     * associated with it. */
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 0);
+    /* We'll trigger a removal because we've reached our maximum amount of
+     * times we should retry a circuit. For this, we need to have a node_t
+     * that matches the identity of this IP. */
+    routerinfo_t ri;
+    ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
+    /* This triggers a node_t creation. */
+    tt_assert(nodelist_set_routerinfo(&ri, NULL));
+    ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1;
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 0);
+    /* No removal but no circuit so this means the IP object will stay in the
+     * descriptor map so we can retry it. */
+    ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    ip->circuit_established = 1;  /* We'll test that, it MUST be 0 after. */
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 1);
+    /* Remove the IP object at once for the next test. */
+    ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1;
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 0);
+    /* Now, we'll create an IP with a registered circuit. The IP object
+     * shouldn't go away. */
+    ip = helper_create_service_ip();
+    service_intro_point_add(service->desc_current->intro_points.map, ip);
+    ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
+                        &ip->auth_key_kp.pubkey);
+    hs_circuitmap_register_intro_circ_v3_service_side(
+                                         circ, &ip->auth_key_kp.pubkey);
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 1);
+    /* We'll mangle the IP object to expire. */
+    ip->time_to_expire = now;
+    run_housekeeping_event(now);
+    tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+              OP_EQ, 0);
+  }
+
+ done:
+  hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
+  circuit_free(TO_CIRCUIT(circ));
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+}
+
+/** Test that we rotate descriptors correctly in overlap period */
+static void
+test_rotate_descriptors(void *arg)
+{
+  int ret;
+  time_t now = time(NULL);
+  hs_service_t *service;
+  hs_service_descriptor_t *desc_next;
+  hs_service_intro_point_t *ip;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+  MOCK(networkstatus_get_live_consensus,
+       mock_networkstatus_get_live_consensus);
+
+  /* Setup the valid_after time to be 13:00 UTC, not in overlap period. The
+   * overlap check doesn't care about the year. */
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC",
+                           &mock_ns.valid_after);
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC",
+                           &mock_ns.fresh_until);
+  tt_int_op(ret, OP_EQ, 0);
+
+  /* Create a service with a default descriptor and state. It's added to the
+   * global map. */
+  service = helper_create_service();
+  ip = helper_create_service_ip();
+  service_intro_point_add(service->desc_current->intro_points.map, ip);
+
+  /* Nothing should happen because we are not in the overlap period. */
+  rotate_all_descriptors(now);
+  tt_int_op(service->state.in_overlap_period, OP_EQ, 0);
+  tt_assert(service->desc_current);
+  tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+            OP_EQ, 1);
+
+  /* Entering an overlap period. */
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 01:00:00 UTC",
+                           &mock_ns.valid_after);
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 02:00:00 UTC",
+                           &mock_ns.fresh_until);
+  tt_int_op(ret, OP_EQ, 0);
+  desc_next = service_descriptor_new();
+  desc_next->next_upload_time = 42; /* Our marker to recognize it. */
+  service->desc_next = desc_next;
+  /* We should have our state flagged to be in the overlap period, our current
+   * descriptor cleaned up and the next descriptor becoming the current. */
+  rotate_all_descriptors(now);
+  tt_int_op(service->state.in_overlap_period, OP_EQ, 1);
+  tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next));
+  tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+            OP_EQ, 0);
+  tt_assert(service->desc_next == NULL);
+  /* A second time should do nothing. */
+  rotate_all_descriptors(now);
+  tt_int_op(service->state.in_overlap_period, OP_EQ, 1);
+  tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next));
+  tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+            OP_EQ, 0);
+  tt_assert(service->desc_next == NULL);
+
+  /* Going out of the overlap period. */
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 12:00:00 UTC",
+                           &mock_ns.valid_after);
+  ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC",
+                           &mock_ns.fresh_until);
+  /* This should reset the state and not touch the current descriptor. */
+  tt_int_op(ret, OP_EQ, 0);
+  rotate_all_descriptors(now);
+  tt_int_op(service->state.in_overlap_period, OP_EQ, 0);
+  tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next));
+  tt_assert(service->desc_next == NULL);
+
+ done:
+  hs_free_all();
+  UNMOCK(circuit_mark_for_close_);
+  UNMOCK(networkstatus_get_live_consensus);
+}
+
+/** Test building descriptors: picking intro points, setting up their link
+ *  specifiers, etc. */
+static void
+test_build_update_descriptors(void *arg)
+{
+  int ret;
+  time_t now = time(NULL);
+  time_t period_num = hs_get_time_period_num(now);
+  time_t next_period_num = hs_get_next_time_period_num(now);
+  node_t *node;
+  hs_service_t *service;
+  hs_service_intro_point_t *ip_cur, *ip_next;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true);
+  MOCK(get_or_state,
+       get_or_state_replacement);
+
+  dummy_state = tor_malloc_zero(sizeof(or_state_t));
+
+  /* Create a service without a current descriptor to trigger a build. */
+  service = hs_service_new(get_options());
+  tt_assert(service);
+  service->config.version = HS_VERSION_THREE;
+  ed25519_secret_key_generate(&service->keys.identity_sk, 0);
+  ed25519_public_key_generate(&service->keys.identity_pk,
+                              &service->keys.identity_sk);
+  /* Register service to global map. */
+  ret = register_service(get_hs_service_map(), service);
+  tt_int_op(ret, OP_EQ, 0);
+
+  build_all_descriptors(now);
+  /* Check *current* descriptor. */
+  tt_assert(service->desc_current);
+  tt_assert(service->desc_current->desc);
+  tt_assert(service->desc_current->intro_points.map);
+  tt_u64_op(service->desc_current->time_period_num, OP_EQ, period_num);
+  /* This should be untouched, the update descriptor process changes it. */
+  tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0);
+
+  /* Check *next* descriptor. */
+  tt_assert(service->desc_next);
+  tt_assert(service->desc_next->desc);
+  tt_assert(service->desc_next->intro_points.map);
+  tt_assert(service->desc_current != service->desc_next);
+  tt_u64_op(service->desc_next->time_period_num, OP_EQ, next_period_num);
+  /* This should be untouched, the update descriptor process changes it. */
+  tt_u64_op(service->desc_next->next_upload_time, OP_EQ, 0);
+
+  /* Time to test the update of those descriptors. At first, we have no node
+   * in the routerlist so this will find NO suitable node for the IPs. */
+  setup_full_capture_of_logs(LOG_INFO);
+  update_all_descriptors(now);
+  expect_log_msg_containing("Unable to find a suitable node to be an "
+                            "introduction point for service");
+  teardown_capture_of_logs();
+  tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+            OP_EQ, 0);
+  tt_int_op(digest256map_size(service->desc_next->intro_points.map),
+            OP_EQ, 0);
+
+  /* Now, we'll setup a node_t. */
+  {
+    routerinfo_t ri;
+    tor_addr_t ipv4_addr;
+    curve25519_secret_key_t curve25519_secret_key;
+
+    memset(&ri, 0, sizeof(routerinfo_t));
+
+    tor_addr_parse(&ipv4_addr, "127.0.0.1");
+    ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
+    ri.or_port = 1337;
+    ri.purpose = ROUTER_PURPOSE_GENERAL;
+    /* Ugly yes but we never free the "ri" object so this just makes things
+     * easier. */
+    ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3";
+    ret = curve25519_secret_key_generate(&curve25519_secret_key, 0);
+    tt_int_op(ret, OP_EQ, 0);
+    ri.onion_curve25519_pkey =
+      tor_malloc_zero(sizeof(curve25519_public_key_t));
+    ri.onion_pkey = crypto_pk_new();
+    curve25519_public_key_generate(ri.onion_curve25519_pkey,
+                                   &curve25519_secret_key);
+    memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
+    /* Setup ed25519 identity */
+    ed25519_keypair_t kp1;
+    ed25519_keypair_generate(&kp1, 0);
+    ri.cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t));
+    tt_assert(ri.cache_info.signing_key_cert);
+    ed25519_pubkey_copy(&ri.cache_info.signing_key_cert->signing_key,
+                        &kp1.pubkey);
+    nodelist_set_routerinfo(&ri, NULL);
+    node = node_get_mutable_by_id(ri.cache_info.identity_digest);
+    tt_assert(node);
+    node->is_running = node->is_valid = node->is_fast = node->is_stable = 1;
+  }
+
+  /* We expect to pick only one intro point from the node above. */
+  setup_full_capture_of_logs(LOG_INFO);
+  update_all_descriptors(now);
+  tor_free(node->ri->onion_curve25519_pkey); /* Avoid memleak. */
+  tor_free(node->ri->cache_info.signing_key_cert);
+  crypto_pk_free(node->ri->onion_pkey);
+  expect_log_msg_containing("just picked 1 intro points and wanted 3. It "
+                            "currently has 0 intro points. Launching "
+                            "ESTABLISH_INTRO circuit shortly.");
+  teardown_capture_of_logs();
+  tt_int_op(digest256map_size(service->desc_current->intro_points.map),
+            OP_EQ, 1);
+  tt_int_op(digest256map_size(service->desc_next->intro_points.map),
+            OP_EQ, 1);
+  /* Get the IP object. Because we don't have the auth key of the IP, we can't
+   * query it so get the first element in the map. */
+  {
+    void *obj = NULL;
+    const uint8_t *key;
+    digest256map_iter_t *iter =
+      digest256map_iter_init(service->desc_current->intro_points.map);
+    digest256map_iter_get(iter, &key, &obj);
+    tt_assert(obj);
+    ip_cur = obj;
+    /* Get also the IP from the next descriptor. We'll make sure it's not the
+     * same object as in the current descriptor. */
+    iter = digest256map_iter_init(service->desc_next->intro_points.map);
+    digest256map_iter_get(iter, &key, &obj);
+    tt_assert(obj);
+    ip_next = obj;
+  }
+  tt_mem_op(ip_cur, OP_NE, ip_next, sizeof(hs_desc_intro_point_t));
+
+  /* We won't test the service IP object because there is a specific test
+   * already for this but we'll make sure that the state is coherent.*/
+
+  /* Three link specifiers are mandatoy so make sure we do have them. */
+  tt_int_op(smartlist_len(ip_cur->base.link_specifiers), OP_EQ, 3);
+  /* Make sure we have a valid encryption keypair generated when we pick an
+   * intro point in the update process. */
+  tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.seckey.secret_key,
+                             CURVE25519_SECKEY_LEN));
+  tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.pubkey.public_key,
+                             CURVE25519_PUBKEY_LEN));
+  tt_u64_op(ip_cur->time_to_expire, OP_GE, now +
+            INTRO_POINT_LIFETIME_MIN_SECONDS);
+  tt_u64_op(ip_cur->time_to_expire, OP_LE, now +
+            INTRO_POINT_LIFETIME_MAX_SECONDS);
+
+ done:
+  hs_free_all();
+  nodelist_free_all();
+  UNMOCK(hs_overlap_mode_is_active);
+}
+
+static void
+test_upload_descriptors(void *arg)
+{
+  int ret;
+  time_t now = time(NULL);
+  hs_service_t *service;
+  hs_service_intro_point_t *ip;
+
+  (void) arg;
+
+  hs_init();
+  MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true);
+  MOCK(get_or_state,
+       get_or_state_replacement);
+
+  dummy_state = tor_malloc_zero(sizeof(or_state_t));
+
+  /* Create a service with no descriptor. It's added to the global map. */
+  service = hs_service_new(get_options());
+  tt_assert(service);
+  service->config.version = HS_VERSION_THREE;
+  ed25519_secret_key_generate(&service->keys.identity_sk, 0);
+  ed25519_public_key_generate(&service->keys.identity_pk,
+                              &service->keys.identity_sk);
+  /* Register service to global map. */
+  ret = register_service(get_hs_service_map(), service);
+  tt_int_op(ret, OP_EQ, 0);
+  /* But first, build our descriptor. */
+  build_all_descriptors(now);
+
+  /* Nothing should happen because we have 0 introduction circuit established
+   * and we want (by default) 3 intro points. */
+  run_upload_descriptor_event(now);
+  /* If no upload happened, this should be untouched. */
+  tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0);
+  /* We'll simulate that we've opened our intro point circuit and that we only
+   * want one intro point. */
+  service->config.num_intro_points = 1;
+
+  /* Set our next upload time after now which will skip the upload. */
+  service->desc_current->next_upload_time = now + 1000;
+  run_upload_descriptor_event(now);
+  /* If no upload happened, this should be untouched. */
+  tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000);
+
+  /* Set our upload time in the past so we trigger an upload. */
+  service->desc_current->next_upload_time = now - 1000;
+  service->desc_next->next_upload_time = now - 1000;
+  ip = helper_create_service_ip();
+  ip->circuit_established = 1;
+  service_intro_point_add(service->desc_current->intro_points.map, ip);
+
+  setup_full_capture_of_logs(LOG_WARN);
+  run_upload_descriptor_event(now);
+  expect_log_msg_containing("No valid consensus so we can't get the");
+  teardown_capture_of_logs();
+  tt_u64_op(service->desc_current->next_upload_time, OP_GE,
+            now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN);
+  tt_u64_op(service->desc_current->next_upload_time, OP_LE,
+            now + HS_SERVICE_NEXT_UPLOAD_TIME_MAX);
+
+ done:
+  hs_free_all();
+  UNMOCK(hs_overlap_mode_is_active);
+}
+
+/** Test the functions that save and load HS revision counters to state. */
+static void
+test_revision_counter_state(void *arg)
+{
+  char *state_line_one = NULL;
+  char *state_line_two = NULL;
+
+  hs_service_descriptor_t *desc_one = service_descriptor_new();
+  hs_service_descriptor_t *desc_two = service_descriptor_new();
+
+  (void) arg;
+
+  /* Prepare both descriptors */
+  desc_one->desc->plaintext_data.revision_counter = 42;
+  desc_two->desc->plaintext_data.revision_counter = 240;
+  memset(&desc_one->blinded_kp.pubkey.pubkey, '\x42',
+         sizeof(desc_one->blinded_kp.pubkey.pubkey));
+  memset(&desc_two->blinded_kp.pubkey.pubkey, '\xf0',
+         sizeof(desc_one->blinded_kp.pubkey.pubkey));
+
+  /* Turn the descriptor rev counters into state lines */
+  state_line_one = encode_desc_rev_counter_for_state(desc_one);
+  tt_str_op(state_line_one, OP_EQ,
+            "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI 42");
+
+  state_line_two = encode_desc_rev_counter_for_state(desc_two);
+  tt_str_op(state_line_two, OP_EQ,
+            "8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PA 240");
+
+  /* Now let's test our state parsing function: */
+  int service_found;
+  uint64_t cached_rev_counter;
+
+  /* First's try with wrong pubkey and check that no service was found */
+  cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one,
+                                                 &desc_two->blinded_kp.pubkey,
+                                                               &service_found);
+  tt_int_op(service_found, OP_EQ, 0);
+
+  /* Now let's try with the right pubkeys */
+  cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one,
+                                                 &desc_one->blinded_kp.pubkey,
+                                                               &service_found);
+  tt_int_op(service_found, OP_EQ, 1);
+  tt_int_op(cached_rev_counter, OP_EQ, 42);
+
+  cached_rev_counter =check_state_line_for_service_rev_counter(state_line_two,
+                                                 &desc_two->blinded_kp.pubkey,
+                                                               &service_found);
+  tt_int_op(service_found, OP_EQ, 1);
+  tt_int_op(cached_rev_counter, OP_EQ, 240);
+
+ done:
+  tor_free(state_line_one);
+  tor_free(state_line_two);
+  service_descriptor_free(desc_one);
+  service_descriptor_free(desc_two);
+}
+
 struct testcase_t hs_service_tests[] = {
-  { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
+  { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
     NULL, NULL },
-  { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK,
+  { "load_keys", test_load_keys, TT_FORK,
     NULL, NULL },
-  { "hs_ntor", test_hs_ntor, TT_FORK,
+  { "access_service", test_access_service, TT_FORK,
     NULL, NULL },
-  { "time_period", test_time_period, TT_FORK,
+  { "service_intro_point", test_service_intro_point, TT_FORK,
     NULL, NULL },
-  { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
+  { "helper_functions", test_helper_functions, TT_FORK,
     NULL, NULL },
-  { "build_address", test_build_address, TT_FORK,
+  { "intro_circuit_opened", test_intro_circuit_opened, TT_FORK,
     NULL, NULL },
-  { "validate_address", test_validate_address, TT_FORK,
+  { "intro_established", test_intro_established, TT_FORK,
     NULL, NULL },
-  { "load_keys", test_load_keys, TT_FORK,
+  { "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK,
     NULL, NULL },
-  { "access_service", test_access_service, TT_FORK,
+  { "introduce2", test_introduce2, TT_FORK,
+    NULL, NULL },
+  { "service_event", test_service_event, TT_FORK,
+    NULL, NULL },
+  { "rotate_descriptors", test_rotate_descriptors, TT_FORK,
+    NULL, NULL },
+  { "build_update_descriptors", test_build_update_descriptors, TT_FORK,
+    NULL, NULL },
+  { "upload_descriptors", test_upload_descriptors, TT_FORK,
+    NULL, NULL },
+  { "revision_counter_state", test_revision_counter_state, TT_FORK,
     NULL, NULL },
 
   END_OF_TESTCASES

+ 120 - 0
src/test/test_shared_random.c

@@ -189,6 +189,120 @@ test_get_state_valid_until_time(void *arg)
   ;
 }
 
+/** Test the function that calculates the start time of the current SRV
+ *  protocol run. */
+static void
+test_get_start_time_of_current_run(void *arg)
+{
+  int retval;
+  char tbuf[ISO_TIME_LEN + 1];
+  time_t current_time, run_start_time;
+
+  (void) arg;
+
+  {
+    /* Get start time if called at 00:00:01 */
+    retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC",
+                                &current_time);
+    tt_int_op(retval, ==, 0);
+    run_start_time =
+      sr_state_get_start_time_of_current_protocol_run(current_time);
+
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, run_start_time);
+    tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
+  }
+
+  {
+    retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:59 UTC",
+                                &current_time);
+    tt_int_op(retval, ==, 0);
+    run_start_time =
+      sr_state_get_start_time_of_current_protocol_run(current_time);
+
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, run_start_time);
+    tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
+  }
+
+  {
+    retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC",
+                                &current_time);
+    tt_int_op(retval, ==, 0);
+    run_start_time =
+      sr_state_get_start_time_of_current_protocol_run(current_time);
+
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, run_start_time);
+    tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
+  }
+
+  /* Now let's alter the voting schedule and check the correctness of the
+   * function. Voting interval of 10 seconds, means that an SRV protocol run
+   * takes 10 seconds * 24 rounds = 4 mins */
+  {
+    or_options_t *options = get_options_mutable();
+    options->V3AuthVotingInterval = 10;
+    options->TestingV3AuthInitialVotingInterval = 10;
+    retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:15:32 UTC",
+                                &current_time);
+
+    tt_int_op(retval, ==, 0);
+    run_start_time =
+      sr_state_get_start_time_of_current_protocol_run(current_time);
+
+    /* Compare it with the correct result */
+    format_iso_time(tbuf, run_start_time);
+    tt_str_op("2015-04-20 00:12:00", OP_EQ, tbuf);
+  }
+
+ done:
+  ;
+}
+
+/** Do some rudimentary consistency checks between the functions that
+ *  understand the shared random protocol schedule */
+static void
+test_get_start_time_functions(void *arg)
+{
+  (void) arg;
+  time_t now = approx_time();
+
+  time_t start_time_of_protocol_run =
+    sr_state_get_start_time_of_current_protocol_run(now);
+  tt_assert(start_time_of_protocol_run);
+
+  /* Check that the round start time of the beginning of the run, is itself */
+  tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run), OP_EQ,
+            start_time_of_protocol_run);
+
+  /* Check that even if we increment the start time, we still get the start
+     time of the run as the beginning of the round. */
+  tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run+1),
+            OP_EQ, start_time_of_protocol_run);
+
+ done: ;
+}
+
+static void
+test_get_sr_protocol_duration(void *arg)
+{
+  (void) arg;
+
+  /* Check that by default an SR phase is 12 hours */
+  tt_int_op(sr_state_get_phase_duration(), ==, 12*60*60);
+  tt_int_op(sr_state_get_protocol_run_duration(), ==, 24*60*60);
+
+  /* Now alter the voting interval and check that the SR phase is 2 mins long
+   * if voting happens every 10 seconds (10*12 seconds = 2 mins) */
+  or_options_t *options = get_options_mutable();
+  options->V3AuthVotingInterval = 10;
+  tt_int_op(sr_state_get_phase_duration(), ==, 2*60);
+  tt_int_op(sr_state_get_protocol_run_duration(), ==, 4*60);
+
+ done: ;
+}
+
 /* Mock function to immediately return our local 'mock_consensus'. */
 static networkstatus_t *
 mock_networkstatus_get_live_consensus(time_t now)
@@ -1272,6 +1386,12 @@ struct testcase_t sr_tests[] = {
     NULL, NULL },
   { "get_next_valid_after_time", test_get_next_valid_after_time, TT_FORK,
     NULL, NULL },
+  { "get_start_time_of_current_run", test_get_start_time_of_current_run,
+    TT_FORK, NULL, NULL },
+  { "get_start_time_functions", test_get_start_time_functions,
+    TT_FORK, NULL, NULL },
+  { "get_sr_protocol_duration", test_get_sr_protocol_duration, TT_FORK,
+    NULL, NULL },
   { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK,
     NULL, NULL },
   { "vote", test_vote, TT_FORK,

+ 4 - 0
src/trunnel/ed25519_cert.h

@@ -59,6 +59,10 @@ struct link_specifier_st {
 };
 #endif
 typedef struct link_specifier_st link_specifier_t;
+/** XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by
+ *  taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE we would
+ *  need to refactor that function to do the coyp by encoding and decoding the
+ *  object. */
 #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT)
 struct ed25519_cert_st {
   uint8_t version;

+ 6 - 0
src/trunnel/ed25519_cert.trunnel

@@ -28,6 +28,12 @@ const LS_IPV6 = 0x01;
 const LS_LEGACY_ID = 0x02;
 const LS_ED25519_ID = 0x03;
 
+// XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by
+//  taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE, or
+//  if we ever make link_specifier contain other types, we will
+//  need to refactor that function to do the copy by encoding and decoding the
+//  object.
+
 // amended from tor.trunnel
 struct link_specifier {
   u8 ls_type;

+ 292 - 0
src/trunnel/hs/cell_rendezvous.c

@@ -0,0 +1,292 @@
+/* cell_rendezvous.c -- generated by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "cell_rendezvous.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+  do {                              \
+    (obj)->trunnel_error_code_ = 1; \
+  } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning a static analysis tool, we don't want it to complain
+ * that some of our remaining-bytes checks are dead-code. */
+int cellrendezvous_deadcode_dummy__ = 0;
+#define OR_DEADCODE_DUMMY || cellrendezvous_deadcode_dummy__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label)                           \
+  do {                                                           \
+    if (remaining < (nbytes) OR_DEADCODE_DUMMY) {                \
+      goto label;                                                \
+    }                                                            \
+  } while (0)
+
+trn_cell_rendezvous1_t *
+trn_cell_rendezvous1_new(void)
+{
+  trn_cell_rendezvous1_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous1_t));
+  if (NULL == val)
+    return NULL;
+  return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+trn_cell_rendezvous1_clear(trn_cell_rendezvous1_t *obj)
+{
+  (void) obj;
+  TRUNNEL_DYNARRAY_WIPE(&obj->handshake_info);
+  TRUNNEL_DYNARRAY_CLEAR(&obj->handshake_info);
+}
+
+void
+trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *obj)
+{
+  if (obj == NULL)
+    return;
+  trn_cell_rendezvous1_clear(obj);
+  trunnel_memwipe(obj, sizeof(trn_cell_rendezvous1_t));
+  trunnel_free_(obj);
+}
+
+size_t
+trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp)
+{
+  (void)inp;  return TRUNNEL_REND_COOKIE_LEN;
+}
+
+uint8_t
+trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx)
+{
+  trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
+  return inp->rendezvous_cookie[idx];
+}
+
+uint8_t
+trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx)
+{
+  return trn_cell_rendezvous1_get_rendezvous_cookie((trn_cell_rendezvous1_t*)inp, idx);
+}
+int
+trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt)
+{
+  trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
+  inp->rendezvous_cookie[idx] = elt;
+  return 0;
+}
+
+uint8_t *
+trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp)
+{
+  return inp->rendezvous_cookie;
+}
+const uint8_t  *
+trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp)
+{
+  return (const uint8_t  *)trn_cell_rendezvous1_getarray_rendezvous_cookie((trn_cell_rendezvous1_t*)inp);
+}
+size_t
+trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp)
+{
+  return TRUNNEL_DYNARRAY_LEN(&inp->handshake_info);
+}
+
+uint8_t
+trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx)
+{
+  return TRUNNEL_DYNARRAY_GET(&inp->handshake_info, idx);
+}
+
+uint8_t
+trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx)
+{
+  return trn_cell_rendezvous1_get_handshake_info((trn_cell_rendezvous1_t*)inp, idx);
+}
+int
+trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_SET(&inp->handshake_info, idx, elt);
+  return 0;
+}
+int
+trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt)
+{
+  TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->handshake_info, elt, {});
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+
+uint8_t *
+trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp)
+{
+  return inp->handshake_info.elts_;
+}
+const uint8_t  *
+trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp)
+{
+  return (const uint8_t  *)trn_cell_rendezvous1_getarray_handshake_info((trn_cell_rendezvous1_t*)inp);
+}
+int
+trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen)
+{
+  uint8_t *newptr;
+  newptr = trunnel_dynarray_setlen(&inp->handshake_info.allocated_,
+                 &inp->handshake_info.n_, inp->handshake_info.elts_, newlen,
+                 sizeof(inp->handshake_info.elts_[0]), (trunnel_free_fn_t) NULL,
+                 &inp->trunnel_error_code_);
+  if (newlen != 0 && newptr == NULL)
+    goto trunnel_alloc_failed;
+  inp->handshake_info.elts_ = newptr;
+  return 0;
+ trunnel_alloc_failed:
+  TRUNNEL_SET_ERROR_CODE(inp);
+  return -1;
+}
+const char *
+trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj)
+{
+  if (obj == NULL)
+    return "Object was NULL";
+  if (obj->trunnel_error_code_)
+    return "A set function failed on this object";
+  return NULL;
+}
+
+ssize_t
+trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj)
+{
+  ssize_t result = 0;
+
+  if (NULL != trn_cell_rendezvous1_check(obj))
+     return -1;
+
+
+  /* Length of u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
+  result += TRUNNEL_REND_COOKIE_LEN;
+
+  /* Length of u8 handshake_info[] */
+  result += TRUNNEL_DYNARRAY_LEN(&obj->handshake_info);
+  return result;
+}
+int
+trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj)
+{
+  int r = obj->trunnel_error_code_;
+  obj->trunnel_error_code_ = 0;
+  return r;
+}
+ssize_t
+trn_cell_rendezvous1_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous1_t *obj)
+{
+  ssize_t result = 0;
+  size_t written = 0;
+  uint8_t *ptr = output;
+  const char *msg;
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  const ssize_t encoded_len = trn_cell_rendezvous1_encoded_len(obj);
+#endif
+
+  if (NULL != (msg = trn_cell_rendezvous1_check(obj)))
+    goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  trunnel_assert(encoded_len >= 0);
+#endif
+
+  /* Encode u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
+  trunnel_assert(written <= avail);
+  if (avail - written < TRUNNEL_REND_COOKIE_LEN)
+    goto truncated;
+  memcpy(ptr, obj->rendezvous_cookie, TRUNNEL_REND_COOKIE_LEN);
+  written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
+
+  /* Encode u8 handshake_info[] */
+  {
+    size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->handshake_info);
+    trunnel_assert(written <= avail);
+    if (avail - written < elt_len)
+      goto truncated;
+    if (elt_len)
+      memcpy(ptr, obj->handshake_info.elts_, elt_len);
+    written += elt_len; ptr += elt_len;
+  }
+
+
+  trunnel_assert(ptr == output + written);
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+  {
+    trunnel_assert(encoded_len >= 0);
+    trunnel_assert((size_t)encoded_len == written);
+  }
+
+#endif
+
+  return written;
+
+ truncated:
+  result = -2;
+  goto fail;
+ check_failed:
+  (void)msg;
+  result = -1;
+  goto fail;
+ fail:
+  trunnel_assert(result < 0);
+  return result;
+}
+
+/** As trn_cell_rendezvous1_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+trn_cell_rendezvous1_parse_into(trn_cell_rendezvous1_t *obj, const uint8_t *input, const size_t len_in)
+{
+  const uint8_t *ptr = input;
+  size_t remaining = len_in;
+  ssize_t result = 0;
+  (void)result;
+
+  /* Parse u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
+  CHECK_REMAINING(TRUNNEL_REND_COOKIE_LEN, truncated);
+  memcpy(obj->rendezvous_cookie, ptr, TRUNNEL_REND_COOKIE_LEN);
+  remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
+
+  /* Parse u8 handshake_info[] */
+  TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->handshake_info, remaining, {});
+  obj->handshake_info.n_ = remaining;
+  if (remaining)
+    memcpy(obj->handshake_info.elts_, ptr, remaining);
+  ptr += remaining; remaining -= remaining;
+  trunnel_assert(ptr + remaining == input + len_in);
+  return len_in - remaining;
+
+ truncated:
+  return -2;
+ trunnel_alloc_failed:
+  return -1;
+}
+
+ssize_t
+trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in)
+{
+  ssize_t result;
+  *output = trn_cell_rendezvous1_new();
+  if (NULL == *output)
+    return -1;
+  result = trn_cell_rendezvous1_parse_into(*output, input, len_in);
+  if (result < 0) {
+    trn_cell_rendezvous1_free(*output);
+    *output = NULL;
+  }
+  return result;
+}

+ 118 - 0
src/trunnel/hs/cell_rendezvous.h

@@ -0,0 +1,118 @@
+/* cell_rendezvous.h -- generated by by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_CELL_RENDEZVOUS_H
+#define TRUNNEL_CELL_RENDEZVOUS_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+#define TRUNNEL_REND_COOKIE_LEN 20
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1)
+struct trn_cell_rendezvous1_st {
+  uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
+  TRUNNEL_DYNARRAY_HEAD(, uint8_t) handshake_info;
+  uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t;
+/** Return a newly allocated trn_cell_rendezvous1 with all elements
+ * set to zero.
+ */
+trn_cell_rendezvous1_t *trn_cell_rendezvous1_new(void);
+/** Release all storage held by the trn_cell_rendezvous1 in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *victim);
+/** Try to parse a trn_cell_rendezvous1 from the buffer in 'input',
+ * using up to 'len_in' bytes from the input buffer. On success,
+ * return the number of bytes consumed and set *output to the newly
+ * allocated trn_cell_rendezvous1_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * trn_cell_rendezvous1 in 'obj'. On failure, return a negative value.
+ * Note that this value may be an overestimate, and can even be an
+ * underestimate for certain unencodeable objects.
+ */
+ssize_t trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj);
+/** Try to encode the trn_cell_rendezvous1 from 'input' into the
+ * buffer at 'output', using up to 'avail' bytes of the output buffer.
+ * On success, return the number of bytes used. On failure, return -2
+ * if the buffer was not long enough, and -1 if the input was invalid.
+ */
+ssize_t trn_cell_rendezvous1_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous1_t *input);
+/** Check whether the internal state of the trn_cell_rendezvous1 in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj);
+/** Return the (constant) length of the array holding the
+ * rendezvous_cookie field of the trn_cell_rendezvous1_t in 'inp'.
+ */
+size_t trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp'.
+ */
+uint8_t trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx);
+/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return
+ * a const pointer
+ */
+uint8_t trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp', so that
+ * it will hold the value 'elt'.
+ */
+int trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array
+ * field rendezvous_cookie of 'inp'.
+ */
+uint8_t * trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp);
+/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return
+ * a const pointer
+ */
+const uint8_t  * trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp);
+/** Return the length of the dynamic array holding the handshake_info
+ * field of the trn_cell_rendezvous1_t in 'inp'.
+ */
+size_t trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * handshake_info of the trn_cell_rendezvous1_t in 'inp'.
+ */
+uint8_t trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx);
+/** As trn_cell_rendezvous1_get_handshake_info, but take and return a
+ * const pointer
+ */
+uint8_t trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * handshake_info of the trn_cell_rendezvous1_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field
+ * handshake_info of the trn_cell_rendezvous1_t in 'inp'.
+ */
+int trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field handshake_info
+ * of 'inp'.
+ */
+uint8_t * trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp);
+/** As trn_cell_rendezvous1_get_handshake_info, but take and return a
+ * const pointer
+ */
+const uint8_t  * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp);
+/** Change the length of the variable-length array field
+ * handshake_info of 'inp' to 'newlen'.Fill extra elements with 0.
+ * Return 0 on success; return -1 and set the error code on 'inp' on
+ * failure.
+ */
+int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen);
+
+
+#endif

+ 18 - 0
src/trunnel/hs/cell_rendezvous.trunnel

@@ -0,0 +1,18 @@
+/*
+ * This contains the definition of the RENDEZVOUS1 cell for onion service
+ * version 3 and onward. The following format is specified in proposal 224
+ * section 4.2.
+ */
+
+/* Rendezvous cookie length. */
+const TRUNNEL_REND_COOKIE_LEN = 20;
+
+/* RENDEZVOUS1 payload. See details in section 4.2. */
+struct trn_cell_rendezvous1 {
+  /* The RENDEZVOUS_COOKIE field. */
+  u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
+  
+  /* The HANDSHAKE_INFO field which has a variable length depending on the
+   * handshake type used. */
+  u8 handshake_info[];
+};

+ 2 - 0
src/trunnel/include.am

@@ -22,6 +22,7 @@ TRUNNELSOURCES = \
 	src/trunnel/hs/cell_common.c            \
 	src/trunnel/hs/cell_establish_intro.c	\
 	src/trunnel/hs/cell_introduce1.c \
+	src/trunnel/hs/cell_rendezvous.c \
 	src/trunnel/channelpadding_negotiation.c
 
 TRUNNELHEADERS = \
@@ -34,6 +35,7 @@ TRUNNELHEADERS = \
 	src/trunnel/hs/cell_common.h            \
 	src/trunnel/hs/cell_establish_intro.h	\
 	src/trunnel/hs/cell_introduce1.h \
+	src/trunnel/hs/cell_rendezvous.h \
 	src/trunnel/channelpadding_negotiation.h
 
 src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES)