]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Write unittest that covers cases of INTRODUCE1 handling.
authorGeorge Kadianakis <desnacked@riseup.net>
Mon, 27 Jan 2020 15:26:47 +0000 (17:26 +0200)
committerGeorge Kadianakis <desnacked@riseup.net>
Mon, 27 Jan 2020 23:08:41 +0000 (01:08 +0200)
Also fix some memleaks of other OB unittests.

src/core/or/circuitbuild.c
src/core/or/circuitbuild.h
src/feature/hs/hs_cell.c
src/feature/hs/hs_circuit.c
src/feature/hs/hs_circuit.h
src/feature/nodelist/nodelist.c
src/feature/nodelist/nodelist.h
src/test/hs_test_helpers.c
src/test/hs_test_helpers.h
src/test/test_hs_ob.c
src/test/test_hs_service.c

index 03ed2c7d292ff8034522372f3751d550f8785fc4..003b91af8df89817c1cd7ba62c4dd34c24df7f10 100644 (file)
@@ -2819,8 +2819,8 @@ extend_info_dup(extend_info_t *info)
  * If there is no chosen exit, or if we don't know the node_t for
  * the chosen exit, return NULL.
  */
-const node_t *
-build_state_get_exit_node(cpath_build_state_t *state)
+MOCK_IMPL(const node_t *,
+build_state_get_exit_node,(cpath_build_state_t *state))
 {
   if (!state || !state->chosen_exit)
     return NULL;
index f5a343906496c55252e713e916c8d3dcf13c28fe..48592dd346c1e4b139ecaa767798c9f06837284a 100644 (file)
@@ -66,7 +66,8 @@ int circuit_can_use_tap(const origin_circuit_t *circ);
 int circuit_has_usable_onion_key(const origin_circuit_t *circ);
 int extend_info_has_preferred_onion_key(const extend_info_t* ei);
 const uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state);
-const node_t *build_state_get_exit_node(cpath_build_state_t *state);
+MOCK_DECL(const node_t *,
+          build_state_get_exit_node,(cpath_build_state_t *state));
 const char *build_state_get_exit_nickname(cpath_build_state_t *state);
 
 struct circuit_guard_state_t;
index fce6fe02203ebd4bd5e67672873c1bd0f592172c..97a1691f16b22b1c6f4bc7cd823feb64b968892c 100644 (file)
@@ -869,7 +869,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
   /* 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"
+    log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the "
                       "same ENCRYPTED section was seen %ld seconds ago. "
                       "Dropping cell.", (long int) elapsed);
     goto done;
index febeec473087444d3b8e44304b5c1f8ef49b9ec5..97507df9f72233b02e25104951fde0a2fd44794f 100644 (file)
@@ -368,10 +368,10 @@ get_service_anonymity_string(const hs_service_t *service)
  * 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)
+MOCK_IMPL(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);
index 5c20e38187fc8f694929cda02f715ea13e665609..22e936e68594c6e715c7f11b990bbde70537e146 100644 (file)
@@ -79,6 +79,12 @@ create_rp_circuit_identifier(const hs_service_t *service,
                              const curve25519_public_key_t *server_pk,
                              const struct hs_ntor_rend_cell_keys_t *keys);
 
+struct hs_cell_introduce2_data_t;
+MOCK_DECL(STATIC void,
+launch_rendezvous_point_circuit,(const hs_service_t *service,
+                                 const hs_service_intro_point_t *ip,
+                                const struct hs_cell_introduce2_data_t *data));
+
 #endif /* defined(HS_CIRCUIT_PRIVATE) */
 
 #endif /* !defined(TOR_HS_CIRCUIT_H) */
index 94ff08826f16d67d9026e7bc4efe369f8be32d7a..1d0b1a0d4b19d5333b4745d8a8d3d94ea9795dcc 100644 (file)
@@ -1213,8 +1213,8 @@ node_get_rsa_id_digest(const node_t *node)
  * If node is NULL, returns an empty smartlist.
  *
  * The smartlist must be freed using link_specifier_smartlist_free(). */
-smartlist_t *
-node_get_link_specifier_smartlist(const node_t *node, bool direct_conn)
+MOCK_IMPL(smartlist_t *,
+node_get_link_specifier_smartlist,(const node_t *node, bool direct_conn))
 {
   link_specifier_t *ls;
   tor_addr_port_t ap;
index 9742e3dff2f3a38f1023027ee74ee8b44b704ff3..40aed0067fd14f1871bc00a507a8449d9568a39b 100644 (file)
@@ -78,8 +78,8 @@ int node_supports_ed25519_hs_intro(const node_t *node);
 int node_supports_v3_rendezvous_point(const node_t *node);
 int node_supports_establish_intro_dos_extension(const node_t *node);
 const uint8_t *node_get_rsa_id_digest(const node_t *node);
-smartlist_t *node_get_link_specifier_smartlist(const node_t *node,
-                                               bool direct_conn);
+MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
+                                                           bool direct_conn));
 void link_specifier_smartlist_free_(smartlist_t *ls_list);
 #define link_specifier_smartlist_free(ls_list) \
   FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list))
index eb4af1c9f7c08ff426dbc7117c8e46ddf404d087..5116fc7169a0b4985b3ccec0006213207c7d9705 100644 (file)
 #include "feature/hs/hs_service.h"
 #include "test/hs_test_helpers.h"
 
+/**
+ * Create an introduction point taken straight out of an HSv3 descriptor.
+ *
+ * Use 'signing_kp' to sign the introduction point certificates.
+ *
+ * If 'intro_auth_kp' is provided use that as the introduction point
+ * authentication keypair, otherwise generate one on the fly.
+ *
+ * If 'intro_enc_kp' is provided use that as the introduction point encryption
+ * keypair, otherwise generate one on the fly.
+ */
 hs_desc_intro_point_t *
 hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
-                            const char *addr, int legacy)
+                            const char *addr, int legacy,
+                            const ed25519_keypair_t *intro_auth_kp,
+                            const curve25519_keypair_t *intro_enc_kp)
 {
   int ret;
   ed25519_keypair_t auth_kp;
@@ -56,8 +69,12 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
     smartlist_add(ip->link_specifiers, ls_ip);
   }
 
-  ret = ed25519_keypair_generate(&auth_kp, 0);
-  tt_int_op(ret, OP_EQ, 0);
+  if (intro_auth_kp) {
+    memcpy(&auth_kp, intro_auth_kp, sizeof(ed25519_keypair_t));
+  } else {
+    ret = ed25519_keypair_generate(&auth_kp, 0);
+    tt_int_op(ret, OP_EQ, 0);
+  }
   ip->auth_key_cert = tor_cert_create(signing_kp, CERT_TYPE_AUTH_HS_IP_KEY,
                                       &auth_kp.pubkey, now,
                                       HS_DESC_CERT_LIFETIME,
@@ -85,8 +102,12 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
     ed25519_keypair_t ed25519_kp;
     tor_cert_t *cross_cert;
 
-    ret = curve25519_keypair_generate(&curve25519_kp, 0);
-    tt_int_op(ret, OP_EQ, 0);
+    if (intro_enc_kp) {
+      memcpy(&curve25519_kp, intro_enc_kp, sizeof(curve25519_keypair_t));
+    } else {
+      ret = curve25519_keypair_generate(&curve25519_kp, 0);
+      tt_int_op(ret, OP_EQ, 0);
+    }
     ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit,
                                             &curve25519_kp);
     cross_cert = tor_cert_create(signing_kp, CERT_TYPE_CROSS_HS_IP_KEYS,
@@ -95,6 +116,8 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
                                  CERT_FLAG_INCLUDE_SIGNING_KEY);
     tt_assert(cross_cert);
     ip->enc_key_cert = cross_cert;
+    memcpy(ip->enc_key.public_key, curve25519_kp.pubkey.public_key,
+           CURVE25519_PUBKEY_LEN);
   }
 
   intro_point = ip;
@@ -165,13 +188,17 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
   if (!no_ip) {
     /* Add four intro points. */
     smartlist_add(desc->encrypted_data.intro_points,
-              hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0));
+                  hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0,
+                                              NULL, NULL));
     smartlist_add(desc->encrypted_data.intro_points,
-              hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0));
+                  hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0,
+                                              NULL, NULL));
     smartlist_add(desc->encrypted_data.intro_points,
-              hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1));
+                  hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1,
+                                              NULL, NULL));
     smartlist_add(desc->encrypted_data.intro_points,
-              hs_helper_build_intro_point(signing_kp, now, "5.6.7.8", 1));
+                  hs_helper_build_intro_point(signing_kp, now, "5.6.7.8", 1,
+                                              NULL, NULL));
   }
 
   descp = desc;
index cad4532826e26fb7e444a02ebc153caa52d90e8f..23d11f2a4aecf4a35b0cf8c012ae43cc809a22e5 100644 (file)
@@ -8,9 +8,11 @@
 #include "feature/hs/hs_descriptor.h"
 
 /* Set of functions to help build and test descriptors. */
-hs_desc_intro_point_t *hs_helper_build_intro_point(
-                          const ed25519_keypair_t *signing_kp, time_t now,
-                          const char *addr, int legacy);
+hs_desc_intro_point_t *
+hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
+                            const char *addr, int legacy,
+                            const ed25519_keypair_t *intro_auth_kp,
+                            const curve25519_keypair_t *intro_enc_kp);
 hs_descriptor_t *hs_helper_build_hs_desc_no_ip(
                                  const ed25519_keypair_t *signing_kp);
 hs_descriptor_t *hs_helper_build_hs_desc_with_ip(
index c4d9d239d223500e4f7ba02984eef9740c6ee8f0..b84cef9dec99410addd2cf95edee98f3c7f2f35f 100644 (file)
@@ -169,6 +169,7 @@ static void
 test_get_subcredentials(void *arg)
 {
   int ret;
+  hs_service_t *service = NULL;
   hs_service_config_t config;
 
   (void) arg;
@@ -192,7 +193,7 @@ test_get_subcredentials(void *arg)
   smartlist_add(config.ob_master_pubkeys, &onion_addr_kp_1.pubkey);
 
   /* Set up an instance */
-  hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
+  service = tor_malloc_zero(sizeof(hs_service_t));
   service->config = config;
   service->desc_current = service_descriptor_new();
   service->desc_next = service_descriptor_new();
@@ -234,7 +235,12 @@ test_get_subcredentials(void *arg)
 
  done:
   tor_free(subcreds);
+
   smartlist_free(config.ob_master_pubkeys);
+  if (service) {
+    memset(&service->config, 0, sizeof(hs_service_config_t));
+    hs_service_free(service);
+  }
 
   UNMOCK(networkstatus_get_live_consensus);
 }
index e33d593d94bccce6d353ff69fe19da86bdcc0686..1767648bbeefb480380662fdfd522bc56502f764 100644 (file)
@@ -51,6 +51,8 @@
 #include "feature/hs/hs_common.h"
 #include "feature/hs/hs_config.h"
 #include "feature/hs/hs_ident.h"
+#include "feature/hs/hs_ob.h"
+#include "feature/hs/hs_cell.h"
 #include "feature/hs/hs_intropoint.h"
 #include "feature/hs/hs_service.h"
 #include "feature/nodelist/networkstatus.h"
@@ -109,6 +111,9 @@ mock_circuit_mark_for_close(circuit_t *circ, int reason, int line,
   return;
 }
 
+static size_t relay_payload_len;
+static char relay_payload[RELAY_PAYLOAD_SIZE];
+
 static int
 mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
                                   uint8_t relay_command, const char *payload,
@@ -124,6 +129,10 @@ mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
   (void) cpath_layer;
   (void) filename;
   (void) lineno;
+
+  memcpy(relay_payload, payload, payload_len);
+  relay_payload_len = payload_len;
+
   return 0;
 }
 
@@ -1160,7 +1169,7 @@ test_closing_intro_circs(void *arg)
 
 /** Test sending and receiving introduce2 cells */
 static void
-test_introduce2(void *arg)
+test_bad_introduce2(void *arg)
 {
   int ret;
   int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
@@ -2169,6 +2178,348 @@ test_export_client_circuit_id(void *arg)
   tor_free(cp2);
 }
 
+static smartlist_t *
+mock_node_get_link_specifier_smartlist(const node_t *node, bool direct_conn)
+{
+  (void) node;
+  (void) direct_conn;
+
+  smartlist_t *lspecs = smartlist_new();
+  link_specifier_t *ls_legacy = link_specifier_new();
+  smartlist_add(lspecs, ls_legacy);
+
+  return lspecs;
+}
+
+static node_t *fake_node = NULL;
+
+static const node_t *
+mock_build_state_get_exit_node(cpath_build_state_t *state)
+{
+  (void) state;
+
+  if (!fake_node) {
+    curve25519_secret_key_t seckey;
+    curve25519_secret_key_generate(&seckey, 0);
+
+    fake_node = tor_malloc_zero(sizeof(node_t));
+    fake_node->ri = tor_malloc_zero(sizeof(routerinfo_t));
+    fake_node->ri->onion_curve25519_pkey =
+      tor_malloc_zero(sizeof(curve25519_public_key_t));
+    curve25519_public_key_generate(fake_node->ri->onion_curve25519_pkey,
+                                   &seckey);
+  }
+
+  return fake_node;
+}
+
+static void
+mock_launch_rendezvous_point_circuit(const hs_service_t *service,
+                                     const hs_service_intro_point_t *ip,
+                                     const hs_cell_introduce2_data_t *data)
+{
+  (void) service;
+  (void) ip;
+  (void) data;
+  return;
+}
+
+/**
+ *  Test that INTRO2 cells are handled well by onion services in the normal
+ *  case and also when onionbalance is enabled.
+ */
+static void
+test_intro2_handling(void *arg)
+{
+  (void)arg;
+
+  MOCK(build_state_get_exit_node, mock_build_state_get_exit_node);
+  MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
+  MOCK(node_get_link_specifier_smartlist,
+       mock_node_get_link_specifier_smartlist);
+  MOCK(launch_rendezvous_point_circuit, mock_launch_rendezvous_point_circuit);
+
+  memset(relay_payload, 0, sizeof(relay_payload));
+
+  int retval;
+  time_t now = 0101010101;
+  update_approx_time(now);
+
+  /** OK this is the play:
+   *
+   *  In Act I, we have a standalone onion service X (without onionbalance
+   *  enabled). We test that X can properly handle INTRO2 cells sent by a
+   *  client Alice.
+   *
+   *  In Act II, we create an onionbalance setup with frontend being Z which
+   *  includes instances X and Y. We then setup onionbalance on X and test that
+   *  Alice who addresses Z can communicate with X through INTRO2 cells.
+   *
+   *  In Act III, we test that Alice can also communicate with X
+   *  directly even tho onionbalance is enabled.
+   *
+   *  And finally in Act IV, we check various cases where the INTRO2 cell
+   *  should not go through because the subcredentials don't line up
+   *  (e.g. Alice sends INTRO2 to X using Y's subcredential).
+   */
+
+  /** Let's start with some setup! Create the instances and the frontend
+      service, create Alice, etc:  */
+
+  /* Create instance X */
+  hs_service_t x_service;
+  memset(&x_service, 0, sizeof(hs_service_t));
+  /* Disable onionbalance */
+  x_service.config.ob_master_pubkeys = NULL;
+  x_service.state.replay_cache_rend_cookie = replaycache_new(0,0);
+
+  /* Create subcredential for x: */
+  ed25519_keypair_t x_identity_keypair;
+  hs_subcredential_t x_subcred;
+  ed25519_keypair_generate(&x_identity_keypair, 0);
+  hs_helper_get_subcred_from_identity_keypair(&x_identity_keypair,
+                                              &x_subcred);
+
+  /* Create the x instance's intro point */
+  hs_service_intro_point_t *x_ip = NULL;
+  {
+    curve25519_secret_key_t seckey;
+    curve25519_public_key_t pkey;
+    curve25519_secret_key_generate(&seckey, 0);
+    curve25519_public_key_generate(&pkey, &seckey);
+
+    node_t intro_node;
+    memset(&intro_node, 0, sizeof(intro_node));
+    routerinfo_t ri;
+    memset(&ri, 0, sizeof(routerinfo_t));
+    ri.onion_curve25519_pkey = &pkey;
+    intro_node.ri = &ri;
+
+    x_ip = service_intro_point_new(&intro_node);
+  }
+
+  /* Create z frontend's subcredential */
+  ed25519_keypair_t z_identity_keypair;
+  hs_subcredential_t z_subcred;
+  ed25519_keypair_generate(&z_identity_keypair, 0);
+  hs_helper_get_subcred_from_identity_keypair(&z_identity_keypair,
+                                              &z_subcred);
+
+  /* Create y instance's subcredential */
+  ed25519_keypair_t y_identity_keypair;
+  hs_subcredential_t y_subcred;
+  ed25519_keypair_generate(&y_identity_keypair, 0);
+  hs_helper_get_subcred_from_identity_keypair(&y_identity_keypair,
+                                              &y_subcred);
+
+  /* Create Alice's intro point */
+  hs_desc_intro_point_t *alice_ip;
+  ed25519_keypair_t signing_kp;
+  ed25519_keypair_generate(&signing_kp, 0);
+  alice_ip = hs_helper_build_intro_point(&signing_kp, now, "1.2.3.4", 0,
+                                         &x_ip->auth_key_kp,
+                                         &x_ip->enc_key_kp);
+
+  /* Create Alice's intro and rend circuits */
+  origin_circuit_t *intro_circ = origin_circuit_new();
+  intro_circ->cpath = tor_malloc_zero(sizeof(crypt_path_t));
+  intro_circ->cpath->prev = intro_circ->cpath;
+  intro_circ->hs_ident = tor_malloc_zero(sizeof(*intro_circ->hs_ident));
+  origin_circuit_t rend_circ;
+  rend_circ.hs_ident = tor_malloc_zero(sizeof(*rend_circ.hs_ident));
+  curve25519_keypair_generate(&rend_circ.hs_ident->rendezvous_client_kp, 0);
+  memset(rend_circ.hs_ident->rendezvous_cookie, 'r', HS_REND_COOKIE_LEN);
+
+  /* ************************************************************ */
+
+  /* Act I:
+   *
+   * Where Alice connects to X without onionbalance in the picture */
+
+  /* Create INTRODUCE1 */
+  tt_assert(fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
+  retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
+                                   alice_ip, &x_subcred);
+
+  /* Check that the payload was written successfully */
+  tt_int_op(retval, OP_EQ, 0);
+  tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
+  tt_int_op(relay_payload_len, OP_NE, 0);
+
+  /* Handle the cell */
+  retval = hs_circ_handle_introduce2(&x_service,
+                                    intro_circ, x_ip,
+                                    &x_subcred,
+                                    (uint8_t*)relay_payload,relay_payload_len);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* ************************************************************ */
+
+  /* Act II:
+   *
+   * We now create an onionbalance setup with Z being the frontend and X and Y
+   * being the backend instances. Make sure that Alice can talk with the
+   * backend instance X even tho she thinks she is talking to the frontend Z.
+   */
+
+  /* Now configure the X instance to do onionbalance with Z as the frontend */
+  x_service.config.ob_master_pubkeys = smartlist_new();
+  smartlist_add(x_service.config.ob_master_pubkeys,
+                &z_identity_keypair.pubkey);
+
+  /* Create descriptors for x and load next descriptor with the x's
+   * subcredential so that it can accept connections for itself. */
+  x_service.desc_current = service_descriptor_new();
+  memset(x_service.desc_current->desc->subcredential.subcred, 'C',SUBCRED_LEN);
+  x_service.desc_next = service_descriptor_new();
+  memcpy(&x_service.desc_next->desc->subcredential, &x_subcred, SUBCRED_LEN);
+
+  /* Refresh OB keys */
+  hs_ob_refresh_keys(&x_service);
+
+  /* Create INTRODUCE1 from Alice to X through Z */
+  memset(relay_payload, 0, sizeof(relay_payload));
+  retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
+                                   alice_ip, &z_subcred);
+
+  /* Check that the payload was written successfully */
+  tt_int_op(retval, OP_EQ, 0);
+  tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
+  tt_int_op(relay_payload_len, OP_NE, 0);
+
+  /* Deliver INTRODUCE1 to X even tho it carries Z's subcredential */
+  replaycache_free(x_service.state.replay_cache_rend_cookie);
+  x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
+
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &z_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, 0);
+
+  replaycache_free(x_ip->replay_cache);
+  x_ip->replay_cache = replaycache_new(0, 0);
+
+  replaycache_free(x_service.state.replay_cache_rend_cookie);
+  x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
+
+  /* ************************************************************ */
+
+  /* Act III:
+   *
+   * Now send a direct INTRODUCE cell from Alice to X using X's subcredential
+   * and check that it succeeds even with onionbalance enabled.
+   */
+
+  /* Refresh OB keys (just to check for memleaks) */
+  hs_ob_refresh_keys(&x_service);
+
+  /* Create INTRODUCE1 from Alice to X using X's subcred. */
+  memset(relay_payload, 0, sizeof(relay_payload));
+  retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
+                                   alice_ip, &x_subcred);
+
+  /* Check that the payload was written successfully */
+  tt_int_op(retval, OP_EQ, 0);
+  tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
+  tt_int_op(relay_payload_len, OP_NE, 0);
+
+  /* Send INTRODUCE1 to X with X's subcredential (should succeed) */
+  replaycache_free(x_service.state.replay_cache_rend_cookie);
+  x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
+
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &x_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* ************************************************************ */
+
+  /* Act IV:
+   *
+   * Test cases where the INTRO2 cell should not be able to decode.
+   */
+
+  /* Try sending the exact same INTRODUCE2 cell again and see that the intro
+   * point replay cache triggers: */
+  setup_full_capture_of_logs(LOG_WARN);
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &x_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, -1);
+  expect_log_msg_containing("with the same ENCRYPTED section");
+  teardown_capture_of_logs();
+
+  /* Now cleanup the intro point replay cache but not the service replay cache
+     and see that this one triggers this time. */
+  replaycache_free(x_ip->replay_cache);
+  x_ip->replay_cache = replaycache_new(0, 0);
+  setup_full_capture_of_logs(LOG_INFO);
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &x_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, -1);
+  expect_log_msg_containing("with same REND_COOKIE");
+  teardown_capture_of_logs();
+
+  /* Now just to make sure cleanup both replay caches and make sure that the
+     cell gets through */
+  replaycache_free(x_ip->replay_cache);
+  x_ip->replay_cache = replaycache_new(0, 0);
+  replaycache_free(x_service.state.replay_cache_rend_cookie);
+  x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &x_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* As a final thing, create an INTRODUCE1 cell from Alice to X using Y's
+   * subcred (should fail since Y is just another instance and not the frontend
+   * service!) */
+  memset(relay_payload, 0, sizeof(relay_payload));
+  retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
+                                   alice_ip, &y_subcred);
+  tt_int_op(retval, OP_EQ, 0);
+
+  /* Check that the payload was written successfully */
+  tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
+  tt_int_op(relay_payload_len, OP_NE, 0);
+
+  retval = hs_circ_handle_introduce2(&x_service,
+                                   intro_circ, x_ip,
+                                   &y_subcred,
+                                   (uint8_t*)relay_payload, relay_payload_len);
+  tt_int_op(retval, OP_EQ, -1);
+
+ done:
+  /* Start cleaning up X */
+  replaycache_free(x_service.state.replay_cache_rend_cookie);
+  smartlist_free(x_service.config.ob_master_pubkeys);
+  tor_free(x_service.ob_subcreds);
+  service_descriptor_free(x_service.desc_current);
+  service_descriptor_free(x_service.desc_next);
+  service_intro_point_free(x_ip);
+
+  /* Clean up Alice */
+  hs_desc_intro_point_free(alice_ip);
+  tor_free(rend_circ.hs_ident);
+
+  if (fake_node) {
+    tor_free(fake_node->ri->onion_curve25519_pkey);
+    tor_free(fake_node->ri);
+    tor_free(fake_node);
+  }
+
+  UNMOCK(build_state_get_exit_node);
+  UNMOCK(relay_send_command_from_edge_);
+  UNMOCK(node_get_link_specifier_smartlist);
+  UNMOCK(launch_rendezvous_point_circuit);
+}
+
 struct testcase_t hs_service_tests[] = {
   { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
     NULL, NULL },
@@ -2194,7 +2545,7 @@ struct testcase_t hs_service_tests[] = {
     NULL, NULL },
   { "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK,
     NULL, NULL },
-  { "introduce2", test_introduce2, TT_FORK,
+  { "bad_introduce2", test_bad_introduce2, TT_FORK,
     NULL, NULL },
   { "service_event", test_service_event, TT_FORK,
     NULL, NULL },
@@ -2212,6 +2563,7 @@ struct testcase_t hs_service_tests[] = {
     TT_FORK, NULL, NULL },
   { "export_client_circuit_id", test_export_client_circuit_id, TT_FORK,
     NULL, NULL },
+  { "intro2_handling", test_intro2_handling, TT_FORK, NULL, NULL },
 
   END_OF_TESTCASES
 };