Browse Source

Merge branch 'ticket21859_032_01_squashed'

Nick Mathewson 6 years ago
parent
commit
c387cc5022

+ 12 - 0
src/common/crypto.c

@@ -1854,6 +1854,18 @@ struct crypto_digest_t {
   } d;
 };
 
+#ifdef TOR_UNIT_TESTS
+
+digest_algorithm_t
+crypto_digest_get_algorithm(crypto_digest_t *digest)
+{
+  tor_assert(digest);
+
+  return digest->algorithm;
+}
+
+#endif
+
 /**
  * Return the number of bytes we need to malloc in order to get a
  * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe

+ 5 - 0
src/common/crypto.h

@@ -20,6 +20,9 @@
 #include "testsupport.h"
 #include "compat.h"
 
+#include <openssl/engine.h>
+#include "keccak-tiny/keccak-tiny.h"
+
 /*
   Macro to create an arbitrary OpenSSL version number as used by
   OPENSSL_VERSION_NUMBER or SSLeay(), since the actual numbers are a bit hard
@@ -335,6 +338,7 @@ struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh);
 void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in);
 
 #ifdef CRYPTO_PRIVATE
+
 STATIC int crypto_force_rand_ssleay(void);
 STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len);
 
@@ -346,6 +350,7 @@ extern int break_strongest_rng_fallback;
 
 #ifdef TOR_UNIT_TESTS
 void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src);
+digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest);
 #endif
 
 #endif

+ 79 - 16
src/or/circuitbuild.c

@@ -46,6 +46,7 @@
 #include "crypto.h"
 #include "directory.h"
 #include "entrynodes.h"
+#include "hs_ntor.h"
 #include "main.h"
 #include "microdesc.h"
 #include "networkstatus.h"
@@ -1370,40 +1371,76 @@ circuit_extend(cell_t *cell, circuit_t *circ)
   return 0;
 }
 
-/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in
- * key_data.  key_data must contain CPATH_KEY_MATERIAL bytes, which are
- * used as follows:
+/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data.
+ *
+ * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
+ * service circuits and <b>key_data</b> must be at least
+ * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
+ *
+ * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
+ * bytes, which are used as follows:
  *   - 20 to initialize f_digest
  *   - 20 to initialize b_digest
  *   - 16 to key f_crypto
  *   - 16 to key b_crypto
  *
  * (If 'reverse' is true, then f_XX and b_XX are swapped.)
+ *
+ * Return 0 if init was successful, else -1 if it failed.
  */
 int
-circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
-                          int reverse)
+circuit_init_cpath_crypto(crypt_path_t *cpath,
+                          const char *key_data, size_t key_data_len,
+                          int reverse, int is_hs_v3)
 {
   crypto_digest_t *tmp_digest;
   crypto_cipher_t *tmp_crypto;
+  size_t digest_len = 0;
+  size_t cipher_key_len = 0;
 
   tor_assert(cpath);
   tor_assert(key_data);
   tor_assert(!(cpath->f_crypto || cpath->b_crypto ||
              cpath->f_digest || cpath->b_digest));
 
-  cpath->f_digest = crypto_digest_new();
-  crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN);
-  cpath->b_digest = crypto_digest_new();
-  crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN);
+  /* Basic key size validation */
+  if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+    return -1;
+  } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) {
+    return -1;
+  }
 
-  if (!(cpath->f_crypto =
-        crypto_cipher_new(key_data+(2*DIGEST_LEN)))) {
+  /* If we are using this cpath for next gen onion services use SHA3-256,
+     otherwise use good ol' SHA1 */
+  if (is_hs_v3) {
+    digest_len = DIGEST256_LEN;
+    cipher_key_len = CIPHER256_KEY_LEN;
+    cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256);
+    cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256);
+  } else {
+    digest_len = DIGEST_LEN;
+    cipher_key_len = CIPHER_KEY_LEN;
+    cpath->f_digest = crypto_digest_new();
+    cpath->b_digest = crypto_digest_new();
+  }
+
+  tor_assert(digest_len != 0);
+  tor_assert(cipher_key_len != 0);
+
+  crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len);
+  crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len);
+
+  cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len),
+                                                cipher_key_len*8);
+  if (!cpath->f_crypto) {
     log_warn(LD_BUG,"Forward cipher initialization failed.");
     return -1;
   }
-  if (!(cpath->b_crypto =
-        crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) {
+
+  cpath->b_crypto = crypto_cipher_new_with_bits(
+                                        key_data+(2*digest_len)+cipher_key_len,
+                                        cipher_key_len*8);
+  if (!cpath->b_crypto) {
     log_warn(LD_BUG,"Backward cipher initialization failed.");
     return -1;
   }
@@ -1469,7 +1506,7 @@ circuit_finish_handshake(origin_circuit_t *circ,
 
   onion_handshake_state_release(&hop->handshake_state);
 
-  if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
+  if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) {
     return -END_CIRC_REASON_TORPROTOCOL;
   }
 
@@ -1536,12 +1573,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason)
 int
 onionskin_answer(or_circuit_t *circ,
                  const created_cell_t *created_cell,
-                 const char *keys,
+                 const char *keys, size_t keys_len,
                  const uint8_t *rend_circ_nonce)
 {
   cell_t cell;
   crypt_path_t *tmp_cpath;
 
+  tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN);
+
   if (created_cell_format(&cell, created_cell) < 0) {
     log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)",
              (int)created_cell->cell_type, (int)created_cell->handshake_len);
@@ -1557,7 +1596,7 @@ onionskin_answer(or_circuit_t *circ,
   log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
             (unsigned int)get_uint32(keys),
             (unsigned int)get_uint32(keys+20));
-  if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
+  if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) {
     log_warn(LD_BUG,"Circuit initialization failed");
     tor_free(tmp_cpath);
     return -1;
@@ -2357,6 +2396,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop)
   }
 }
 
+#ifdef TOR_UNIT_TESTS
+
+/** Unittest helper function: Count number of hops in cpath linked list. */
+unsigned int
+cpath_get_n_hops(crypt_path_t **head_ptr)
+{
+  unsigned int n_hops = 0;
+  crypt_path_t *tmp;
+
+  if (!*head_ptr) {
+    return 0;
+  }
+
+  tmp = *head_ptr;
+  if (tmp) {
+    n_hops++;
+    tmp = (*head_ptr)->next;
+  }
+
+  return n_hops;
+}
+
+#endif
+
 /** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
  * and <b>state</b> and the cpath <b>head</b> (currently populated only
  * to length <b>cur_len</b> to decide a suitable middle hop for a

+ 6 - 3
src/or/circuitbuild.h

@@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ);
 int circuit_send_next_onion_skin(origin_circuit_t *circ);
 void circuit_note_clock_jumped(int seconds_elapsed);
 int circuit_extend(cell_t *cell, circuit_t *circ);
-int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
-                              int reverse);
+int circuit_init_cpath_crypto(crypt_path_t *cpath,
+                              const char *key_data, size_t key_data_len,
+                              int reverse, int is_hs_v3);
 struct created_cell_t;
 int circuit_finish_handshake(origin_circuit_t *circ,
                              const struct created_cell_t *created_cell);
@@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer,
                       int reason);
 int onionskin_answer(or_circuit_t *circ,
                      const struct created_cell_t *created_cell,
-                     const char *keys,
+                     const char *keys, size_t keys_len,
                      const uint8_t *rend_circ_nonce);
 MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
                                                      int *need_uptime,
@@ -83,6 +84,8 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes));
 #if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS)
 STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags,
                                                   const or_options_t *options);
+unsigned int cpath_get_n_hops(crypt_path_t **head_ptr);
+
 #endif
 
 #endif

+ 2 - 0
src/or/circuitlist.c

@@ -67,6 +67,7 @@
 #include "main.h"
 #include "hs_circuitmap.h"
 #include "hs_common.h"
+#include "hs_ident.h"
 #include "networkstatus.h"
 #include "nodelist.h"
 #include "onion.h"
@@ -957,6 +958,7 @@ circuit_free(circuit_t *circ)
 
     crypto_pk_free(ocirc->intro_key);
     rend_data_free(ocirc->rend_data);
+    hs_ident_circuit_free(ocirc->hs_ident);
 
     tor_free(ocirc->dest_address);
     if (ocirc->socks_username) {

+ 35 - 9
src/or/circuituse.c

@@ -42,6 +42,8 @@
 #include "control.h"
 #include "entrynodes.h"
 #include "hs_common.h"
+#include "hs_client.h"
+#include "hs_ident.h"
 #include "nodelist.h"
 #include "networkstatus.h"
 #include "policies.h"
@@ -55,6 +57,36 @@
 static void circuit_expire_old_circuits_clientside(void);
 static void circuit_increment_failure_count(void);
 
+/** Check whether the hidden service destination of the stream at
+ *  <b>edge_conn</b> is the same as the destination of the circuit at
+ *  <b>origin_circ</b>. */
+static int
+circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
+                                 const origin_circuit_t *origin_circ)
+{
+  /* Check if this is a v2 rendezvous circ/stream */
+  if ((edge_conn->rend_data && !origin_circ->rend_data) ||
+      (!edge_conn->rend_data && origin_circ->rend_data) ||
+      (edge_conn->rend_data && origin_circ->rend_data &&
+       rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
+                            rend_data_get_address(origin_circ->rend_data)))) {
+    /* this circ is not for this conn */
+    return 0;
+  }
+
+  /* Check if this is a v3 rendezvous circ/stream */
+  if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
+      (!edge_conn->hs_ident && origin_circ->hs_ident) ||
+      (edge_conn->hs_ident && origin_circ->hs_ident &&
+       !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+                          &origin_circ->hs_ident->identity_pk))) {
+    /* this circ is not for this conn */
+    return 0;
+  }
+
+  return 1;
+}
+
 /** Return 1 if <b>circ</b> could be returned by circuit_get_best().
  * Else return 0.
  */
@@ -169,14 +201,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
       /* can't exit from this router */
       return 0;
     }
-  } else { /* not general */
+  } else { /* not general: this might be a rend circuit */
     const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
-    if ((edge_conn->rend_data && !origin_circ->rend_data) ||
-        (!edge_conn->rend_data && origin_circ->rend_data) ||
-        (edge_conn->rend_data && origin_circ->rend_data &&
-         rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
-                            rend_data_get_address(origin_circ->rend_data)))) {
-      /* this circ is not for this conn */
+    if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
       return 0;
     }
   }
@@ -2348,8 +2375,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
     /* We are attaching a stream to a rendezvous circuit.  That means
      * that an attempt to connect to a hidden service just
      * succeeded.  Tell rendclient.c. */
-    rend_client_note_connection_attempt_ended(
-                    ENTRY_TO_EDGE_CONN(apconn)->rend_data);
+    hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
   }
 
   if (cpath) { /* we were given one; use it */

+ 2 - 1
src/or/command.c

@@ -381,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
     created_cell.handshake_len = len;
 
     if (onionskin_answer(circ, &created_cell,
-                         (const char *)keys, rend_circ_nonce)<0) {
+                         (const char *)keys, sizeof(keys),
+                         rend_circ_nonce)<0) {
       log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing.");
       circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
       return;

+ 3 - 0
src/or/connection.c

@@ -84,6 +84,7 @@
 #include "geoip.h"
 #include "main.h"
 #include "hs_common.h"
+#include "hs_ident.h"
 #include "nodelist.h"
 #include "policies.h"
 #include "reasons.h"
@@ -605,6 +606,7 @@ connection_free_(connection_t *conn)
   }
   if (CONN_IS_EDGE(conn)) {
     rend_data_free(TO_EDGE_CONN(conn)->rend_data);
+    hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident);
   }
   if (conn->type == CONN_TYPE_CONTROL) {
     control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -636,6 +638,7 @@ connection_free_(connection_t *conn)
     }
 
     rend_data_free(dir_conn->rend_data);
+    hs_ident_dir_conn_free(dir_conn->hs_ident);
     if (dir_conn->guard_state) {
       /* Cancel before freeing, if it's still there. */
       entry_guard_cancel(&dir_conn->guard_state);

+ 7 - 3
src/or/connection_edge.c

@@ -2455,8 +2455,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn)
  *
  * If ap_conn is broken, mark it for close and return -1. Else return 0.
  */
-int
-connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+MOCK_IMPL(int,
+connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
 {
   char payload[CELL_PAYLOAD_SIZE];
   int payload_len;
@@ -3566,8 +3566,12 @@ int
 connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
 {
   tor_assert(conn);
-  if (conn->rend_data)
+  /* It should not be possible to set both of these structs */
+  tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident));
+
+  if (conn->rend_data || conn->hs_ident) {
     return 1;
+  }
   return 0;
 }
 

+ 2 - 1
src/or/connection_edge.h

@@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn);
 void connection_ap_about_to_close(entry_connection_t *edge_conn);
 void connection_exit_about_to_close(edge_connection_t *edge_conn);
 
-int connection_ap_handshake_send_begin(entry_connection_t *ap_conn);
+MOCK_DECL(int,
+          connection_ap_handshake_send_begin,(entry_connection_t *ap_conn));
 int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn);
 
 entry_connection_t  *connection_ap_make_link(connection_t *partner,

+ 1 - 1
src/or/cpuworker.c

@@ -372,7 +372,7 @@ cpuworker_onion_handshake_replyfn(void *work_)
 
   if (onionskin_answer(circ,
                        &rpl.created_cell,
-                       (const char*)rpl.keys,
+                       (const char*)rpl.keys, sizeof(rpl.keys),
                        rpl.rend_auth_material) < 0) {
     log_warn(LD_OR,"onionskin_answer failed. Closing.");
     circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);

+ 224 - 0
src/or/hs_circuit.c

@@ -0,0 +1,224 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.c
+ **/
+
+#include "or.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "hs_ntor.h"
+
+/* A circuit is about to become an e2e rendezvous circuit. Check
+ * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
+ * circuit purpose is properly set, otherwise return false. */
+static int
+circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
+                                    int is_service_side)
+{
+  if (is_service_side) {
+    if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
+      log_warn(LD_BUG,
+            "HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
+      return 0;
+    }
+  }
+
+  if (!is_service_side) {
+    if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+        circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+      log_warn(LD_BUG,
+            "Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
+ * circuit. Initialize the crypt path crypto using the output material from the
+ * ntor key exchange at <b>ntor_key_seed</b>.
+ *
+ * If <b>is_service_side</b> is set, we are the hidden service and the final
+ * hop of the rendezvous circuit is the client on the other side. */
+static crypt_path_t *
+create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
+                  int is_service_side)
+{
+  uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
+  crypt_path_t *cpath = NULL;
+
+  /* Do the key expansion */
+  if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
+                                    keys, sizeof(keys)) < 0) {
+    goto err;
+  }
+
+  /* Setup the cpath */
+  cpath = tor_malloc_zero(sizeof(crypt_path_t));
+  cpath->magic = CRYPT_PATH_MAGIC;
+
+  if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
+                                is_service_side, 1) < 0) {
+    tor_free(cpath);
+    goto err;
+  }
+
+ err:
+  memwipe(keys, 0, sizeof(keys));
+  return cpath;
+}
+
+/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
+ * service on the other side of the rendezvous circuit <b>circ</b>. Initialize
+ * the crypt path crypto using the body of the RENDEZVOUS1 cell at
+ * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes).
+ */
+static crypt_path_t *
+create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
+{
+  crypt_path_t *hop = NULL;
+  char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+
+  /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
+   * handshake...*/
+  tor_assert(circ->build_state);
+  tor_assert(circ->build_state->pending_final_cpath);
+  hop = circ->build_state->pending_final_cpath;
+
+  tor_assert(hop->rend_dh_handshake_state);
+  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
+                               (char*)rend_cell_body, DH_KEY_LEN,
+                               keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
+    log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+    goto err;
+  }
+  /* ... and set up cpath. */
+  if (circuit_init_cpath_crypto(hop,
+                                keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+                                0, 0) < 0)
+    goto err;
+
+  /* Check whether the digest is right... */
+  if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) {
+    log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
+    goto err;
+  }
+
+  /* clean up the crypto stuff we just made */
+  crypto_dh_free(hop->rend_dh_handshake_state);
+  hop->rend_dh_handshake_state = NULL;
+
+  goto done;
+
+ err:
+  hop = NULL;
+
+ done:
+  memwipe(keys, 0, sizeof(keys));
+  return hop;
+}
+
+/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
+ * <b>circ</b> ready for use to transfer HS relay cells. */
+static void
+finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
+                      int is_service_side)
+{
+  tor_assert(circ);
+  tor_assert(hop);
+
+  /* Notify the circuit state machine that we are splicing this circuit */
+  int new_circ_purpose = is_service_side ?
+    CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
+  circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
+
+  /* All is well. Extend the circuit. */
+  hop->state = CPATH_STATE_OPEN;
+  /* Set the windows to default. */
+  hop->package_window = circuit_initial_package_window();
+  hop->deliver_window = CIRCWINDOW_START;
+
+  /* Now that this circuit has finished connecting to its destination,
+   * make sure circuit_get_open_circ_or_launch is willing to return it
+   * so we can actually use it. */
+  circ->hs_circ_has_timed_out = 0;
+
+  /* Append the hop to the cpath of this circuit */
+  onion_append_to_cpath(&circ->cpath, hop);
+
+  /* In legacy code, 'pending_final_cpath' points to the final hop we just
+   * appended to the cpath. We set the original pointer to NULL so that we
+   * don't double free it. */
+  if (circ->build_state) {
+    circ->build_state->pending_final_cpath = NULL;
+  }
+
+  /* Finally, mark circuit as ready to be used for client streams */
+  if (!is_service_side) {
+    circuit_try_attaching_streams(circ);
+  }
+}
+
+/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
+ * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
+ * serve as a rendezvous end-to-end circuit between the client and the
+ * service. If <b>is_service_side</b> is set, then we are the hidden service
+ * and the other side is the client.
+ *
+ * Return 0 if the operation went well; in case of error return -1. */
+int
+hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+                               const uint8_t *ntor_key_seed, size_t seed_len,
+                               int is_service_side)
+{
+  if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
+                                        is_service_side))) {
+    return -1;
+  }
+
+  crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
+                                        is_service_side);
+  if (!hop) {
+    log_warn(LD_REND, "Couldn't get v3 %s cpath!",
+             is_service_side ? "service-side" : "client-side");
+    return -1;
+  }
+
+  finalize_rend_circuit(circ, hop, is_service_side);
+
+  return 0;
+}
+
+/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
+ * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
+ * extend the crypt path of <b>circ</b> so that the hidden service is on the
+ * other side. */
+int
+hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+                                             const uint8_t *rend_cell_body)
+{
+
+  if (BUG(!circuit_purpose_is_correct_for_rend(
+                                      TO_CIRCUIT(circ)->purpose, 0))) {
+    return -1;
+  }
+
+  crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
+  if (!hop) {
+    log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
+    return -1;
+  }
+
+  finalize_rend_circuit(circ, hop, 0);
+
+  return 0;
+}
+

+ 24 - 0
src/or/hs_circuit.h

@@ -0,0 +1,24 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.h
+ * \brief Header file containing circuit data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CIRCUIT_H
+#define TOR_HS_CIRCUIT_H
+
+#include "or.h"
+
+/* e2e circuit API. */
+
+int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
+                                   const uint8_t *ntor_key_seed,
+                                   size_t seed_len,
+                                   int is_service_side);
+int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+                                          const uint8_t *rend_cell_body);
+
+#endif /* TOR_HS_CIRCUIT_H */
+

+ 47 - 0
src/or/hs_client.c

@@ -0,0 +1,47 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service client functionality
+ **/
+
+#include "or.h"
+#include "hs_circuit.h"
+#include "connection_edge.h"
+#include "rendclient.h"
+
+#include "hs_client.h"
+
+/** A prop224 v3 HS circuit successfully connected to the hidden
+ *  service. Update the stream state at <b>hs_conn_ident</b> appropriately. */
+static void
+hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+{
+  (void) hs_conn_ident;
+
+  /* TODO: When implementing client side */
+  return;
+}
+
+/** A circuit just finished connecting to a hidden service that the stream
+ *  <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
+void
+hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
+{
+  tor_assert(connection_edge_is_rendezvous_stream(conn));
+
+  if (BUG(conn->rend_data && conn->hs_ident)) {
+    log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
+             "Prioritizing hs_ident");
+  }
+
+  if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
+    hs_client_attempt_succeeded(conn->hs_ident);
+    return;
+  } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
+    rend_client_note_connection_attempt_ended(conn->rend_data);
+    return;
+  }
+}
+

+ 16 - 0
src/or/hs_client.h

@@ -0,0 +1,16 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.h
+ * \brief Header file containing client data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CLIENT_H
+#define TOR_HS_CLIENT_H
+
+void hs_client_note_connection_attempt_succeeded(
+                                       const edge_connection_t *conn);
+
+#endif /* TOR_HS_CLIENT_H */
+

+ 6 - 0
src/or/hs_common.h

@@ -49,6 +49,12 @@
 /* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
 #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
 
+/* 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;
+
 int hs_check_service_private_dir(const char *username, const char *path,
                                  unsigned int dir_group_readable,
                                  unsigned int create);

+ 81 - 0
src/or/hs_ident.c

@@ -0,0 +1,81 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.c
+ * \brief Contains circuit and connection identifier code for the whole HS
+ *        subsytem.
+ **/
+
+#include "hs_ident.h"
+
+/* Return a newly allocated circuit identifier. The given public key is copied
+ * identity_pk into the identifier. */
+hs_ident_circuit_t *
+hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
+                     hs_ident_circuit_type_t circuit_type)
+{
+  tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
+             circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
+  hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+  ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+  ident->circuit_type = circuit_type;
+  return ident;
+}
+
+/* Free the given circuit identifier. */
+void
+hs_ident_circuit_free(hs_ident_circuit_t *ident)
+{
+  if (ident == NULL) {
+    return;
+  }
+  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 directory connection identifier src, return a newly allocated
+ * copy of it. This can't fail. */
+hs_ident_dir_conn_t *
+hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
+{
+  hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+  memcpy(ident, src, sizeof(*ident));
+  return ident;
+}
+
+/* Free the given directory connection identifier. */
+void
+hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident)
+{
+  if (ident == NULL) {
+    return;
+  }
+  memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
+  tor_free(ident);
+}
+
+/* Return a newly allocated edge connection identifier. The given public key
+ * identity_pk is copied into the identifier. */
+hs_ident_edge_conn_t *
+hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
+{
+  hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+  ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+  return ident;
+}
+
+/* Free the given edge connection identifier. */
+void
+hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
+{
+  if (ident == NULL) {
+    return;
+  }
+  memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
+  tor_free(ident);
+}
+

+ 124 - 0
src/or/hs_ident.h

@@ -0,0 +1,124 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.h
+ * \brief Header file containing circuit and connection identifier data for
+ *        the whole HS subsytem.
+ *
+ * \details
+ * This interface is used to uniquely identify a hidden service on a circuit
+ * or connection using the service identity public key. Once the circuit or
+ * connection subsystem calls in the hidden service one, we use those
+ * identifiers to lookup the corresponding objects like service, intro point
+ * and descriptor.
+ *
+ * Furthermore, the circuit identifier holds cryptographic material needed for
+ * the e2e encryption on the rendezvous circuit which is set once the
+ * rendezvous circuit has opened and ready to be used.
+ **/
+
+#ifndef TOR_HS_IDENT_H
+#define TOR_HS_IDENT_H
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_common.h"
+
+/* Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define HS_REND_COOKIE_LEN DIGEST_LEN
+
+/* Type of circuit an hs_ident_t object is associated with. */
+typedef enum {
+  HS_IDENT_CIRCUIT_INTRO      = 1,
+  HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
+} hs_ident_circuit_type_t;
+
+/* Client and service side circuit identifier that is used for hidden service
+ * circuit establishment. Not all fields contain data, it depends on the
+ * circuit purpose. This is attached to an origin_circuit_t. All fields are
+ * used by both client and service. */
+typedef struct hs_ident_circuit_t {
+  /* (All circuit) The public key used to uniquely identify the service. It is
+   * the one found in the onion address. */
+  ed25519_public_key_t identity_pk;
+
+  /* (All circuit) The type of circuit this identifier is attached to.
+   * Accessors of the fields in this object assert non fatal on this circuit
+   * type. In other words, if a rendezvous field is being accessed, the
+   * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
+   * set when an object is initialized in its constructor. */
+  hs_ident_circuit_type_t circuit_type;
+
+  /* (Only intro point circuit) Which type of authentication key this
+   * circuit identifier is using. */
+  hs_auth_key_type_t auth_key_type;
+
+  /* (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 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:
+   *    SERVER_PK   [32 bytes]
+   *    AUTH_MAC    [32 bytes]
+   */
+  uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
+
+  /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for
+   * the e2e encryption with the client on the circuit. */
+  uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN];
+
+  /* (Only rendezvous circuit) Number of streams associated with this
+   * rendezvous circuit. We track this because there is a check on a maximum
+   * value. */
+  uint64_t num_rdv_streams;
+} hs_ident_circuit_t;
+
+/* Client and service side directory connection identifier used for a
+ * directory connection to identify which service is being queried. This is
+ * attached to a dir_connection_t. */
+typedef struct hs_ident_dir_conn_t {
+  /* The public key used to uniquely identify the service. It is the one found
+   * in the onion address. */
+  ed25519_public_key_t identity_pk;
+
+  /* XXX: Client authorization. */
+} hs_ident_dir_conn_t;
+
+/* Client and service side edge connection identifier used for an edge
+ * connection to identify which service is being queried. This is attached to
+ * a edge_connection_t. */
+typedef struct hs_ident_edge_conn_t {
+  /* The public key used to uniquely identify the service. It is the one found
+   * in the onion address. */
+  ed25519_public_key_t identity_pk;
+
+  /* XXX: Client authorization. */
+} hs_ident_edge_conn_t;
+
+/* Circuit identifier API. */
+hs_ident_circuit_t *hs_ident_circuit_new(
+                             const ed25519_public_key_t *identity_pk,
+                             hs_ident_circuit_type_t circuit_type);
+void hs_ident_circuit_free(hs_ident_circuit_t *ident);
+
+/* Directory connection identifier API. */
+hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
+void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident);
+
+/* Edge connection identifier API. */
+hs_ident_edge_conn_t *hs_ident_edge_conn_new(
+                                    const ed25519_public_key_t *identity_pk);
+void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
+
+#endif /* TOR_HS_IDENT_H */
+

+ 21 - 29
src/or/hs_ntor.c

@@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good(
 
 /* Input length to KDF for key expansion */
 #define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN)
-/* Output length of KDF for key expansion */
-#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2)
-
-/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the
- *  circuit key expansion as specified by section '4.2.1. Key expansion' and
- *  return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */
-hs_ntor_rend_circuit_keys_t *
-hs_ntor_circuit_key_expansion(
-                       const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys)
+
+/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size
+ *  DIGEST256_LEN), do the circuit key expansion as specified by section
+ *  '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be
+ *  of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN).
+ *
+ * Return 0 if things went well, else return -1. */
+int
+hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len,
+                              uint8_t *keys_out, size_t keys_out_len)
 {
   uint8_t *ptr;
   uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN];
-  uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN];
   crypto_xof_t *xof;
-  hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL;
+
+  /* Sanity checks on lengths to make sure we are good */
+  if (BUG(seed_len != DIGEST256_LEN)) {
+    return -1;
+  }
+  if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+    return -1;
+  }
 
   /* Let's build the input to the KDF */
   ptr = kdf_input;
-  APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN);
+  APPEND(ptr, ntor_key_seed, DIGEST256_LEN);
   APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
   tor_assert(ptr == kdf_input + sizeof(kdf_input));
 
   /* Generate the keys */
   xof = crypto_xof_new();
   crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
-  crypto_xof_squeeze_bytes(xof, keys, sizeof(keys));
+  crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN);
   crypto_xof_free(xof);
 
-  /* Generate keys structure and assign keys to it */
-  rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t));
-  ptr = keys;
-  memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN);
-  ptr += DIGEST256_LEN;;
-  memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN);
-  ptr += DIGEST256_LEN;
-  memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN);
-  ptr += DIGEST256_LEN;
-  memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN);
-  ptr += CIPHER256_KEY_LEN;
-  memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN);
-  ptr += CIPHER256_KEY_LEN;
-  tor_assert(ptr == keys + sizeof(keys));
-
-  return rend_circuit_keys;
+  return 0;
 }
 

+ 7 - 17
src/or/hs_ntor.h

@@ -6,6 +6,10 @@
 
 #include "or.h"
 
+/* Output length of KDF for key expansion */
+#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \
+  (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2)
+
 /* Key material needed to encode/decode INTRODUCE1 cells */
 typedef struct {
   /* Key used for encryption of encrypted INTRODUCE1 blob */
@@ -23,21 +27,6 @@ typedef struct {
   uint8_t ntor_key_seed[DIGEST256_LEN];
 } hs_ntor_rend_cell_keys_t;
 
-/* Key material resulting from key expansion as detailed in section "4.2.1. Key
- * expansion" of rend-spec-ng.txt. */
-typedef struct {
-  /* Per-circuit key material used in ESTABLISH_INTRO cell */
-  uint8_t KH[DIGEST256_LEN];
-  /* Authentication key for outgoing RELAY cells */
-  uint8_t Df[DIGEST256_LEN];
-  /* Authentication key for incoming RELAY cells */
-  uint8_t Db[DIGEST256_LEN];
-  /* Encryption key for outgoing RELAY cells */
-  uint8_t Kf[CIPHER256_KEY_LEN];
-  /* Decryption key for incoming RELAY cells */
-  uint8_t Kb[CIPHER256_KEY_LEN];
-} hs_ntor_rend_circuit_keys_t;
-
 int hs_ntor_client_get_introduce1_keys(
                       const ed25519_public_key_t *intro_auth_pubkey,
                       const curve25519_public_key_t *intro_enc_pubkey,
@@ -66,8 +55,9 @@ int hs_ntor_service_get_rendezvous1_keys(
                   const curve25519_public_key_t *client_ephemeral_enc_pubkey,
                   hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
 
-hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion(
-                       const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys);
+int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed,
+                                  size_t seed_len,
+                                  uint8_t *keys_out, size_t keys_out_len);
 
 int hs_ntor_client_rendezvous2_mac_is_good(
                         const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,

+ 13 - 7
src/or/include.am

@@ -50,16 +50,19 @@ LIBTOR_A_SOURCES = \
 	src/or/dnsserv.c				\
 	src/or/fp_pair.c				\
 	src/or/geoip.c					\
-	src/or/hs_intropoint.c          \
-	src/or/hs_circuitmap.c          \
-	src/or/hs_ntor.c                \
-	src/or/hs_service.c             \
-	src/or/entrynodes.c				\
-	src/or/ext_orport.c				\
-	src/or/hibernate.c				\
 	src/or/hs_cache.c				\
+	src/or/hs_circuitmap.c				\
 	src/or/hs_common.c				\
+	src/or/hs_circuit.c				\
 	src/or/hs_descriptor.c				\
+	src/or/hs_ident.c				\
+	src/or/hs_intropoint.c				\
+	src/or/hs_ntor.c				\
+	src/or/hs_service.c				\
+	src/or/hs_client.c				\
+	src/or/entrynodes.c				\
+	src/or/ext_orport.c				\
+	src/or/hibernate.c				\
 	src/or/keypin.c					\
 	src/or/main.c					\
 	src/or/microdesc.c				\
@@ -181,11 +184,14 @@ ORHEADERS = \
 	src/or/hibernate.h				\
 	src/or/hs_cache.h				\
 	src/or/hs_common.h				\
+	src/or/hs_circuit.h				\
 	src/or/hs_descriptor.h				\
+	src/or/hs_ident.h				\
 	src/or/hs_intropoint.h          \
 	src/or/hs_circuitmap.h          \
 	src/or/hs_ntor.h                \
 	src/or/hs_service.h             \
+	src/or/hs_client.h              \
 	src/or/keypin.h					\
 	src/or/main.h					\
 	src/or/microdesc.h				\

+ 19 - 0
src/or/or.h

@@ -846,6 +846,11 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
   return DOWNCAST(rend_data_v2_t, d);
 }
 
+/* Stub because we can't include hs_ident.h. */
+typedef struct hs_ident_edge_conn_t hs_ident_edge_conn_t;
+typedef struct hs_ident_dir_conn_t hs_ident_dir_conn_t;
+typedef struct hs_ident_circuit_t hs_ident_circuit_t;
+
 /** Time interval for tracking replays of DH public keys received in
  * INTRODUCE2 cells.  Used only to avoid launching multiple
  * simultaneous attempts to connect to the same rendezvous point. */
@@ -1633,6 +1638,11 @@ typedef struct edge_connection_t {
    * an exit)? */
   rend_data_t *rend_data;
 
+  /* Hidden service connection identifier for edge connections. Used by the HS
+   * client-side code to identify client SOCKS connections and by the
+   * service-side code to match HS circuits with their streams. */
+  hs_ident_edge_conn_t *hs_ident;
+
   uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit
                          * connection.  Exit connections only. */
   uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell
@@ -1783,6 +1793,11 @@ typedef struct dir_connection_t {
   /** What rendezvous service are we querying for? */
   rend_data_t *rend_data;
 
+  /* Hidden service connection identifier for dir connections: Used by HS
+     client-side code to fetch HS descriptors, and by the service-side code to
+     upload descriptors. */
+  hs_ident_dir_conn_t *hs_ident;
+
   /** If this is a one-hop connection, tracks the state of the directory guard
    * for this connection (if any). */
   struct circuit_guard_state_t *guard_state;
@@ -3186,6 +3201,10 @@ typedef struct origin_circuit_t {
   /** Holds all rendezvous data on either client or service side. */
   rend_data_t *rend_data;
 
+  /** Holds hidden service identifier on either client or service side. This
+   * is for both introduction and rendezvous circuit. */
+  hs_ident_circuit_t *hs_ident;
+
   /** Holds the data that the entry guard system uses to track the
    * status of the guard this circuit is using, and thereby to determine
    * whether this circuit can be used. */

+ 4 - 48
src/or/rendclient.c

@@ -17,6 +17,7 @@
 #include "connection_edge.h"
 #include "directory.h"
 #include "hs_common.h"
+#include "hs_circuit.h"
 #include "main.h"
 #include "networkstatus.h"
 #include "nodelist.h"
@@ -1150,9 +1151,6 @@ int
 rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
                                size_t request_len)
 {
-  crypt_path_t *hop;
-  char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
-
   if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
        circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
       || !circ->build_state->pending_final_cpath) {
@@ -1170,55 +1168,13 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
 
   log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
 
-  /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
-   * handshake...*/
-  tor_assert(circ->build_state);
-  tor_assert(circ->build_state->pending_final_cpath);
-  hop = circ->build_state->pending_final_cpath;
-  tor_assert(hop->rend_dh_handshake_state);
-  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
-                               hop->rend_dh_handshake_state, (char*)request,
-                               DH_KEY_LEN,
-                               keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
-    log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+  if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
+    log_warn(LD_GENERAL, "Failed to setup circ");
     goto err;
   }
-  /* ... and set up cpath. */
-  if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0)
-    goto err;
-
-  /* Check whether the digest is right... */
-  if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) {
-    log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
-    goto err;
-  }
-
-  crypto_dh_free(hop->rend_dh_handshake_state);
-  hop->rend_dh_handshake_state = NULL;
-
-  /* All is well. Extend the circuit. */
-  circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED);
-  hop->state = CPATH_STATE_OPEN;
-  /* set the windows to default. these are the windows
-   * that the client thinks the service has.
-   */
-  hop->package_window = circuit_initial_package_window();
-  hop->deliver_window = CIRCWINDOW_START;
-
-  /* Now that this circuit has finished connecting to its destination,
-   * make sure circuit_get_open_circ_or_launch is willing to return it
-   * so we can actually use it. */
-  circ->hs_circ_has_timed_out = 0;
-
-  onion_append_to_cpath(&circ->cpath, hop);
-  circ->build_state->pending_final_cpath = NULL; /* prevent double-free */
-
-  circuit_try_attaching_streams(circ);
-
-  memwipe(keys, 0, sizeof(keys));
   return 0;
+
  err:
-  memwipe(keys, 0, sizeof(keys));
   circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
   return -1;
 }

+ 3 - 1
src/or/rendservice.c

@@ -2195,7 +2195,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
 
   cpath->rend_dh_handshake_state = dh;
   dh = NULL;
-  if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
+  if (circuit_init_cpath_crypto(cpath,
+                                keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+                                1, 0)<0)
     goto err;
   memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN);
 

+ 2 - 0
src/test/include.am

@@ -115,6 +115,7 @@ src_test_test_SOURCES = \
 	src/test/test_extorport.c \
 	src/test/test_hs.c \
 	src/test/test_hs_service.c \
+	src/test/test_hs_client.c  \
 	src/test/test_hs_intropoint.c \
 	src/test/test_handles.c \
 	src/test/test_hs_cache.c \
@@ -270,6 +271,7 @@ noinst_HEADERS+= \
 	src/test/test.h \
 	src/test/test_helpers.h \
 	src/test/test_dir_common.h \
+	src/test/test_connection.h \
 	src/test/test_descriptors.inc \
 	src/test/example_extrainfo.inc \
 	src/test/failing_routerdescs.inc \

+ 16 - 0
src/test/rend_test_helpers.c

@@ -71,3 +71,19 @@ create_descriptor(rend_service_descriptor_t **generated, char **service_id,
   crypto_pk_free(pk2);
 }
 
+rend_data_t *
+mock_rend_data(const char *onion_address)
+{
+  rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
+  rend_data_t *rend_query = &v2_data->base_;
+  rend_query->version = 2;
+
+  strlcpy(v2_data->onion_address, onion_address,
+          sizeof(v2_data->onion_address));
+  v2_data->auth_type = REND_NO_AUTH;
+  rend_query->hsdirs_fp = smartlist_new();
+  smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
+                                                 DIGEST_LEN));
+  return rend_query;
+}
+

+ 1 - 0
src/test/rend_test_helpers.h

@@ -10,6 +10,7 @@ void generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc,
                    char **service_id, int intro_points);
 void create_descriptor(rend_service_descriptor_t **generated,
                        char **service_id, int intro_points);
+rend_data_t *mock_rend_data(const char *onion_address);
 
 #endif
 

+ 1 - 0
src/test/test.c

@@ -1215,6 +1215,7 @@ struct testgroup_t testgroups[] = {
   { "hs_cache/", hs_cache },
   { "hs_descriptor/", hs_descriptor },
   { "hs_service/", hs_service_tests },
+  { "hs_client/", hs_client_tests },
   { "hs_intropoint/", hs_intropoint_tests },
   { "introduce/", introduce_tests },
   { "keypin/", keypin_tests },

+ 1 - 0
src/test/test.h

@@ -209,6 +209,7 @@ extern struct testcase_t hs_tests[];
 extern struct testcase_t hs_cache[];
 extern struct testcase_t hs_descriptor[];
 extern struct testcase_t hs_service_tests[];
+extern struct testcase_t hs_client_tests[];
 extern struct testcase_t hs_intropoint_tests[];
 extern struct testcase_t introduce_tests[];
 extern struct testcase_t keypin_tests[];

+ 3 - 90
src/test/test_connection.c

@@ -17,9 +17,8 @@
 #include "rendcache.h"
 #include "directory.h"
 
-static void test_conn_lookup_addr_helper(const char *address,
-                                         int family,
-                                         tor_addr_t *addr);
+#include "test_connection.h"
+#include "test_helpers.h"
 
 static void * test_conn_get_basic_setup(const struct testcase_t *tc);
 static int test_conn_get_basic_teardown(const struct testcase_t *tc,
@@ -62,48 +61,7 @@ static int test_conn_get_rsrc_teardown(const struct testcase_t *tc,
 #define TEST_CONN_UNATTACHED_STATE (AP_CONN_STATE_CIRCUIT_WAIT)
 #define TEST_CONN_ATTACHED_STATE   (AP_CONN_STATE_CONNECT_WAIT)
 
-#define TEST_CONN_FD_INIT 50
-static int mock_connection_connect_sockaddr_called = 0;
-static int fake_socket_number = TEST_CONN_FD_INIT;
-
-static int
-mock_connection_connect_sockaddr(connection_t *conn,
-                                 const struct sockaddr *sa,
-                                 socklen_t sa_len,
-                                 const struct sockaddr *bindaddr,
-                                 socklen_t bindaddr_len,
-                                 int *socket_error)
-{
-  (void)sa_len;
-  (void)bindaddr;
-  (void)bindaddr_len;
-
-  tor_assert(conn);
-  tor_assert(sa);
-  tor_assert(socket_error);
-
-  mock_connection_connect_sockaddr_called++;
-
-  conn->s = fake_socket_number++;
-  tt_assert(SOCKET_OK(conn->s));
-  /* We really should call tor_libevent_initialize() here. Because we don't,
-   * we are relying on other parts of the code not checking if the_event_base
-   * (and therefore event->ev_base) is NULL.  */
-  tt_assert(connection_add_connecting(conn) == 0);
-
- done:
-  /* Fake "connected" status */
-  return 1;
-}
-
-static int
-fake_close_socket(evutil_socket_t sock)
-{
-  (void)sock;
-  return 0;
-}
-
-static void
+void
 test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
 {
   int rv = 0;
@@ -122,51 +80,6 @@ test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
   tor_addr_make_null(addr, TEST_CONN_FAMILY);
 }
 
-static connection_t *
-test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
-{
-  connection_t *conn = NULL;
-  tor_addr_t addr;
-  int socket_err = 0;
-  int in_progress = 0;
-
-  MOCK(connection_connect_sockaddr,
-       mock_connection_connect_sockaddr);
-  MOCK(tor_close_socket, fake_close_socket);
-
-  init_connection_lists();
-
-  conn = connection_new(type, TEST_CONN_FAMILY);
-  tt_assert(conn);
-
-  test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
-  tt_assert(!tor_addr_is_null(&addr));
-
-  tor_addr_copy_tight(&conn->addr, &addr);
-  conn->port = TEST_CONN_PORT;
-  mock_connection_connect_sockaddr_called = 0;
-  in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
-                                   TEST_CONN_PORT, &socket_err);
-  tt_assert(mock_connection_connect_sockaddr_called == 1);
-  tt_assert(!socket_err);
-  tt_assert(in_progress == 0 || in_progress == 1);
-
-  /* fake some of the attributes so the connection looks OK */
-  conn->state = state;
-  conn->purpose = purpose;
-  assert_connection_ok(conn, time(NULL));
-
-  UNMOCK(connection_connect_sockaddr);
-  UNMOCK(tor_close_socket);
-  return conn;
-
-  /* On failure */
- done:
-  UNMOCK(connection_connect_sockaddr);
-  UNMOCK(tor_close_socket);
-  return NULL;
-}
-
 static void *
 test_conn_get_basic_setup(const struct testcase_t *tc)
 {

+ 13 - 0
src/test/test_connection.h

@@ -0,0 +1,13 @@
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/** Some constants used by test_connection and helpers */
+#define TEST_CONN_FAMILY        (AF_INET)
+#define TEST_CONN_ADDRESS       "127.0.0.1"
+#define TEST_CONN_PORT          (12345)
+#define TEST_CONN_ADDRESS_PORT  "127.0.0.1:12345"
+#define TEST_CONN_FD_INIT 50
+
+void test_conn_lookup_addr_helper(const char *address,
+                                  int family, tor_addr_t *addr);
+

+ 96 - 0
src/test/test_helpers.c

@@ -7,9 +7,14 @@
  */
 
 #define ROUTERLIST_PRIVATE
+#define CONNECTION_PRIVATE
+#define MAIN_PRIVATE
+
 #include "orconfig.h"
 #include "or.h"
 
+#include "connection.h"
+#include "main.h"
 #include "relay.h"
 #include "routerlist.h"
 #include "nodelist.h"
@@ -17,6 +22,7 @@
 
 #include "test.h"
 #include "test_helpers.h"
+#include "test_connection.h"
 
 #ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS
 DISABLE_GCC_WARNING(overlength-strings)
@@ -143,3 +149,93 @@ mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
   return tor_addr_lookup__real(name, family, out);
 }
 
+/*********** Helper funcs for making new connections/streams *****************/
+
+/* Helper for test_conn_get_connection() */
+static int
+fake_close_socket(evutil_socket_t sock)
+{
+  (void)sock;
+  return 0;
+}
+
+static int mock_connection_connect_sockaddr_called = 0;
+static int fake_socket_number = TEST_CONN_FD_INIT;
+
+/* Helper for test_conn_get_connection() */
+static int
+mock_connection_connect_sockaddr(connection_t *conn,
+                                 const struct sockaddr *sa,
+                                 socklen_t sa_len,
+                                 const struct sockaddr *bindaddr,
+                                 socklen_t bindaddr_len,
+                                 int *socket_error)
+{
+  (void)sa_len;
+  (void)bindaddr;
+  (void)bindaddr_len;
+
+  tor_assert(conn);
+  tor_assert(sa);
+  tor_assert(socket_error);
+
+  mock_connection_connect_sockaddr_called++;
+
+  conn->s = fake_socket_number++;
+  tt_assert(SOCKET_OK(conn->s));
+  /* We really should call tor_libevent_initialize() here. Because we don't,
+   * we are relying on other parts of the code not checking if the_event_base
+   * (and therefore event->ev_base) is NULL.  */
+  tt_assert(connection_add_connecting(conn) == 0);
+
+ done:
+  /* Fake "connected" status */
+  return 1;
+}
+
+/** Create and return a new connection/stream */
+connection_t *
+test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
+{
+  connection_t *conn = NULL;
+  tor_addr_t addr;
+  int socket_err = 0;
+  int in_progress = 0;
+
+  MOCK(connection_connect_sockaddr,
+       mock_connection_connect_sockaddr);
+  MOCK(tor_close_socket, fake_close_socket);
+
+  init_connection_lists();
+
+  conn = connection_new(type, TEST_CONN_FAMILY);
+  tt_assert(conn);
+
+  test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
+  tt_assert(!tor_addr_is_null(&addr));
+
+  tor_addr_copy_tight(&conn->addr, &addr);
+  conn->port = TEST_CONN_PORT;
+  mock_connection_connect_sockaddr_called = 0;
+  in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
+                                   TEST_CONN_PORT, &socket_err);
+  tt_assert(mock_connection_connect_sockaddr_called == 1);
+  tt_assert(!socket_err);
+  tt_assert(in_progress == 0 || in_progress == 1);
+
+  /* fake some of the attributes so the connection looks OK */
+  conn->state = state;
+  conn->purpose = purpose;
+  assert_connection_ok(conn, time(NULL));
+
+  UNMOCK(connection_connect_sockaddr);
+  UNMOCK(tor_close_socket);
+  return conn;
+
+  /* On failure */
+ done:
+  UNMOCK(connection_connect_sockaddr);
+  UNMOCK(tor_close_socket);
+  return NULL;
+}
+

+ 7 - 2
src/test/test_helpers.h

@@ -1,9 +1,11 @@
-/* Copyright (c) 2014-2017, The Tor Project, Inc. */
+/* Copyright (c) 2017, The Tor Project, Inc. */
 /* See LICENSE for licensing information */
 
 #ifndef TOR_TEST_HELPERS_H
 #define TOR_TEST_HELPERS_H
 
+#include "or.h"
+
 const char *get_yesterday_date_str(void);
 
 circuit_t * dummy_origin_circuit_new(int num_cells);
@@ -20,7 +22,10 @@ void connection_write_to_buf_mock(const char *string, size_t len,
 int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
                                             uint16_t family, tor_addr_t *out);
 
+connection_t *test_conn_get_connection(uint8_t state,
+                                       uint8_t type, uint8_t purpose);
+
 extern const char TEST_DESCRIPTORS[];
 
-#endif
+#endif /* TOR_TEST_HELPERS_H */
 

+ 286 - 0
src/test/test_hs_client.c

@@ -0,0 +1,286 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_client.c
+ * \brief Test prop224 HS client functionality.
+ */
+
+#define CRYPTO_PRIVATE
+#define MAIN_PRIVATE
+#define TOR_CHANNEL_INTERNAL_
+#define CIRCUITBUILD_PRIVATE
+#define CIRCUITLIST_PRIVATE
+#define CONNECTION_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+#include "rend_test_helpers.h"
+
+#include "config.h"
+#include "crypto.h"
+#include "channeltls.h"
+
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "circuitlist.h"
+#include "circuitbuild.h"
+#include "connection.h"
+#include "connection_edge.h"
+
+static int
+mock_connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+{
+  (void) ap_conn;
+  return 0;
+}
+
+/* Test helper function: Setup a circuit and a stream with the same hidden
+ * service destination, and put them in <b>circ_out</b> and
+ * <b>conn_out</b>. Make the stream wait for circuits to be established to the
+ * hidden service. */
+static int
+helper_get_circ_and_stream_for_test(origin_circuit_t **circ_out,
+                                    connection_t **conn_out,
+                                    int is_legacy)
+{
+  int retval;
+  channel_tls_t *n_chan=NULL;
+  rend_data_t *conn_rend_data = NULL;
+  origin_circuit_t *or_circ = NULL;
+  connection_t *conn = NULL;
+  ed25519_public_key_t service_pk;
+
+  /* Make a dummy connection stream and make it wait for our circuit */
+  conn = test_conn_get_connection(AP_CONN_STATE_CIRCUIT_WAIT,
+                                  CONN_TYPE_AP /* ??? */,
+                                  0);
+  if (is_legacy) {
+    /* Legacy: Setup rend_data of stream */
+    char service_id[REND_SERVICE_ID_LEN_BASE32+1] = {0};
+    TO_EDGE_CONN(conn)->rend_data = mock_rend_data(service_id);
+    conn_rend_data = TO_EDGE_CONN(conn)->rend_data;
+  } else {
+    /* prop224: Setup hs conn identifier on the stream */
+    ed25519_secret_key_t sk;
+    tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
+    tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
+
+    /* Setup hs_conn_identifier of stream */
+    TO_EDGE_CONN(conn)->hs_ident = hs_ident_edge_conn_new(&service_pk);
+  }
+
+  /* Make it wait for circuit */
+  connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn));
+
+  /* This is needed to silence a BUG warning from
+     connection_edge_update_circuit_isolation() */
+  TO_ENTRY_CONN(conn)->original_dest_address =
+    tor_strdup(TO_ENTRY_CONN(conn)->socks_request->address);
+
+  /****************************************************/
+
+  /* Now make dummy circuit */
+  or_circ = origin_circuit_new();
+
+  or_circ->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
+
+  or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
+  or_circ->build_state->is_internal = 1;
+
+  if (is_legacy) {
+    /* Legacy: Setup rend data and final cpath */
+    or_circ->build_state->pending_final_cpath =
+      tor_malloc_zero(sizeof(crypt_path_t));
+    or_circ->build_state->pending_final_cpath->magic = CRYPT_PATH_MAGIC;
+    or_circ->build_state->pending_final_cpath->rend_dh_handshake_state =
+      crypto_dh_new(DH_TYPE_REND);
+    tt_assert(
+         or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
+    retval = crypto_dh_generate_public(
+           or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
+    tt_int_op(retval, ==, 0);
+    or_circ->rend_data = rend_data_dup(conn_rend_data);
+  } else {
+    /* prop224: Setup hs ident on the circuit */
+    or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
+                                             HS_IDENT_CIRCUIT_RENDEZVOUS);
+  }
+
+  TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
+
+  /* fake n_chan */
+  n_chan = tor_malloc_zero(sizeof(channel_tls_t));
+  n_chan->base_.global_identifier = 1;
+  or_circ->base_.n_chan = &(n_chan->base_);
+
+  *circ_out = or_circ;
+  *conn_out = conn;
+
+  return 0;
+
+ done:
+  /* something failed */
+  return -1;
+}
+
+/* Test: Ensure that setting up legacy e2e rendezvous circuits works
+ * correctly. */
+static void
+test_e2e_rend_circuit_setup_legacy(void *arg)
+{
+  int retval;
+  origin_circuit_t *or_circ = NULL;
+  connection_t *conn = NULL;
+
+  (void) arg;
+
+  /** In this test we create a v2 legacy HS stream and a circuit with the same
+   *  hidden service destination. We make the stream wait for circuits to be
+   *  established to the hidden service, and then we complete the circuit using
+   *  the hs_circuit_setup_e2e_rend_circ_legacy_client() function. We then
+   *  check that the end-to-end cpath was setup correctly and that the stream
+   *  was attached to the circuit as expected. */
+
+  MOCK(connection_ap_handshake_send_begin,
+       mock_connection_ap_handshake_send_begin);
+
+  /* Setup */
+  retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 1);
+  tt_int_op(retval, OP_EQ, 0);
+  tt_assert(or_circ);
+  tt_assert(conn);
+
+  /* Check number of hops */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, ==, 0);
+
+  /* Check that our stream is not attached on any circuits */
+  tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
+
+  /********************************************** */
+
+  /* Make a good RENDEZVOUS1 cell body because it needs to pass key exchange
+   * digest verification... */
+  uint8_t rend_cell_body[DH_KEY_LEN+DIGEST_LEN] = {2};
+  {
+    char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+    crypto_dh_t *dh_state =
+      or_circ->build_state->pending_final_cpath->rend_dh_handshake_state;
+    /* compute and overwrite digest of cell body with the right value */
+    retval = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh_state,
+                                      (char*)rend_cell_body, DH_KEY_LEN,
+                                      keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN);
+    tt_int_op(retval, OP_GT, 0);
+    memcpy(rend_cell_body+DH_KEY_LEN, keys, DIGEST_LEN);
+  }
+
+  /* Setup the circuit */
+  retval = hs_circuit_setup_e2e_rend_circ_legacy_client(or_circ,
+                                                        rend_cell_body);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /**********************************************/
+
+  /* See that a hop was added to the circuit's cpath */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, OP_EQ, 1);
+
+  /* Check the digest algo */
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+            OP_EQ, DIGEST_SHA1);
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+            OP_EQ, DIGEST_SHA1);
+  tt_assert(or_circ->cpath->f_crypto);
+  tt_assert(or_circ->cpath->b_crypto);
+
+  /* Ensure that circ purpose was changed */
+  tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
+
+  /* Test that stream got attached */
+  tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
+
+ done:
+  connection_free_(conn);
+  tor_free(TO_CIRCUIT(or_circ)->n_chan);
+  circuit_free(TO_CIRCUIT(or_circ));
+}
+
+/* Test: Ensure that setting up v3 rendezvous circuits works correctly. */
+static void
+test_e2e_rend_circuit_setup(void *arg)
+{
+  uint8_t ntor_key_seed[DIGEST256_LEN] = {0};
+  origin_circuit_t *or_circ;
+  int retval;
+  connection_t *conn = NULL;
+
+  (void) arg;
+
+  /** In this test we create a prop224 v3 HS stream and a circuit with the same
+   *  hidden service destination. We make the stream wait for circuits to be
+   *  established to the hidden service, and then we complete the circuit using
+   *  the hs_circuit_setup_e2e_rend_circ() function. We then check that the
+   *  end-to-end cpath was setup correctly and that the stream was attached to
+   *  the circuit as expected. */
+
+  MOCK(connection_ap_handshake_send_begin,
+       mock_connection_ap_handshake_send_begin);
+
+  /* Setup */
+  retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 0);
+  tt_int_op(retval, OP_EQ, 0);
+  tt_assert(or_circ);
+  tt_assert(conn);
+
+  /* Check number of hops: There should be no hops yet to this circ */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, ==, 0);
+  tt_assert(!or_circ->cpath);
+
+  /* Check that our stream is not attached on any circuits */
+  tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
+
+  /**********************************************/
+
+  /* Setup the circuit */
+  retval = hs_circuit_setup_e2e_rend_circ(or_circ,
+                                          ntor_key_seed, sizeof(ntor_key_seed),
+                                          0);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /**********************************************/
+
+  /* See that a hop was added to the circuit's cpath */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, OP_EQ, 1);
+
+  /* Check that the crypt path has prop224 algorithm parameters */
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+            OP_EQ, DIGEST_SHA3_256);
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+            OP_EQ, DIGEST_SHA3_256);
+  tt_assert(or_circ->cpath->f_crypto);
+  tt_assert(or_circ->cpath->b_crypto);
+
+  /* Ensure that circ purpose was changed */
+  tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
+
+  /* Test that stream got attached */
+  tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
+
+ done:
+  connection_free_(conn);
+  tor_free(TO_CIRCUIT(or_circ)->n_chan);
+  circuit_free(TO_CIRCUIT(or_circ));
+}
+
+struct testcase_t hs_client_tests[] = {
+  { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy,
+    TT_FORK, NULL, NULL },
+  { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup,
+    TT_FORK, NULL, NULL },
+  END_OF_TESTCASES
+};
+

+ 88 - 6
src/test/test_hs_service.c

@@ -6,20 +6,34 @@
  * \brief Test hidden service functionality.
  */
 
+#define CIRCUITBUILD_PRIVATE
+#define CIRCUITLIST_PRIVATE
+#define CONNECTION_PRIVATE
+#define CRYPTO_PRIVATE
 #define HS_COMMON_PRIVATE
-#define HS_SERVICE_PRIVATE
 #define HS_INTROPOINT_PRIVATE
+#define MAIN_PRIVATE
+#define TOR_CHANNEL_INTERNAL_
 
 #include "test.h"
 #include "log_test_helpers.h"
-#include "crypto.h"
-
-#include "hs/cell_establish_intro.h"
+#include "rend_test_helpers.h"
+
+#include "or.h"
+#include "channeltls.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "connection.h"
+#include "hs_circuit.h"
 #include "hs_common.h"
-#include "hs_service.h"
+#include "hs_ident.h"
 #include "hs_intropoint.h"
-
 #include "hs_ntor.h"
+#include "hs_service.h"
+#include "main.h"
+#include "rendservice.h"
 
 /** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we
  *  parse it from the receiver side. */
@@ -235,6 +249,72 @@ test_time_period(void *arg)
   ;
 }
 
+/* Test: Ensure that setting up rendezvous circuits works correctly. */
+static void
+test_e2e_rend_circuit_setup(void *arg)
+{
+  ed25519_public_key_t service_pk;
+  origin_circuit_t *or_circ;
+  int retval;
+
+  /** In this test we create a v3 prop224 service-side rendezvous circuit.
+   *  We simulate an HS ntor key exchange with a client, and check that
+   *  the circuit was setup correctly and is ready to accept rendezvous data */
+
+  (void) arg;
+
+  /* Now make dummy circuit */
+  {
+    or_circ = origin_circuit_new();
+
+    or_circ->base_.purpose = CIRCUIT_PURPOSE_S_CONNECT_REND;
+
+    or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
+    or_circ->build_state->is_internal = 1;
+
+    /* prop224: Setup hs conn identifier on the stream */
+    ed25519_secret_key_t sk;
+    tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
+    tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
+
+    or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
+                                             HS_IDENT_CIRCUIT_RENDEZVOUS);
+
+    TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
+  }
+
+  /* Check number of hops */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* Setup the circuit: do the ntor key exchange */
+  {
+    uint8_t ntor_key_seed[DIGEST256_LEN] = {2};
+    retval = hs_circuit_setup_e2e_rend_circ(or_circ,
+                                       ntor_key_seed, sizeof(ntor_key_seed),
+                                       1);
+    tt_int_op(retval, OP_EQ, 0);
+  }
+
+  /* See that a hop was added to the circuit's cpath */
+  retval = cpath_get_n_hops(&or_circ->cpath);
+  tt_int_op(retval, OP_EQ, 1);
+
+  /* Check the digest algo */
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+            OP_EQ, DIGEST_SHA3_256);
+  tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+            OP_EQ, DIGEST_SHA3_256);
+  tt_assert(or_circ->cpath->f_crypto);
+  tt_assert(or_circ->cpath->b_crypto);
+
+  /* Ensure that circ purpose was changed */
+  tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED);
+
+ done:
+  circuit_free(TO_CIRCUIT(or_circ));
+}
+
 struct testcase_t hs_service_tests[] = {
   { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
     NULL, NULL },
@@ -244,6 +324,8 @@ struct testcase_t hs_service_tests[] = {
     NULL, NULL },
   { "time_period", test_time_period, TT_FORK,
     NULL, NULL },
+  { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
+    NULL, NULL },
 
   END_OF_TESTCASES
 };

+ 0 - 16
src/test/test_rendcache.c

@@ -21,22 +21,6 @@ static const int TIME_IN_THE_PAST = -(REND_CACHE_MAX_AGE + \
                                       REND_CACHE_MAX_SKEW + 60);
 static const int TIME_IN_THE_FUTURE = REND_CACHE_MAX_SKEW + 60;
 
-static rend_data_t *
-mock_rend_data(const char *onion_address)
-{
-  rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
-  rend_data_t *rend_query = &v2_data->base_;
-  rend_query->version = 2;
-
-  strlcpy(v2_data->onion_address, onion_address,
-          sizeof(v2_data->onion_address));
-  v2_data->auth_type = REND_NO_AUTH;
-  rend_query->hsdirs_fp = smartlist_new();
-  smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
-                                                 DIGEST_LEN));
-  return rend_query;
-}
-
 static void
 test_rend_cache_lookup_entry(void *data)
 {