Переглянути джерело

Refactor INTRODUCE2 parsing code in rend_service_introduce()

Andrea Shepard 12 роки тому
батько
коміт
471ab34032
2 змінених файлів з 1058 додано та 182 видалено
  1. 985 182
      src/or/rendservice.c
  2. 73 0
      src/or/rendservice.h

+ 985 - 182
src/or/rendservice.c

@@ -7,6 +7,8 @@
  * \brief The hidden-service side of rendezvous functionality.
  **/
 
+#define RENDSERVICE_PRIVATE
+
 #include "or.h"
 #include "circuitbuild.h"
 #include "circuitlist.h"
@@ -29,6 +31,10 @@ static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro,
                                             const char *pk_digest);
 static rend_intro_point_t *find_intro_point(origin_circuit_t *circ);
 
+static extend_info_t *find_rp_for_intro(
+    const rend_intro_cell_t *intro,
+    uint8_t *need_free_out, char **err_msg_out);
+
 static int intro_point_accepted_intro_count(rend_intro_point_t *intro);
 static int intro_point_should_expire_now(rend_intro_point_t *intro,
                                          time_t now);
@@ -37,6 +43,22 @@ static int rend_service_load_keys(struct rend_service_t *s);
 static int rend_service_load_auth_keys(struct rend_service_t *s,
                                        const char *hfname);
 
+static ssize_t rend_service_parse_intro_for_v0_or_v1(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out);
+static ssize_t rend_service_parse_intro_for_v2(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out);
+static ssize_t rend_service_parse_intro_for_v3(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out);
+
 /** Represents the mapping from a virtual port of a rendezvous service to
  * a real port on some IP.
  */
@@ -1061,38 +1083,50 @@ rend_service_note_removing_intro_point(rend_service_t *service,
 /** Respond to an INTRODUCE2 cell by launching a circuit to the chosen
  * rendezvous point.
  */
- /* XXXX024 this function sure could use some organizing. -RD */
 int
 rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
                        size_t request_len)
 {
-  int status = 0;
-  char *ptr, *r_cookie;
-  extend_info_t *extend_info = NULL;
+  /* Global status stuff */
+  int status = 0, result;
+  const or_options_t *options = get_options();
+  char *err_msg = NULL;
+  const char *stage_descr = NULL;
+  int reason = END_CIRC_REASON_TORPROTOCOL;
+  /* Service/circuit/key stuff we can learn before parsing */
+  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
+  rend_service_t *service = NULL;
+  rend_intro_point_t *intro_point = NULL;
+  crypto_pk_t *intro_key = NULL;
+  /* Parsed cell */
+  rend_intro_cell_t *parsed_req = NULL;
+  /* Rendezvous point */
+  extend_info_t *rp = NULL;
+  /*
+   * We need to look up and construct the extend_info_t for v0 and v1,
+   * but all the info is in the cell and it's constructed by the parser
+   * for v2 and v3, so freeing it would be a double-free.  Use this to
+   * keep track of whether we should free it.
+   */
+  uint8_t need_rp_free = 0;
+  /* XXX not handled yet */
   char buf[RELAY_PAYLOAD_SIZE];
   char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
-  rend_service_t *service;
-  rend_intro_point_t *intro_point;
-  int r, i, v3_shift = 0;
-  size_t len, keylen;
+  int i;
   crypto_dh_t *dh = NULL;
   origin_circuit_t *launched = NULL;
   crypt_path_t *cpath = NULL;
-  char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
   char hexcookie[9];
   int circ_needs_uptime;
-  int reason = END_CIRC_REASON_TORPROTOCOL;
-  crypto_pk_t *intro_key;
   char intro_key_digest[DIGEST_LEN];
-  int auth_type;
   size_t auth_len = 0;
   char auth_data[REND_DESC_COOKIE_LEN];
   time_t now = time(NULL);
   char diffie_hellman_hash[DIGEST_LEN];
   time_t elapsed;
   int replay;
-  const or_options_t *options = get_options();
 
+  /* Do some initial validation and logging before we parse the cell */
   if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) {
     log_warn(LD_PROTOCOL,
              "Got an INTRODUCE2 over a non-introduction circuit %d.",
@@ -1105,57 +1139,60 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
 #endif
   tor_assert(circuit->rend_data);
 
+  /* We'll use this in a bazillion log messages */
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
                 circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
-  log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
-           escaped(serviceid), circuit->_base.n_circ_id);
-
-  /* min key length plus digest length plus nickname length */
-  if (request_len < DIGEST_LEN+REND_COOKIE_LEN+(MAX_NICKNAME_LEN+1)+
-      DH_KEY_LEN+42) {
-    log_warn(LD_PROTOCOL, "Got a truncated INTRODUCE2 cell on circ %d.",
-             circuit->_base.n_circ_id);
-    goto err;
-  }
 
   /* look up service depending on circuit. */
-  service = rend_service_get_by_pk_digest(
-                circuit->rend_data->rend_pk_digest);
+  service =
+    rend_service_get_by_pk_digest(circuit->rend_data->rend_pk_digest);
   if (!service) {
-    log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro "
+    log_warn(LD_BUG,
+             "Internal error: Got an INTRODUCE2 cell on an intro "
              "circ for an unrecognized service %s.",
              escaped(serviceid));
     goto err;
   }
 
-  /* use intro key instead of service key. */
-  intro_key = circuit->intro_key;
-
-  /* first DIGEST_LEN bytes of request is intro or service pk digest */
-  crypto_pk_get_digest(intro_key, intro_key_digest);
-  if (tor_memneq(intro_key_digest, request, DIGEST_LEN)) {
-    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                  (char*)request, REND_SERVICE_ID_LEN);
-    log_warn(LD_REND, "Got an INTRODUCE2 cell for the wrong service (%s).",
-             escaped(serviceid));
-    goto err;
-  }
-
-  keylen = crypto_pk_keysize(intro_key);
-  if (request_len < keylen+DIGEST_LEN) {
-    log_warn(LD_PROTOCOL,
-             "PK-encrypted portion of INTRODUCE2 cell was truncated.");
-    goto err;
-  }
-
   intro_point = find_intro_point(circuit);
   if (intro_point == NULL) {
-    log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro circ "
-             "(for service %s) with no corresponding rend_intro_point_t.",
+    log_warn(LD_BUG,
+             "Internal error: Got an INTRODUCE2 cell on an "
+             "intro circ (for service %s) with no corresponding "
+             "rend_intro_point_t.",
              escaped(serviceid));
     goto err;
   }
 
+  log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
+           escaped(serviceid), circuit->_base.n_circ_id);
+
+  /* use intro key instead of service key. */
+  intro_key = circuit->intro_key;
+
+  tor_free(err_msg);
+  stage_descr = NULL;
+
+  stage_descr = "early parsing";
+  /* Early parsing pass (get pk, ciphertext); type 2 is INTRODUCE2 */
+  parsed_req =
+    rend_service_begin_parse_intro(request, request_len, 2, &err_msg);
+  if (!parsed_req) goto err;
+  else if (err_msg) {
+    log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
+    tor_free(err_msg);
+  }
+  
+  stage_descr = "early validation";
+  /* Early validation of pk/ciphertext part */
+  result = rend_service_validate_intro_early(parsed_req, &err_msg);
+  if (result < 0) goto err;
+  else if (err_msg) {
+    log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
+    tor_free(err_msg);
+  }
+
+  /* make sure service replay caches are present */
   if (!service->accepted_intro_dh_parts) {
     service->accepted_intro_dh_parts =
       replaycache_new(REND_REPLAY_TIME_INTERVAL,
@@ -1166,153 +1203,72 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
     intro_point->accepted_intro_rsa_parts = replaycache_new(0, 0);
   }
 
-  /* Check for replay of PK-encrypted portion. */
+  /* check for replay of PK-encrypted portion. */
   replay = replaycache_add_test_and_elapsed(
-      intro_point->accepted_intro_rsa_parts,
-      ((char*)request)+DIGEST_LEN, keylen,
-      &elapsed);
+    intro_point->accepted_intro_rsa_parts,
+    parsed_req->ciphertext, parsed_req->ciphertext_len,
+    &elapsed);
 
   if (replay) {
-    log_warn(LD_REND, "Possible replay detected! We received an "
-             "INTRODUCE2 cell with same PK-encrypted part %d seconds ago. "
-             "Dropping cell.", (int)elapsed);
-    goto err;
-  }
+    log_warn(LD_REND,
+             "Possible replay detected! We received an "
+             "INTRODUCE2 cell with same PK-encrypted part %d "
+             "seconds ago.  Dropping cell.",
+             (int)elapsed);
+     goto err;
+  }
+  
+  stage_descr = "decryption";
+  /* Now try to decrypt it */
+  result = rend_service_decrypt_intro(parsed_req, intro_key, &err_msg);
+  if (result < 0) goto err;
+  else if (err_msg) {
+    log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
+    tor_free(err_msg);
+  }
+
+  stage_descr = "late parsing";
+  /* Parse the plaintext */
+  result = rend_service_parse_intro_plaintext(parsed_req, &err_msg);
+  if (result < 0) goto err;
+  else if (err_msg) {
+    log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
+    tor_free(err_msg);
+  }
+
+  stage_descr = "late validation";
+  /* Validate the parsed plaintext parts */
+  result = rend_service_validate_intro_late(parsed_req, &err_msg);
+  if (result < 0) goto err;
+  else if (err_msg) {
+    log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
+    tor_free(err_msg);
+  }
+  stage_descr = NULL;
 
   /* Increment INTRODUCE2 counter */
   ++(intro_point->accepted_introduce2_count);
 
-  /* Next N bytes is encrypted with service key */
-  note_crypto_pk_op(REND_SERVER);
-  r = crypto_pk_private_hybrid_decrypt(
-       intro_key,buf,sizeof(buf),
-       (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
-       PK_PKCS1_OAEP_PADDING,1);
-  if (r<0) {
-    log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell.");
-    goto err;
-  }
-  len = r;
-  if (*buf == 3) {
-    /* Version 3 INTRODUCE2 cell. */
-    v3_shift = 1;
-    auth_type = buf[1];
-    switch (auth_type) {
-      case REND_BASIC_AUTH:
-        /* fall through */
-      case REND_STEALTH_AUTH:
-        auth_len = ntohs(get_uint16(buf+2));
-        if (auth_len != REND_DESC_COOKIE_LEN) {
-          log_info(LD_REND, "Wrong auth data size %d, should be %d.",
-                   (int)auth_len, REND_DESC_COOKIE_LEN);
-          goto err;
-        }
-        memcpy(auth_data, buf+4, sizeof(auth_data));
-        v3_shift += 2+REND_DESC_COOKIE_LEN;
-        break;
-      case REND_NO_AUTH:
-        break;
-      default:
-        log_info(LD_REND, "Unknown authorization type '%d'", auth_type);
-    }
-
-    /* Skip the timestamp field.  We no longer use it. */
-    v3_shift += 4;
-  }
-  if (*buf == 2 || *buf == 3) {
-    /* Version 2 INTRODUCE2 cell. */
-    int klen;
-    extend_info = tor_malloc_zero(sizeof(extend_info_t));
-    tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+v3_shift+1));
-    extend_info->port = ntohs(get_uint16(buf+v3_shift+5));
-    memcpy(extend_info->identity_digest, buf+v3_shift+7,
-           DIGEST_LEN);
-    extend_info->nickname[0] = '$';
-    base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
-                  extend_info->identity_digest, DIGEST_LEN);
-
-    klen = ntohs(get_uint16(buf+v3_shift+7+DIGEST_LEN));
-    if ((int)len != v3_shift+7+DIGEST_LEN+2+klen+20+128) {
-      log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.",
-               (int)len, *buf);
-      reason = END_CIRC_REASON_TORPROTOCOL;
-      goto err;
-    }
-    extend_info->onion_key =
-        crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen);
-    if (!extend_info->onion_key) {
-      log_warn(LD_PROTOCOL, "Error decoding onion key in version %d "
-                            "INTRODUCE2 cell.", *buf);
-      reason = END_CIRC_REASON_TORPROTOCOL;
-      goto err;
-    }
-    ptr = buf+v3_shift+7+DIGEST_LEN+2+klen;
-    len -= v3_shift+7+DIGEST_LEN+2+klen;
-  } else {
-    char *rp_nickname;
-    size_t nickname_field_len;
-    const node_t *node;
-    int version;
-    if (*buf == 1) {
-      rp_nickname = buf+1;
-      nickname_field_len = MAX_HEX_NICKNAME_LEN+1;
-      version = 1;
-    } else {
-      nickname_field_len = MAX_NICKNAME_LEN+1;
-      rp_nickname = buf;
-      version = 0;
-    }
-    ptr=memchr(rp_nickname,0,nickname_field_len);
-    if (!ptr || ptr == rp_nickname) {
-      log_warn(LD_PROTOCOL,
-               "Couldn't find a nul-padded nickname in INTRODUCE2 cell.");
-      goto err;
-    }
-    if ((version == 0 && !is_legal_nickname(rp_nickname)) ||
-        (version == 1 && !is_legal_nickname_or_hexdigest(rp_nickname))) {
-      log_warn(LD_PROTOCOL, "Bad nickname in INTRODUCE2 cell.");
-      goto err;
-    }
-    /* Okay, now we know that a nickname is at the start of the buffer. */
-    ptr = rp_nickname+nickname_field_len;
-    len -= nickname_field_len;
-    len -= rp_nickname - buf; /* also remove header space used by version, if
-                               * any */
-    node = node_get_by_nickname(rp_nickname, 0);
-    if (!node) {
-      log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.",
-               escaped_safe_str_client(rp_nickname));
-      /* XXXX Add a no-such-router reason? */
-      reason = END_CIRC_REASON_TORPROTOCOL;
-      goto err;
-    }
-
-    extend_info = extend_info_from_node(node, 0);
-  }
-
-  if (len != REND_COOKIE_LEN+DH_KEY_LEN) {
-    log_warn(LD_PROTOCOL, "Bad length %u for INTRODUCE2 cell.", (int)len);
-    reason = END_CIRC_REASON_TORPROTOCOL;
-    goto err;
-  }
+  /* Find the rendezvous point */
+  rp = find_rp_for_intro(parsed_req, &need_rp_free, &err_msg);
+  if (!rp) goto err;
 
   /* Check if we'd refuse to talk to this router */
   if (options->StrictNodes &&
-      routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) {
+      routerset_contains_extendinfo(options->ExcludeNodes, rp)) {
     log_warn(LD_REND, "Client asked to rendezvous at a relay that we "
              "exclude, and StrictNodes is set. Refusing service.");
     reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */
     goto err;
   }
 
-  r_cookie = ptr;
-  base16_encode(hexcookie,9,r_cookie,4);
+  base16_encode(hexcookie, 9, (const char *)(parsed_req->rc), 4);
 
   /* Check whether there is a past request with the same Diffie-Hellman,
    * part 1. */
   replay = replaycache_add_test_and_elapsed(
       service->accepted_intro_dh_parts,
-      ptr+REND_COOKIE_LEN, DH_KEY_LEN,
+      parsed_req->dh, DH_KEY_LEN,
       &elapsed);
 
   if (replay) {
@@ -1358,7 +1314,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
     reason = END_CIRC_REASON_INTERNAL;
     goto err;
   }
-  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN,
+  if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh,
+                               (char *)(parsed_req->dh),
                                DH_KEY_LEN, keys,
                                DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
     log_warn(LD_BUG, "Internal error: couldn't complete DH handshake");
@@ -1377,7 +1334,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
     int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
     if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
     launched = circuit_launch_by_extend_info(
-                        CIRCUIT_PURPOSE_S_CONNECT_REND, extend_info, flags);
+                        CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
 
     if (launched)
       break;
@@ -1385,7 +1342,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   if (!launched) { /* give up */
     log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous "
              "point %s for service %s.",
-             safe_str_client(extend_info_describe(extend_info)),
+             safe_str_client(extend_info_describe(rp)),
              serviceid);
     reason = END_CIRC_REASON_CONNECTFAILED;
     goto err;
@@ -1393,7 +1350,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   log_info(LD_REND,
            "Accepted intro; launching circuit to %s "
            "(cookie %s) for service %s.",
-           safe_str_client(extend_info_describe(extend_info)),
+           safe_str_client(extend_info_describe(rp)),
            hexcookie, serviceid);
   tor_assert(launched->build_state);
   /* Fill in the circuit's state. */
@@ -1401,7 +1358,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   memcpy(launched->rend_data->rend_pk_digest,
          circuit->rend_data->rend_pk_digest,
          DIGEST_LEN);
-  memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
+  memcpy(launched->rend_data->rend_cookie, parsed_req->rc, REND_COOKIE_LEN);
   strlcpy(launched->rend_data->onion_address, service->service_id,
           sizeof(launched->rend_data->onion_address));
 
@@ -1419,16 +1376,26 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
     goto err;
   memcpy(cpath->handshake_digest, keys, DIGEST_LEN);
-  if (extend_info) extend_info_free(extend_info);
 
   goto done;
 
  err:
   status = -1;
+  if (!err_msg) {
+    if (stage_descr) {
+      tor_asprintf(&err_msg,
+                   "unknown %s error for INTRODUCE2", stage_descr);
+     } else {
+      err_msg = tor_strdup("unknown error for INTRODUCE2");
+     }
+  }
   if (dh) crypto_dh_free(dh);
-  if (launched)
+  if (launched) {
     circuit_mark_for_close(TO_CIRCUIT(launched), reason);
-  if (extend_info) extend_info_free(extend_info);
+  }
+  log_warn(LD_REND, "%s on circ %d", err_msg, circuit->_base.n_circ_id);
+  tor_free(err_msg);
+
  done:
   memset(keys, 0, sizeof(keys));
   memset(buf, 0, sizeof(buf));
@@ -1438,6 +1405,842 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
   memset(auth_data, 0, sizeof(auth_data));
   memset(diffie_hellman_hash, 0, sizeof(diffie_hellman_hash));
 
+  /* Free the parsed cell */
+  if (parsed_req) {
+    rend_service_free_intro(parsed_req);
+    parsed_req = NULL;
+  }
+
+  /* Free rp if we must */
+  if (need_rp_free) extend_info_free(rp);
+
+  return status;
+}
+
+/** Given a parsed and decrypted INTRODUCE2, find the rendezvous point or
+ * return NULL and an error string if we can't.
+ */
+
+static extend_info_t *
+find_rp_for_intro(const rend_intro_cell_t *intro,
+                  uint8_t *need_free_out, char **err_msg_out)
+{
+  extend_info_t *rp = NULL;
+  char *err_msg = NULL;
+  const char *rp_nickname = NULL;
+  const node_t *node = NULL;
+  uint8_t need_free = 0;
+
+  if (!intro || !need_free_out) {
+    if (err_msg_out)
+      err_msg = tor_strdup("Bad parameters to find_rp_for_intro()");
+
+    goto err;
+  }
+
+  if (intro->version == 0 || intro->version == 1) {
+    if (intro->version == 1) rp_nickname = (const char *)(intro->u.v1.rp);
+    else rp_nickname = (const char *)(intro->u.v0.rp);
+
+    node = node_get_by_nickname(rp_nickname, 0);
+    if (!node) {
+      if (err_msg_out) {
+        tor_asprintf(&err_msg,
+                     "Couldn't find router %s named in INTRODUCE2 cell",
+                     escaped_safe_str_client(rp_nickname));
+      }
+
+      goto err;
+    }
+
+    rp = extend_info_from_node(node, 0);
+    if (!rp) {
+      if (err_msg_out) {
+        tor_asprintf(&err_msg,
+                     "Could build extend_info_t for router %s named "
+                     "in INTRODUCE2 cell",
+                     escaped_safe_str_client(rp_nickname));
+      }
+
+      goto err;
+    }
+    else need_free = 1;
+  } else if (intro->version == 2) rp = intro->u.v2.extend_info;
+  else if (intro->version == 3) rp = intro->u.v3.extend_info;
+  else {
+    if (err_msg_out) {
+      tor_asprintf(&err_msg,
+                   "Unknown version %d in INTRODUCE2 cell",
+                   (int)(intro->version));
+    }
+
+    goto err;
+  }
+
+  goto done;
+
+ err:
+  if (err_msg_out) *err_msg_out = err_msg;
+  else tor_free(err_msg);
+
+ done:
+  if (rp && need_free_out) *need_free_out = need_free;
+
+  return rp;
+}
+
+/** Remove unnecessary parts from a rend_intro_cell_t - the ciphertext if
+ * already decrypted, the plaintext too if already parsed
+ */
+
+void
+rend_service_compact_intro(rend_intro_cell_t *request)
+{
+  if (!request) return;
+
+  if ((request->plaintext && request->plaintext_len > 0) ||
+       request->parsed) {
+    tor_free(request->ciphertext);
+    request->ciphertext_len = 0;
+  }
+
+  if (request->parsed) {
+    tor_free(request->plaintext);
+    request->plaintext_len = 0;
+  }
+}
+
+/** Free a parsed INTRODUCE1 or INTRODUCE2 cell that was allocated by
+ * rend_service_parse_intro().
+ */
+void
+rend_service_free_intro(rend_intro_cell_t *request)
+{
+  if (!request) {
+    log_info(LD_BUG, "rend_service_free_intro() called with NULL request!");
+    return;
+  }
+
+  /* Free ciphertext */
+  tor_free(request->ciphertext);
+  request->ciphertext_len = 0;
+
+  /* Have plaintext? */
+  if (request->plaintext) {
+    /* Zero it out just to be safe */
+    memset(request->plaintext, 0, request->plaintext_len);
+    tor_free(request->plaintext);
+    request->plaintext_len = 0;
+  }
+
+  /* Have parsed plaintext? */
+  if (request->parsed) {
+    switch (request->version) {
+      case 0:
+      case 1:
+        /*
+         * Nothing more to do; these formats have no further pointers
+         * in them.
+         */
+        break;
+      case 2:
+        extend_info_free(request->u.v2.extend_info);
+        request->u.v2.extend_info = NULL;
+        break;
+      case 3:
+        if (request->u.v3.auth_data) {
+          memset(request->u.v3.auth_data, 0, request->u.v3.auth_len);
+          tor_free(request->u.v3.auth_data);
+        }
+
+        extend_info_free(request->u.v3.extend_info);
+        request->u.v3.extend_info = NULL;
+        break;
+      default:
+        log_info(LD_BUG,
+                 "rend_service_free_intro() saw unknown protocol "
+                 "version %d.",
+                 request->version);
+    }
+  }
+
+  /* Zero it out to make sure sensitive stuff doesn't hang around in memory */
+  memset(request, 0, sizeof(*request));
+
+  tor_free(request);
+}
+
+/** Parse an INTRODUCE1 or INTRODUCE2 cell into a newly allocated
+ * rend_intro_cell_t structure.  Free it with rend_service_free_intro()
+ * when finished.  The type parameter should be 1 or 2 to indicate whether
+ * this is INTRODUCE1 or INTRODUCE2.  This parses only the non-encrypted
+ * parts; after this, call rend_service_decrypt_intro() with a key, then
+ * rend_service_parse_intro_plaintext() to finish parsing.  The optional
+ * err_msg_out parameter is set to a string suitable for log output
+ * if parsing fails.  This function does some validation, but only
+ * that which depends solely on the contents of the cell and the
+ * key; it can be unit-tested.  Further validation is done in
+ * rend_service_validate_intro().
+ */
+
+rend_intro_cell_t *
+rend_service_begin_parse_intro(const uint8_t *request,
+                               size_t request_len,
+                               uint8_t type,
+                               char **err_msg_out)
+{
+  rend_intro_cell_t *rv = NULL;
+  char *err_msg = NULL;
+
+  if (!request || request_len <= 0) goto err;
+  if (!(type == 1 || type == 2)) goto err;
+
+  /* First, check that the cell is long enough to be a sensible INTRODUCE */
+
+  /* min key length plus digest length plus nickname length */
+  if (request_len <
+        (DIGEST_LEN + REND_COOKIE_LEN + (MAX_NICKNAME_LEN + 1) +
+         DH_KEY_LEN + 42)) {
+    if (err_msg_out) {
+      tor_asprintf(&err_msg,
+                   "got a truncated INTRODUCE%d cell",
+                   (int)type);
+    }
+    goto err;
+  }
+
+  /* Allocate a new parsed cell structure */
+  rv = tor_malloc_zero(sizeof(*rv));
+
+  /* Set the type */
+  rv->type = type;
+
+  /* Copy in the ID */
+  memcpy(rv->pk, request, DIGEST_LEN);
+
+  /* Copy in the ciphertext */
+  rv->ciphertext = tor_malloc(request_len - DIGEST_LEN);
+  memcpy(rv->ciphertext, request + DIGEST_LEN, request_len - DIGEST_LEN);
+  rv->ciphertext_len = request_len - DIGEST_LEN;
+
+  goto done;
+
+ err:
+  if (rv) rend_service_free_intro(rv);
+  rv = NULL;
+  if (err_msg_out && !err_msg) {
+    tor_asprintf(&err_msg,
+                 "unknown INTRODUCE%d error",
+                 (int)type);
+  }
+
+ done:
+  if (err_msg_out) *err_msg_out = err_msg;
+  else tor_free(err_msg);
+
+  return rv;
+}
+
+/** Parse the version-specific parts of a v0 or v1 INTRODUCE1 or INTRODUCE2
+ * cell
+ */
+
+static ssize_t
+rend_service_parse_intro_for_v0_or_v1(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out)
+{
+  const char *rp_nickname, *endptr;
+  size_t nickname_field_len, ver_specific_len;
+
+  if (intro->version == 1) {
+    ver_specific_len = MAX_HEX_NICKNAME_LEN + 2;
+    rp_nickname = ((const char *)buf) + 1;
+    nickname_field_len = MAX_HEX_NICKNAME_LEN + 1;
+  } else if (intro->version == 0) {
+    ver_specific_len = MAX_NICKNAME_LEN + 1;
+    rp_nickname = (const char *)buf;
+    nickname_field_len = MAX_NICKNAME_LEN + 1;
+  } else {
+    if (err_msg_out)
+      tor_asprintf(err_msg_out,
+                   "rend_service_parse_intro_for_v0_or_v1() called with "
+                   "bad version %d on INTRODUCE%d cell (this is a bug)",
+                   intro->version,
+                   (int)(intro->type));
+    goto err;
+  }
+
+  if (plaintext_len < ver_specific_len) {
+    if (err_msg_out)
+      tor_asprintf(err_msg_out,
+                   "short plaintext of encrypted part in v1 INTRODUCE%d "
+                   "cell (%lu bytes, needed %lu)",
+                   (int)(intro->type),
+                   (unsigned long)plaintext_len,
+                   (unsigned long)ver_specific_len);
+    goto err;
+  }
+
+  endptr = memchr(rp_nickname, 0, nickname_field_len);
+  if (!endptr || endptr == rp_nickname) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "couldn't find a nul-padded nickname in "
+                   "INTRODUCE%d cell",
+                   (int)(intro->type));
+    }
+    goto err;
+  }
+
+  if ((intro->version == 0 &&
+       !is_legal_nickname(rp_nickname)) ||
+      (intro->version == 1 &&
+       !is_legal_nickname_or_hexdigest(rp_nickname))) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "bad nickname in INTRODUCE%d cell",
+                   (int)(intro->type));
+    }
+    goto err;
+  }
+
+  if (intro->version == 1) {
+    memcpy(intro->u.v1.rp, rp_nickname, endptr - rp_nickname + 1);
+  } else {
+    memcpy(intro->u.v0.rp, rp_nickname, endptr - rp_nickname + 1);
+  }
+
+  return ver_specific_len;
+
+ err:
+  return -1;
+}
+
+/** Parse the version-specific parts of a v2 INTRODUCE1 or INTRODUCE2 cell
+ */
+
+static ssize_t
+rend_service_parse_intro_for_v2(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out)
+{
+  unsigned int klen;
+  extend_info_t *extend_info = NULL;
+  ssize_t ver_specific_len;
+
+  /*
+   * We accept version 3 too so that the v3 parser can call this with
+   * and adjusted buffer for the latter part of a v3 cell, which is
+   * identical to a v2 cell.
+   */
+  if (!(intro->version == 2 ||
+        intro->version == 3)) {
+    if (err_msg_out)
+      tor_asprintf(err_msg_out,
+                   "rend_service_parse_intro_for_v2() called with "
+                   "bad version %d on INTRODUCE%d cell (this is a bug)",
+                   intro->version,
+                   (int)(intro->type));
+    goto err;
+  }
+
+  /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
+  if (plaintext_len < 7 + DIGEST_LEN + 2) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "truncated plaintext of encrypted parted of "
+                   "version %d INTRODUCE%d cell",
+                   intro->version,
+                   (int)(intro->type));
+    }
+
+    goto err;
+  }
+
+  extend_info = tor_malloc_zero(sizeof(extend_info_t));
+  tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf + 1));
+  extend_info->port = ntohs(get_uint16(buf + 5));
+  memcpy(extend_info->identity_digest, buf + 7, DIGEST_LEN);
+  extend_info->nickname[0] = '$';
+  base16_encode(extend_info->nickname + 1, sizeof(extend_info->nickname) - 1,
+                extend_info->identity_digest, DIGEST_LEN);
+  klen = ntohs(get_uint16(buf + 7 + DIGEST_LEN));
+
+  /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
+  if (plaintext_len < 7 + DIGEST_LEN + 2 + klen) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "truncated plaintext of encrypted parted of "
+                   "version %d INTRODUCE%d cell",
+                   intro->version,
+                   (int)(intro->type));
+    }
+
+    goto err;
+  }
+
+  extend_info->onion_key =
+    crypto_pk_asn1_decode((const char *)(buf + 7 + DIGEST_LEN + 2), klen);
+  if (!extend_info->onion_key) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "error decoding onion key in version %d "
+                   "INTRODUCE%d cell",
+                   intro->version,
+                   (intro->type));
+    }
+
+    goto err;
+  }
+
+  ver_specific_len = 7+DIGEST_LEN+2+klen;
+
+  if (intro->version == 2) intro->u.v2.extend_info = extend_info;
+  else intro->u.v3.extend_info = extend_info;
+
+  return ver_specific_len;
+
+ err:
+  extend_info_free(extend_info);
+
+  return -1;
+}
+
+/** Parse the version-specific parts of a v3 INTRODUCE1 or INTRODUCE2 cell
+ */
+
+static ssize_t
+rend_service_parse_intro_for_v3(
+    rend_intro_cell_t *intro,
+    const uint8_t *buf,
+    size_t plaintext_len,
+    char **err_msg_out)
+{
+  ssize_t adjust, v2_ver_specific_len, ts_offset;
+
+  /* This should only be called on v3 cells */
+  if (intro->version != 3) {
+    if (err_msg_out)
+      tor_asprintf(err_msg_out,
+                   "rend_service_parse_intro_for_v3() called with "
+                   "bad version %d on INTRODUCE%d cell (this is a bug)",
+                   intro->version,
+                   (int)(intro->type));
+    goto err;
+  }
+
+  /*
+   * Check that we have at least enough to get auth_len:
+   *
+   * 1 octet for version, 1 for auth_type, 2 for auth_len
+   */
+  if (plaintext_len < 4) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "truncated plaintext of encrypted parted of "
+                   "version %d INTRODUCE%d cell",
+                   intro->version,
+                   (int)(intro->type));
+    }
+
+    goto err;
+  }
+
+  /*
+   * The rend_client_send_introduction() function over in rendclient.c is
+   * broken (i.e., fails to match the spec) in such a way that we can't
+   * change it without breaking the protocol.  Specifically, it doesn't
+   * emit auth_len when auth-type is REND_NO_AUTH, so everything is off
+   * by two bytes after that.  Calculate ts_offset and do everything from
+   * the timestamp on relative to that to handle this dain bramage.
+   */
+
+  intro->u.v3.auth_type = buf[1];
+  if (intro->u.v3.auth_type != REND_NO_AUTH) {
+    intro->u.v3.auth_len = ntohs(get_uint16(buf + 2));
+    ts_offset = 4 + intro->u.v3.auth_len;
+  }
+  else {
+    intro->u.v3.auth_len = 0;
+    ts_offset = 2;
+  }
+
+  /* Check that auth len makes sense for this auth type */
+  if (intro->u.v3.auth_type == REND_BASIC_AUTH ||
+      intro->u.v3.auth_type == REND_STEALTH_AUTH) {
+      if (intro->u.v3.auth_len != REND_DESC_COOKIE_LEN) {
+        if (err_msg_out) {
+          tor_asprintf(err_msg_out,
+                       "wrong auth data size %d for INTRODUCE%d cell, "
+                       "should be %d",
+                       (int)(intro->u.v3.auth_len),
+                       (int)(intro->type),
+                       REND_DESC_COOKIE_LEN);
+        }
+
+        goto err;
+      }
+  }
+
+  /* Check that we actually have everything up to the timestamp */
+  if (plaintext_len < (size_t)(ts_offset)) {
+    if (err_msg_out) {
+      tor_asprintf(err_msg_out,
+                   "truncated plaintext of encrypted parted of "
+                   "version %d INTRODUCE%d cell",
+                   intro->version,
+                   (int)(intro->type));
+    }
+
+    goto err;
+  }
+
+  if (intro->u.v3.auth_type != REND_NO_AUTH &&
+      intro->u.v3.auth_len > 0) {
+    /* Okay, we can go ahead and copy auth_data */
+    intro->u.v3.auth_data = tor_malloc(intro->u.v3.auth_len);
+    /*
+     * We know we had an auth_len field in this case, so 4 is
+     * always right.
+     */
+    memcpy(intro->u.v3.auth_data, buf + 4, intro->u.v3.auth_len);
+  }
+
+  /*
+   * Apparently we don't use the timestamp any more, but might as well copy
+   * over just in case we ever care about it.
+   */
+  intro->u.v3.timestamp = ntohl(get_uint32(buf + ts_offset));
+
+  /*
+   * From here on, the format is as in v2, so we call the v2 parser with
+   * adjusted buffer and length.  We are 4 + ts_offset octets in, but the
+   * v2 parser expects to skip over a version byte at the start, so we
+   * adjust by 3 + ts_offset.
+   */
+  adjust = 3 + ts_offset;
+
+  v2_ver_specific_len =
+    rend_service_parse_intro_for_v2(intro,
+                                    buf + adjust, plaintext_len - adjust,
+                                    err_msg_out);
+
+  /* Success in v2 parser */
+  if (v2_ver_specific_len >= 0) return v2_ver_specific_len + adjust;
+  /* Failure in v2 parser; it will have provided an err_msg */
+  else return v2_ver_specific_len;
+
+ err:
+  return -1;
+}
+
+/** Table of parser functions for version-specific parts of an INTRODUCE2
+ * cell.
+ */
+
+static ssize_t
+  (*intro_version_handlers[])(
+    rend_intro_cell_t *,
+    const uint8_t *,
+    size_t,
+    char **) =
+{ rend_service_parse_intro_for_v0_or_v1,
+  rend_service_parse_intro_for_v0_or_v1,
+  rend_service_parse_intro_for_v2,
+  rend_service_parse_intro_for_v3 };
+
+/** Decrypt the encrypted part of an INTRODUCE1 or INTRODUCE2 cell,
+ * return 0 if successful, or < 0 and write an error message to
+ * *err_msg_out if provided.
+ */
+
+int
+rend_service_decrypt_intro(
+    rend_intro_cell_t *intro,
+    crypto_pk_t *key,
+    char **err_msg_out)
+{
+  char *err_msg = NULL;
+  uint8_t key_digest[DIGEST_LEN];
+  char service_id[REND_SERVICE_ID_LEN_BASE32+1];
+  ssize_t key_len;
+  uint8_t buf[RELAY_PAYLOAD_SIZE];
+  int result, status = 0;
+
+  if (!intro || !key) {
+    if (err_msg_out) {
+      err_msg =
+        tor_strdup("rend_service_decrypt_intro() called with bad "
+                   "parameters");
+    }
+
+    status = -2;
+    goto err;
+  }
+
+  /* Make sure we have ciphertext */
+  if (!(intro->ciphertext) || intro->ciphertext_len <= 0) {
+    if (err_msg_out) {
+      tor_asprintf(&err_msg,
+                   "rend_intro_cell_t was missing ciphertext for "
+                   "INTRODUCE%d cell",
+                   (int)(intro->type));
+    }
+    status = -3;
+    goto err;
+  }
+
+  /* Check that this cell actually matches this service key */
+
+  /* first DIGEST_LEN bytes of request is intro or service pk digest */
+  crypto_pk_get_digest(key, (char *)key_digest);
+  if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) {
+    if (err_msg_out) {
+      base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1,
+                    (char*)(intro->pk), REND_SERVICE_ID_LEN);
+      tor_asprintf(&err_msg,
+                   "got an INTRODUCE%d cell for the wrong service (%s)",
+                   (int)(intro->type),
+                   escaped(service_id));
+    }
+
+    status = -4;
+    goto err;
+  }
+
+  /* Make sure the encrypted part is long enough to decrypt */
+
+  key_len = crypto_pk_keysize(key);
+  if (intro->ciphertext_len < key_len) {
+    if (err_msg_out) {
+      tor_asprintf(&err_msg,
+                   "got an INTRODUCE%d cell with a truncated PK-encrypted "
+                   "part",
+                   (int)(intro->type));
+    }
+
+    status = -5;
+    goto err;
+  }
+
+  /* Decrypt the encrypted part */
+
+  note_crypto_pk_op(REND_SERVER);
+  result =
+    crypto_pk_private_hybrid_decrypt(
+       key, (char *)buf, sizeof(buf),
+       (const char *)(intro->ciphertext), intro->ciphertext_len,
+       PK_PKCS1_OAEP_PADDING, 1);
+  if (result < 0) {
+    if (err_msg_out) {
+      tor_asprintf(&err_msg,
+                   "couldn't decrypt INTRODUCE%d cell",
+                   (int)(intro->type));
+    }
+    status = -6;
+    goto err;
+  }
+  intro->plaintext_len = result;
+  intro->plaintext = tor_malloc(intro->plaintext_len);
+  memcpy(intro->plaintext, buf, intro->plaintext_len);
+
+  goto done;
+
+ err:
+  if (err_msg_out && !err_msg) {
+    tor_asprintf(&err_msg,
+                 "unknown INTRODUCE%d error decrypting encrypted part",
+                 (int)(intro->type));
+  }
+  if (status >= 0) status = -1;
+
+ done:
+  if (err_msg_out) *err_msg_out = err_msg;
+  else tor_free(err_msg);
+
+  /* clean up potentially sensitive material */
+  memset(buf, 0, sizeof(buf));
+  memset(key_digest, 0, sizeof(key_digest));
+  memset(service_id, 0, sizeof(service_id));
+
+  return status;
+}
+
+/** Parse the plaintext of the encrypted part of an INTRODUCE1 or
+ * INTRODUCE2 cell, return 0 if successful, or < 0 and write an error
+ * message to *err_msg_out if provided.
+ */
+
+int
+rend_service_parse_intro_plaintext(
+    rend_intro_cell_t *intro,
+    char **err_msg_out)
+{
+  char *err_msg = NULL;
+  ssize_t ver_specific_len, ver_invariant_len;
+  uint8_t version;
+  int status = 0;
+
+  if (!intro) {
+    if (err_msg_out) {
+      err_msg =
+        tor_strdup("rend_service_parse_intro_plaintext() called with NULL "
+                   "rend_intro_cell_t");
+    }
+
+    status = -2;
+    goto err;
+  }
+
+  /* Check that we have plaintext */
+  if (!(intro->plaintext) || intro->plaintext_len <= 0) {
+    if (err_msg_out) {
+      err_msg = tor_strdup("rend_intro_cell_t was missing plaintext");
+    }
+    status = -3;
+    goto err;
+  }
+
+  /* In all formats except v0, the first byte is a version number */
+  version = intro->plaintext[0];
+
+  /* v0 has no version byte (stupid...), so handle it as a fallback */
+  if (version > 3) version = 0;
+
+  /* Copy the version into the parsed cell structure */
+  intro->version = version;
+
+  /* Call the version-specific parser from the table */
+  ver_specific_len =
+    intro_version_handlers[version](intro,
+                                    intro->plaintext, intro->plaintext_len,
+                                    &err_msg);
+  if (ver_specific_len < 0) {
+    status = -4;
+    goto err;
+  }
+
+  /** The rendezvous cookie and Diffie-Hellman stuff are version-invariant
+   * and at the end of the plaintext of the encrypted part of the cell.
+   */
+
+  ver_invariant_len = intro->plaintext_len - ver_specific_len;
+  if (ver_invariant_len < REND_COOKIE_LEN + DH_KEY_LEN) {
+    tor_asprintf(&err_msg,
+        "decrypted plaintext of INTRODUCE%d cell was truncated (%ld bytes)",
+        (int)(intro->type),
+        (long)(intro->plaintext_len));
+    status = -5;
+    goto err;
+  } else if (ver_invariant_len > REND_COOKIE_LEN + DH_KEY_LEN) {
+    tor_asprintf(&err_msg,
+        "decrypted plaintext of INTRODUCE%d cell was too long (%ld bytes)",
+        (int)(intro->type),
+        (long)(intro->plaintext_len));
+    status = -6;
+  } else {
+    memcpy(intro->rc,
+           intro->plaintext + ver_specific_len,
+           REND_COOKIE_LEN);
+    memcpy(intro->dh,
+           intro->plaintext + ver_specific_len + REND_COOKIE_LEN,
+           DH_KEY_LEN);
+  }
+
+  /* Flag it as being fully parsed */
+  intro->parsed = 1;
+
+  goto done;
+
+ err:
+  if (err_msg_out && !err_msg) {
+    tor_asprintf(&err_msg,
+                 "unknown INTRODUCE%d error parsing encrypted part",
+                 (int)(intro->type));
+  }
+  if (status >= 0) status = -1;
+
+ done:
+  if (err_msg_out) *err_msg_out = err_msg;
+  else tor_free(err_msg);
+
+  return status;
+}
+
+/** Do validity checks on a parsed intro cell before decryption; some of
+ * these are not done in rend_service_begin_parse_intro() itself because
+ * they depend on a lot of other state and would make it hard to unit test.
+ * Returns >= 0 if successful or < 0 if the intro cell is invalid, and
+ * optionally writes out an error message for logging.  If an err_msg
+ * pointer is provided, it is the caller's responsibility to free any
+ * provided message.
+ */
+
+int
+rend_service_validate_intro_early(const rend_intro_cell_t *intro,
+                                  char **err_msg_out)
+{
+  int status = 0;
+
+  if (!intro) {
+    if (err_msg_out)
+      *err_msg_out =
+        tor_strdup("NULL intro cell passed to "
+                   "rend_service_validate_intro_early()");
+
+    status = -1;
+    goto err;
+  }
+
+  /* TODO */
+
+ err:
+  return status;
+}
+
+/** Do validity checks on a parsed intro cell after decryption; some of
+ * these are not done in rend_service_parse_intro_plaintext() itself because
+ * they depend on a lot of other state and would make it hard to unit test.
+ * Returns >= 0 if successful or < 0 if the intro cell is invalid, and
+ * optionally writes out an error message for logging.  If an err_msg
+ * pointer is provided, it is the caller's responsibility to free any
+ * provided message.
+ */
+
+int
+rend_service_validate_intro_late(const rend_intro_cell_t *intro,
+                                 char **err_msg_out)
+{
+  int status = 0;
+
+  if (!intro) {
+    if (err_msg_out)
+      *err_msg_out =
+        tor_strdup("NULL intro cell passed to "
+                   "rend_service_validate_intro_late()");
+
+    status = -1;
+    goto err;
+  }
+
+  if (intro->version == 3 && intro->parsed) {
+    if (!(intro->u.v3.auth_type == REND_NO_AUTH ||
+          intro->u.v3.auth_type == REND_BASIC_AUTH ||
+          intro->u.v3.auth_type == REND_STEALTH_AUTH)) {
+      /* This is an informative message, not an error, as in the old code */
+      if (err_msg_out)
+        tor_asprintf(err_msg_out,
+                     "unknown authorization type %d",
+                     intro->u.v3.auth_type);
+    }
+  }
+
+ err:
   return status;
 }
 

+ 73 - 0
src/or/rendservice.h

@@ -12,6 +12,64 @@
 #ifndef _TOR_RENDSERVICE_H
 #define _TOR_RENDSERVICE_H
 
+#include "or.h"
+
+typedef struct rend_intro_cell_s rend_intro_cell_t;
+
+#ifdef RENDSERVICE_PRIVATE
+
+/* This can be used for both INTRODUCE1 and INTRODUCE2 */
+
+struct rend_intro_cell_s {
+  /* Is this an INTRODUCE1 or INTRODUCE2? (set to 1 or 2) */
+  uint8_t type;
+  /* Public key digest */
+  uint8_t pk[DIGEST_LEN];
+  /* Optionally, store ciphertext here */
+  uint8_t *ciphertext;
+  ssize_t ciphertext_len;
+  /* Optionally, store plaintext */
+  uint8_t *plaintext;
+  ssize_t plaintext_len;
+  /* Have we parsed the plaintext? */
+  uint8_t parsed;
+  /* intro protocol version (0, 1, 2 or 3) */
+  uint8_t version;
+  /* Version-specific parts */
+  union {
+    struct {
+      /* Rendezvous point nickname */
+      uint8_t rp[20];
+    } v0;
+    struct {
+      /* Rendezvous point nickname or hex-encoded key digest */
+      uint8_t rp[42];
+    } v1;
+    struct {
+      /* The extend_info_t struct has everything v2 uses */
+      extend_info_t *extend_info;
+    } v2;
+    struct {
+      /* Auth type used */
+      uint8_t auth_type;
+      /* Length of auth data */
+      uint16_t auth_len;
+      /* Auth data */
+      uint8_t *auth_data;
+      /* timestamp */
+      uint32_t timestamp;
+      /* Rendezvous point's IP address/port, identity digest and onion key */
+      extend_info_t *extend_info;
+    } v3;
+  } u;
+  /* Rendezvous cookie */
+  uint8_t rc[REND_COOKIE_LEN];
+  /* Diffie-Hellman data */
+  uint8_t dh[DH_KEY_LEN];
+};
+
+#endif
+
 int num_rend_services(void);
 int rend_config_services(const or_options_t *options, int validate_only);
 int rend_service_load_all_keys(void);
@@ -27,6 +85,21 @@ int rend_service_intro_established(origin_circuit_t *circuit,
 void rend_service_rendezvous_has_opened(origin_circuit_t *circuit);
 int rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
                            size_t request_len);
+void rend_service_compact_intro(rend_intro_cell_t *request);
+int rend_service_decrypt_intro(rend_intro_cell_t *request,
+                               crypto_pk_t *key,
+                               char **err_msg_out);
+void rend_service_free_intro(rend_intro_cell_t *request);
+rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request,
+                                                   size_t request_len,
+                                                   uint8_t type,
+                                                   char **err_msg_out);
+int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro,
+                                       char **err_msg_out);
+int rend_service_validate_intro_early(const rend_intro_cell_t *intro,
+                                      char **err_msg_out);
+int rend_service_validate_intro_late(const rend_intro_cell_t *intro,
+                                     char **err_msg_out);
 void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);
 int rend_service_set_connection_addr_port(edge_connection_t *conn,
                                           origin_circuit_t *circ);