瀏覽代碼

Add patch 4 from Karsten for proposal 121, slightly modified. Karsten should definitely re-review the bits I changed.

svn:r16955
Nick Mathewson 17 年之前
父節點
當前提交
8bc1536a9e
共有 11 個文件被更改,包括 661 次插入236 次删除
  1. 11 0
      ChangeLog
  2. 28 0
      doc/tor.1.in
  3. 15 8
      src/or/circuitlist.c
  4. 19 9
      src/or/circuituse.c
  5. 18 8
      src/or/connection.c
  6. 31 15
      src/or/connection_edge.c
  7. 98 33
      src/or/directory.c
  8. 74 47
      src/or/or.h
  9. 113 61
      src/or/rendclient.c
  10. 36 5
      src/or/rendcommon.c
  11. 218 50
      src/or/rendservice.c

+ 11 - 0
ChangeLog

@@ -1,4 +1,13 @@
 Changes in version 0.2.1.6-alpha - 2008-09-xx
 Changes in version 0.2.1.6-alpha - 2008-09-xx
+  o Major features:
+    - Implement proposal 121: make it possible to build hidden services
+      that only certain clients are allowed to connect to.  This is
+      enforced at several points, so that unauthorized clients are
+      unable to send INTRODUCE cells to the service, or even (depending
+      on the type of authentication) to learn introduction points.  This
+      feature raises the bar for certain kinds of active attacks against
+      hidden services.
+
   o Major bugfixes:
   o Major bugfixes:
     - Fix a bug when parsing ports in tor_addr_port_parse() that caused
     - Fix a bug when parsing ports in tor_addr_port_parse() that caused
       Tor to fail to start if you had it configured to use a bridge
       Tor to fail to start if you had it configured to use a bridge
@@ -63,6 +72,8 @@ Changes in version 0.2.1.6-alpha - 2008-09-xx
       actual mistakes  we're making here.
       actual mistakes  we're making here.
     - Refactor unit testing logic so that dmalloc can be used sensibly with
     - Refactor unit testing logic so that dmalloc can be used sensibly with
       unit tests to check for memory leaks.
       unit tests to check for memory leaks.
+    - Move all hidden-service related fields from connection and circuit
+      structure to substructures: this way they won't eat so much memory.
 
 
 
 
 Changes in version 0.2.0.31 - 2008-09-03
 Changes in version 0.2.0.31 - 2008-09-03

+ 28 - 0
doc/tor.1.in

@@ -472,6 +472,15 @@ used when \fBFascistFirewall\fR is set. This option is deprecated; use
 ReachableAddresses instead. (Default: 80, 443)
 ReachableAddresses instead. (Default: 80, 443)
 .LP
 .LP
 .TP
 .TP
+\fBHidServAuth \fR\fIonion-address\fR \fIauth-cookie\fP \fIservice-name\fR 
+Client authorization for a hidden service. Valid onion addresses contain 16
+characters in a-z2-7 plus ".onion", and valid auth cookies contain 22
+characters in A-Za-z0-9+/. The service name is only used for internal
+purposes, e.g., for Tor controllers. This option may be used multiple times
+for different hidden services. If a hidden service uses authorization and
+this option is not set, the hidden service is not accessible.
+.LP
+.TP
 \fBReachableAddresses \fR\fIADDR\fP[\fB/\fP\fIMASK\fP][:\fIPORT\fP]...\fP
 \fBReachableAddresses \fR\fIADDR\fP[\fB/\fP\fIMASK\fP][:\fIPORT\fP]...\fP
 A comma-separated list of IP addresses and ports that your firewall allows you
 A comma-separated list of IP addresses and ports that your firewall allows you
 to connect to. The format is as
 to connect to. The format is as
@@ -1269,6 +1278,18 @@ A list of rendezvous service descriptor versions to publish for the hidden
 service. Possible version numbers are 0 and 2. (Default: 0, 2)
 service. Possible version numbers are 0 and 2. (Default: 0, 2)
 .LP
 .LP
 .TP
 .TP
+\fBHiddenServiceAuthorizeClient \fR\fIauth-type\fR \fR\fIclient-name\fR,\fIclient-name\fR,\fI...\fP
+If configured, the hidden service is accessible for authorized clients
+only. The auth-type can either be 'basic' for a general-purpose
+authorization protocol or 'stealth' for a less scalable protocol that also
+hides service activity from unauthorized clients. Only clients that are
+listed here are authorized to access the hidden service. Valid client names
+are 1 to 19 characters long and only use characters in A-Za-z0-9+-_
+(no spaces). If this option is set, the hidden service is not accessible
+for clients without authorization any more. Generated authorization data
+can be found in the hostname file.
+.LP
+.TP
 \fBRendPostPeriod \fR\fIN\fR \fBseconds\fR|\fBminutes\fR|\fBhours\fR|\fBdays\fR|\fBweeks\fP
 \fBRendPostPeriod \fR\fIN\fR \fBseconds\fR|\fBminutes\fR|\fBhours\fR|\fBdays\fR|\fBweeks\fP
 Every time the specified period elapses, Tor uploads any rendezvous
 Every time the specified period elapses, Tor uploads any rendezvous
 service descriptors to the directory servers.  This information is also
 service descriptors to the directory servers.  This information is also
@@ -1453,10 +1474,17 @@ Only used by authoritative directory servers.  Tracks measurements for router me
 .TP
 .TP
 .B \fIHiddenServiceDirectory\fP/hostname 
 .B \fIHiddenServiceDirectory\fP/hostname 
 The <base32-encoded-fingerprint>.onion domain name for this hidden service.
 The <base32-encoded-fingerprint>.onion domain name for this hidden service.
+If the hidden service is restricted to authorized clients only, this file
+also contains authorization data for all clients.
 .LP
 .LP
 .TP
 .TP
 .B \fIHiddenServiceDirectory\fP/private_key 
 .B \fIHiddenServiceDirectory\fP/private_key 
 The private key for this hidden service.
 The private key for this hidden service.
+.LP
+.TP
+.B \fIHiddenServiceDirectory\fP/client_keys 
+Authorization data for a hidden service that is only accessible by authorized
+clients.
 .SH SEE ALSO
 .SH SEE ALSO
 .BR privoxy (1),
 .BR privoxy (1),
 .BR tsocks (1),
 .BR tsocks (1),

+ 15 - 8
src/or/circuitlist.c

@@ -401,7 +401,8 @@ circuit_free(circuit_t *circ)
     circuit_free_cpath(ocirc->cpath);
     circuit_free_cpath(ocirc->cpath);
     if (ocirc->intro_key)
     if (ocirc->intro_key)
       crypto_free_pk_env(ocirc->intro_key);
       crypto_free_pk_env(ocirc->intro_key);
-
+    if (ocirc->rend_data)
+      rend_data_free(ocirc->rend_data);
   } else {
   } else {
     or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
     or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
     mem = ocirc;
     mem = ocirc;
@@ -720,7 +721,7 @@ circuit_unlink_all_from_or_conn(or_connection_t *conn, int reason)
 }
 }
 
 
 /** Return a circ such that:
 /** Return a circ such that:
- *  - circ-\>rend_query is equal to <b>rend_query</b>, and
+ *  - circ-\>rend_data-\>query is equal to <b>rend_query</b>, and
  *  - circ-\>purpose is equal to <b>purpose</b>.
  *  - circ-\>purpose is equal to <b>purpose</b>.
  *
  *
  * Return NULL if no such circuit exists.
  * Return NULL if no such circuit exists.
@@ -734,9 +735,13 @@ circuit_get_by_rend_query_and_purpose(const char *rend_query, uint8_t purpose)
 
 
   for (circ = global_circuitlist; circ; circ = circ->next) {
   for (circ = global_circuitlist; circ; circ = circ->next) {
     if (!circ->marked_for_close &&
     if (!circ->marked_for_close &&
-        circ->purpose == purpose &&
-        !rend_cmp_service_ids(rend_query, TO_ORIGIN_CIRCUIT(circ)->rend_query))
-      return TO_ORIGIN_CIRCUIT(circ);
+        circ->purpose == purpose) {
+      origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+      if (ocirc->rend_data &&
+          !rend_cmp_service_ids(rend_query,
+                                ocirc->rend_data->onion_address))
+        return ocirc;
+    }
   }
   }
   return NULL;
   return NULL;
 }
 }
@@ -764,7 +769,8 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
       continue;
       continue;
     if (!digest)
     if (!digest)
       return TO_ORIGIN_CIRCUIT(circ);
       return TO_ORIGIN_CIRCUIT(circ);
-    else if (!memcmp(TO_ORIGIN_CIRCUIT(circ)->rend_pk_digest,
+    else if (TO_ORIGIN_CIRCUIT(circ)->rend_data &&
+             !memcmp(TO_ORIGIN_CIRCUIT(circ)->rend_data->rend_pk_digest,
                      digest, DIGEST_LEN))
                      digest, DIGEST_LEN))
       return TO_ORIGIN_CIRCUIT(circ);
       return TO_ORIGIN_CIRCUIT(circ);
   }
   }
@@ -1020,13 +1026,14 @@ _circuit_mark_for_close(circuit_t *circ, int reason, int line,
     origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
     origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
     tor_assert(circ->state == CIRCUIT_STATE_OPEN);
     tor_assert(circ->state == CIRCUIT_STATE_OPEN);
     tor_assert(ocirc->build_state->chosen_exit);
     tor_assert(ocirc->build_state->chosen_exit);
+    tor_assert(ocirc->rend_data);
     /* treat this like getting a nack from it */
     /* treat this like getting a nack from it */
     log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). "
     log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). "
              "Removing from descriptor.",
              "Removing from descriptor.",
-             safe_str(ocirc->rend_query),
+             safe_str(ocirc->rend_data->onion_address),
              safe_str(build_state_get_exit_nickname(ocirc->build_state)));
              safe_str(build_state_get_exit_nickname(ocirc->build_state)));
     rend_client_remove_intro_point(ocirc->build_state->chosen_exit,
     rend_client_remove_intro_point(ocirc->build_state->chosen_exit,
-                                   ocirc->rend_query);
+                                   ocirc->rend_data);
   }
   }
   if (circ->n_conn)
   if (circ->n_conn)
     connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);
     connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason);

+ 19 - 9
src/or/circuituse.c

@@ -121,8 +121,12 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn,
       return 0;
       return 0;
     }
     }
   } else { /* not general */
   } else { /* not general */
-    if (rend_cmp_service_ids(conn->rend_query,
-                             TO_ORIGIN_CIRCUIT(circ)->rend_query)) {
+    origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+    if ((conn->rend_data && !ocirc->rend_data) ||
+        (!conn->rend_data && ocirc->rend_data) ||
+        (conn->rend_data && ocirc->rend_data &&
+         rend_cmp_service_ids(conn->rend_data->onion_address,
+                              ocirc->rend_data->onion_address))) {
       /* this circ is not for this conn */
       /* this circ is not for this conn */
       return 0;
       return 0;
     }
     }
@@ -300,7 +304,7 @@ circuit_expire_building(time_t now)
           /* c_rend_ready circs measure age since timestamp_dirty,
           /* c_rend_ready circs measure age since timestamp_dirty,
            * because that's set when they switch purposes
            * because that's set when they switch purposes
            */
            */
-          if (TO_ORIGIN_CIRCUIT(victim)->rend_query[0] ||
+          if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
               victim->timestamp_dirty > cutoff)
               victim->timestamp_dirty > cutoff)
             continue;
             continue;
           break;
           break;
@@ -1076,18 +1080,24 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
 
 
     if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
     if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
       /* need to pick an intro point */
       /* need to pick an intro point */
-      extend_info = rend_client_get_random_intro(conn->rend_query);
+      tor_assert(conn->rend_data);
+      extend_info = rend_client_get_random_intro(conn->rend_data);
       if (!extend_info) {
       if (!extend_info) {
         log_info(LD_REND,
         log_info(LD_REND,
                  "No intro points for '%s': refetching service descriptor.",
                  "No intro points for '%s': refetching service descriptor.",
-                 safe_str(conn->rend_query));
-        rend_client_refetch_renddesc(conn->rend_query);
-        rend_client_refetch_v2_renddesc(conn->rend_query);
+                 safe_str(conn->rend_data->onion_address));
+        /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
+         * arrives first. Exception: When using client authorization, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_data);
+        if (conn->rend_data->auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_data->onion_address);
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         return 0;
         return 0;
       }
       }
       log_info(LD_REND,"Chose '%s' as intro point for '%s'.",
       log_info(LD_REND,"Chose '%s' as intro point for '%s'.",
-               extend_info->nickname, safe_str(conn->rend_query));
+               extend_info->nickname,
+               safe_str(conn->rend_data->onion_address));
     }
     }
 
 
     /* If we have specified a particular exit node for our
     /* If we have specified a particular exit node for our
@@ -1163,7 +1173,7 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       rep_hist_note_used_internal(time(NULL), need_uptime, 1);
       if (circ) {
       if (circ) {
         /* write the service_id into circ */
         /* write the service_id into circ */
-        strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+        circ->rend_data = rend_data_dup(conn->rend_data);
         if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
         if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
             circ->_base.state == CIRCUIT_STATE_OPEN)
             circ->_base.state == CIRCUIT_STATE_OPEN)
           rend_client_rendcirc_has_opened(circ);
           rend_client_rendcirc_has_opened(circ);

+ 18 - 8
src/or/connection.c

@@ -385,6 +385,8 @@ _connection_free(connection_t *conn)
       memset(edge_conn->socks_request, 0xcc, sizeof(socks_request_t));
       memset(edge_conn->socks_request, 0xcc, sizeof(socks_request_t));
       tor_free(edge_conn->socks_request);
       tor_free(edge_conn->socks_request);
     }
     }
+    if (edge_conn->rend_data)
+      rend_data_free(edge_conn->rend_data);
   }
   }
   if (conn->type == CONN_TYPE_CONTROL) {
   if (conn->type == CONN_TYPE_CONTROL) {
     control_connection_t *control_conn = TO_CONTROL_CONN(conn);
     control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -405,6 +407,8 @@ _connection_free(connection_t *conn)
     }
     }
     if (dir_conn->cached_dir)
     if (dir_conn->cached_dir)
       cached_dir_decref(dir_conn->cached_dir);
       cached_dir_decref(dir_conn->cached_dir);
+    if (dir_conn->rend_data)
+      rend_data_free(dir_conn->rend_data);
   }
   }
 
 
   if (conn->s >= 0) {
   if (conn->s >= 0) {
@@ -523,21 +527,22 @@ connection_about_to_close_connection(connection_t *conn)
          * failed: forget about this router, and maybe try again. */
          * failed: forget about this router, and maybe try again. */
         connection_dir_request_failed(dir_conn);
         connection_dir_request_failed(dir_conn);
       }
       }
-      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC) {
+      if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC && dir_conn->rend_data) {
         /* Give it a try. However, there is no re-fetching for v0 rend
         /* Give it a try. However, there is no re-fetching for v0 rend
          * descriptors; if the response is empty or the descriptor is
          * descriptors; if the response is empty or the descriptor is
          * unusable, close pending connections (unless a v2 request is
          * unusable, close pending connections (unless a v2 request is
          * still in progress). */
          * still in progress). */
-        rend_client_desc_trynow(dir_conn->rend_query, 0);
+        rend_client_desc_trynow(dir_conn->rend_data->onion_address, 0);
       }
       }
       /* If we were trying to fetch a v2 rend desc and did not succeed,
       /* If we were trying to fetch a v2 rend desc and did not succeed,
        * retry as needed. (If a fetch is successful, the connection state
        * retry as needed. (If a fetch is successful, the connection state
        * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that
        * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC to mark that
        * refetching is unnecessary.) */
        * refetching is unnecessary.) */
       if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
       if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
-          dir_conn->rend_query &&
-          strlen(dir_conn->rend_query) == REND_SERVICE_ID_LEN_BASE32)
-        rend_client_refetch_v2_renddesc(dir_conn->rend_query);
+          dir_conn->rend_data &&
+          strlen(dir_conn->rend_data->onion_address) ==
+              REND_SERVICE_ID_LEN_BASE32)
+        rend_client_refetch_v2_renddesc(dir_conn->rend_data);
       break;
       break;
     case CONN_TYPE_OR:
     case CONN_TYPE_OR:
       or_conn = TO_OR_CONN(conn);
       or_conn = TO_OR_CONN(conn);
@@ -2565,6 +2570,7 @@ connection_get_by_type_state_rendquery(int type, int state,
 
 
   tor_assert(type == CONN_TYPE_DIR ||
   tor_assert(type == CONN_TYPE_DIR ||
              type == CONN_TYPE_AP || type == CONN_TYPE_EXIT);
              type == CONN_TYPE_AP || type == CONN_TYPE_EXIT);
+  tor_assert(rendquery);
 
 
   SMARTLIST_FOREACH(conns, connection_t *, conn,
   SMARTLIST_FOREACH(conns, connection_t *, conn,
   {
   {
@@ -2572,12 +2578,16 @@ connection_get_by_type_state_rendquery(int type, int state,
         !conn->marked_for_close &&
         !conn->marked_for_close &&
         (!state || state == conn->state)) {
         (!state || state == conn->state)) {
       if (type == CONN_TYPE_DIR &&
       if (type == CONN_TYPE_DIR &&
+          TO_DIR_CONN(conn)->rend_data &&
           (rendversion < 0 ||
           (rendversion < 0 ||
-           rendversion == TO_DIR_CONN(conn)->rend_version) &&
-          !rend_cmp_service_ids(rendquery, TO_DIR_CONN(conn)->rend_query))
+           rendversion == TO_DIR_CONN(conn)->rend_data->rend_desc_version) &&
+          !rend_cmp_service_ids(rendquery,
+                                TO_DIR_CONN(conn)->rend_data->onion_address))
         return conn;
         return conn;
       else if (CONN_IS_EDGE(conn) &&
       else if (CONN_IS_EDGE(conn) &&
-              !rend_cmp_service_ids(rendquery, TO_EDGE_CONN(conn)->rend_query))
+               TO_EDGE_CONN(conn)->rend_data &&
+               !rend_cmp_service_ids(rendquery,
+                            TO_EDGE_CONN(conn)->rend_data->onion_address))
         return conn;
         return conn;
     }
     }
   });
   });

+ 31 - 15
src/or/connection_edge.c

@@ -1587,6 +1587,7 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
     /* it's a hidden-service request */
     /* it's a hidden-service request */
     rend_cache_entry_t *entry;
     rend_cache_entry_t *entry;
     int r;
     int r;
+    rend_service_authorization_t *client_auth;
     tor_assert(!automap);
     tor_assert(!automap);
     if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
     if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
       /* if it's a resolve request, fail it right now, rather than
       /* if it's a resolve request, fail it right now, rather than
@@ -1608,14 +1609,16 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
       return -1;
       return -1;
     }
     }
 
 
-    strlcpy(conn->rend_query, socks->address, sizeof(conn->rend_query));
+    conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+    strlcpy(conn->rend_data->onion_address, socks->address,
+            sizeof(conn->rend_data->onion_address));
     log_info(LD_REND,"Got a hidden service request for ID '%s'",
     log_info(LD_REND,"Got a hidden service request for ID '%s'",
-             safe_str(conn->rend_query));
+             safe_str(conn->rend_data->onion_address));
     /* see if we already have it cached */
     /* see if we already have it cached */
-    r = rend_cache_lookup_entry(conn->rend_query, -1, &entry);
+    r = rend_cache_lookup_entry(conn->rend_data->onion_address, -1, &entry);
     if (r<0) {
     if (r<0) {
       log_warn(LD_BUG,"Invalid service name '%s'",
       log_warn(LD_BUG,"Invalid service name '%s'",
-               safe_str(conn->rend_query));
+               safe_str(conn->rend_data->onion_address));
       connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
       connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
       return -1;
       return -1;
     }
     }
@@ -1624,14 +1627,26 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
      * a stable circuit yet, but we know we'll need *something*. */
      * a stable circuit yet, but we know we'll need *something*. */
     rep_hist_note_used_internal(now, 0, 1);
     rep_hist_note_used_internal(now, 0, 1);
 
 
+    /* Look up if we have client authorization for it. */
+    client_auth = rend_client_lookup_service_authorization(
+                                          conn->rend_data->onion_address);
+    if (client_auth) {
+      log_info(LD_REND, "Using previously configured client authorization "
+                        "for hidden service request.");
+      memcpy(conn->rend_data->descriptor_cookie,
+             client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN);
+      conn->rend_data->auth_type = client_auth->auth_type;
+    }
     if (r==0) {
     if (r==0) {
       conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
       conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
-               safe_str(conn->rend_query));
+               safe_str(conn->rend_data->onion_address));
       /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
       /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-       * arrives first. */
-      rend_client_refetch_v2_renddesc(conn->rend_query);
-      rend_client_refetch_renddesc(conn->rend_query);
+       * arrives first. Exception: When using client authorization, only
+       * fetch v2 descriptors.*/
+      rend_client_refetch_v2_renddesc(conn->rend_data);
+      if (conn->rend_data->auth_type == REND_NO_AUTH)
+        rend_client_refetch_renddesc(conn->rend_data->onion_address);
     } else { /* r > 0 */
     } else { /* r > 0 */
 /** How long after we receive a hidden service descriptor do we consider
 /** How long after we receive a hidden service descriptor do we consider
  * it valid? */
  * it valid? */
@@ -1647,11 +1662,13 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
       } else {
       } else {
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT;
         log_info(LD_REND, "Stale descriptor %s. Refetching.",
         log_info(LD_REND, "Stale descriptor %s. Refetching.",
-                 safe_str(conn->rend_query));
+                 safe_str(conn->rend_data->onion_address));
         /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
         /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-         * arrives first. */
-        rend_client_refetch_v2_renddesc(conn->rend_query);
-        rend_client_refetch_renddesc(conn->rend_query);
+         * arrives first. Exception: When using client authorization, only
+         * fetch v2 descriptors.*/
+        rend_client_refetch_v2_renddesc(conn->rend_data);
+        if (conn->rend_data->auth_type == REND_NO_AUTH)
+          rend_client_refetch_renddesc(conn->rend_data->onion_address);
       }
       }
     }
     }
     return 0;
     return 0;
@@ -2531,8 +2548,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
     log_info(LD_REND,"begin is for rendezvous. configuring stream.");
     log_info(LD_REND,"begin is for rendezvous. configuring stream.");
     n_stream->_base.address = tor_strdup("(rendezvous)");
     n_stream->_base.address = tor_strdup("(rendezvous)");
     n_stream->_base.state = EXIT_CONN_STATE_CONNECTING;
     n_stream->_base.state = EXIT_CONN_STATE_CONNECTING;
-    strlcpy(n_stream->rend_query, origin_circ->rend_query,
-            sizeof(n_stream->rend_query));
+    n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
     tor_assert(connection_edge_is_rendezvous_stream(n_stream));
     tor_assert(connection_edge_is_rendezvous_stream(n_stream));
     assert_circuit_ok(circ);
     assert_circuit_ok(circ);
     if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) {
     if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) {
@@ -2815,7 +2831,7 @@ int
 connection_edge_is_rendezvous_stream(edge_connection_t *conn)
 connection_edge_is_rendezvous_stream(edge_connection_t *conn)
 {
 {
   tor_assert(conn);
   tor_assert(conn);
-  if (*conn->rend_query) /* XXX */ /* XXXX Why is this XXX? -NM */
+  if (conn->rend_data) /* XXX */ /* XXXX Why is this XXX? -NM */
     return 1;
     return 1;
   return 0;
   return 0;
 }
 }

+ 98 - 33
src/or/directory.c

@@ -60,6 +60,22 @@ static void dir_routerdesc_download_failed(smartlist_t *failed,
 static void note_client_request(int purpose, int compressed, size_t bytes);
 static void note_client_request(int purpose, int compressed, size_t bytes);
 static int client_likes_consensus(networkstatus_t *v, const char *want_url);
 static int client_likes_consensus(networkstatus_t *v, const char *want_url);
 
 
+static void directory_initiate_command_rend(const char *address,
+                                            const tor_addr_t *addr,
+                                            uint16_t or_port,
+                                            uint16_t dir_port,
+                                            int supports_conditional_consensus,
+                                            int supports_begindir,
+                                            const char *digest,
+                                            uint8_t dir_purpose,
+                                            uint8_t router_purpose,
+                                            int anonymized_connection,
+                                            const char *resource,
+                                            const char *payload,
+                                            size_t payload_len,
+                                            time_t if_modified_since,
+                                            const rend_data_t *rend_query);
+
 /********* START VARIABLES **********/
 /********* START VARIABLES **********/
 
 
 /** How far in the future do we allow a directory server to tell us it is
 /** How far in the future do we allow a directory server to tell us it is
@@ -434,29 +450,18 @@ directory_get_from_all_authorities(uint8_t dir_purpose,
     });
     });
 }
 }
 
 
-/** Launch a new connection to the directory server <b>status</b> to
- * upload or download a server or rendezvous
- * descriptor. <b>dir_purpose</b> determines what
- * kind of directory connection we're launching, and must be one of
- * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC|RENDDESC_V2}. <b>router_purpose</b>
- * specifies the descriptor purposes we have in mind (currently only
- * used for FETCH_DIR).
- *
- * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
- * of the HTTP post.  Otherwise, <b>payload</b> should be NULL.
- *
- * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we
- * want to fetch.
- */
+/** Same as directory_initiate_command_routerstatus(), but accepts
+ * rendezvous data to fetch a hidden service descriptor. */
 void
 void
-directory_initiate_command_routerstatus(routerstatus_t *status,
-                                        uint8_t dir_purpose,
-                                        uint8_t router_purpose,
-                                        int anonymized_connection,
-                                        const char *resource,
-                                        const char *payload,
-                                        size_t payload_len,
-                                        time_t if_modified_since)
+directory_initiate_command_routerstatus_rend(routerstatus_t *status,
+                                             uint8_t dir_purpose,
+                                             uint8_t router_purpose,
+                                             int anonymized_connection,
+                                             const char *resource,
+                                             const char *payload,
+                                             size_t payload_len,
+                                             time_t if_modified_since,
+                                             const rend_data_t *rend_query)
 {
 {
   routerinfo_t *router;
   routerinfo_t *router;
   char address_buf[INET_NTOA_BUF_LEN+1];
   char address_buf[INET_NTOA_BUF_LEN+1];
@@ -476,14 +481,46 @@ directory_initiate_command_routerstatus(routerstatus_t *status,
     address = address_buf;
     address = address_buf;
   }
   }
   tor_addr_from_ipv4h(&addr, status->addr);
   tor_addr_from_ipv4h(&addr, status->addr);
-  directory_initiate_command(address, &addr,
+  directory_initiate_command_rend(address, &addr,
                              status->or_port, status->dir_port,
                              status->or_port, status->dir_port,
                              status->version_supports_conditional_consensus,
                              status->version_supports_conditional_consensus,
                              status->version_supports_begindir,
                              status->version_supports_begindir,
                              status->identity_digest,
                              status->identity_digest,
                              dir_purpose, router_purpose,
                              dir_purpose, router_purpose,
                              anonymized_connection, resource,
                              anonymized_connection, resource,
-                             payload, payload_len, if_modified_since);
+                             payload, payload_len, if_modified_since,
+                             rend_query);
+}
+
+/** Launch a new connection to the directory server <b>status</b> to
+ * upload or download a server or rendezvous
+ * descriptor. <b>dir_purpose</b> determines what
+ * kind of directory connection we're launching, and must be one of
+ * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC|RENDDESC_V2}. <b>router_purpose</b>
+ * specifies the descriptor purposes we have in mind (currently only
+ * used for FETCH_DIR).
+ *
+ * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
+ * of the HTTP post.  Otherwise, <b>payload</b> should be NULL.
+ *
+ * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we
+ * want to fetch.
+ */
+void
+directory_initiate_command_routerstatus(routerstatus_t *status,
+                                        uint8_t dir_purpose,
+                                        uint8_t router_purpose,
+                                        int anonymized_connection,
+                                        const char *resource,
+                                        const char *payload,
+                                        size_t payload_len,
+                                        time_t if_modified_since)
+{
+  directory_initiate_command_routerstatus_rend(status, dir_purpose,
+                                          router_purpose,
+                                          anonymized_connection, resource,
+                                          payload, payload_len,
+                                          if_modified_since, NULL);
 }
 }
 
 
 /** Return true iff <b>conn</b> is the client side of a directory connection
 /** Return true iff <b>conn</b> is the client side of a directory connection
@@ -667,6 +704,28 @@ directory_initiate_command(const char *address, const tor_addr_t *_addr,
                            int anonymized_connection, const char *resource,
                            int anonymized_connection, const char *resource,
                            const char *payload, size_t payload_len,
                            const char *payload, size_t payload_len,
                            time_t if_modified_since)
                            time_t if_modified_since)
+{
+  directory_initiate_command_rend(address, _addr, or_port, dir_port,
+                             supports_conditional_consensus,
+                             supports_begindir, digest, dir_purpose,
+                             router_purpose, anonymized_connection,
+                             resource, payload, payload_len,
+                             if_modified_since, NULL);
+}
+
+/** Same as directory_initiate_command(), but accepts rendezvous data to
+ * fetch a hidden service descriptor. */
+static void
+directory_initiate_command_rend(const char *address, const tor_addr_t *_addr,
+                                uint16_t or_port, uint16_t dir_port,
+                                int supports_conditional_consensus,
+                                int supports_begindir, const char *digest,
+                                uint8_t dir_purpose, uint8_t router_purpose,
+                                int anonymized_connection,
+                                const char *resource,
+                                const char *payload, size_t payload_len,
+                                time_t if_modified_since,
+                                const rend_data_t *rend_query)
 {
 {
   dir_connection_t *conn;
   dir_connection_t *conn;
   or_options_t *options = get_options();
   or_options_t *options = get_options();
@@ -705,6 +764,10 @@ directory_initiate_command(const char *address, const tor_addr_t *_addr,
   /* decide whether we can learn our IP address from this conn */
   /* decide whether we can learn our IP address from this conn */
   conn->dirconn_direct = !anonymized_connection;
   conn->dirconn_direct = !anonymized_connection;
 
 
+  /* copy rendezvous data, if any */
+  if (rend_query)
+    conn->rend_data = rend_data_dup(rend_query);
+
   if (!anonymized_connection && !use_begindir) {
   if (!anonymized_connection && !use_begindir) {
     /* then we want to connect to dirport directly */
     /* then we want to connect to dirport directly */
 
 
@@ -1005,8 +1068,10 @@ directory_send_command(dir_connection_t *conn,
       /* this must be true or we wouldn't be doing the lookup */
       /* this must be true or we wouldn't be doing the lookup */
       tor_assert(strlen(resource) <= REND_SERVICE_ID_LEN_BASE32);
       tor_assert(strlen(resource) <= REND_SERVICE_ID_LEN_BASE32);
       /* This breaks the function abstraction. */
       /* This breaks the function abstraction. */
-      strlcpy(conn->rend_query, resource, sizeof(conn->rend_query));
-      conn->rend_version = 0;
+      conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+      strlcpy(conn->rend_data->onion_address, resource,
+              sizeof(conn->rend_data->onion_address));
+      conn->rend_data->rend_desc_version = 0;
 
 
       httpcommand = "GET";
       httpcommand = "GET";
       /* Request the most recent versioned descriptor. */
       /* Request the most recent versioned descriptor. */
@@ -1019,10 +1084,8 @@ directory_send_command(dir_connection_t *conn,
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
     case DIR_PURPOSE_FETCH_RENDDESC_V2:
       tor_assert(resource);
       tor_assert(resource);
       tor_assert(strlen(resource) <= REND_DESC_ID_V2_LEN_BASE32);
       tor_assert(strlen(resource) <= REND_DESC_ID_V2_LEN_BASE32);
-      /* Remember the query to refer to it when a response arrives. */
-      strlcpy(conn->rend_query, payload, sizeof(conn->rend_query));
-      conn->rend_version = 2;
-      payload = NULL;
+      tor_assert(!payload);
+      conn->rend_data->rend_desc_version = 2;
       httpcommand = "GET";
       httpcommand = "GET";
       len = strlen(resource) + 32;
       len = strlen(resource) + 32;
       url = tor_malloc(len);
       url = tor_malloc(len);
@@ -1877,6 +1940,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
   }
   }
 
 
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC) {
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC) {
+    tor_assert(conn->rend_data);
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
              "(%s))",
              "(%s))",
              (int)body_len, status_code, escaped(reason));
              (int)body_len, status_code, escaped(reason));
@@ -1892,7 +1956,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
         } else {
         } else {
           /* success. notify pending connections about this. */
           /* success. notify pending connections about this. */
           conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
           conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
-          rend_client_desc_trynow(conn->rend_query, -1);
+          rend_client_desc_trynow(conn->rend_data->onion_address, -1);
         }
         }
         break;
         break;
       case 404:
       case 404:
@@ -1914,12 +1978,13 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
   }
   }
 
 
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) {
   if (conn->_base.purpose == DIR_PURPOSE_FETCH_RENDDESC_V2) {
+    tor_assert(conn->rend_data);
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
     log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
              "(%s))",
              "(%s))",
              (int)body_len, status_code, escaped(reason));
              (int)body_len, status_code, escaped(reason));
     switch (status_code) {
     switch (status_code) {
       case 200:
       case 200:
-        switch (rend_cache_store_v2_desc_as_client(body, NULL)) {
+        switch (rend_cache_store_v2_desc_as_client(body, conn->rend_data)) {
           case -2:
           case -2:
             log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
             log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. "
                      "Retrying at another directory.");
                      "Retrying at another directory.");
@@ -1938,7 +2003,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
             log_info(LD_REND, "Successfully fetched v2 rendezvous "
             log_info(LD_REND, "Successfully fetched v2 rendezvous "
                      "descriptor.");
                      "descriptor.");
             conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
             conn->_base.purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
-            rend_client_desc_trynow(conn->rend_query, -1);
+            rend_client_desc_trynow(conn->rend_data->onion_address, -1);
             break;
             break;
         }
         }
         break;
         break;

+ 74 - 47
src/or/or.h

@@ -676,6 +676,55 @@ typedef enum {
 /** Maximum length of authorized client names for a hidden service. */
 /** Maximum length of authorized client names for a hidden service. */
 #define REND_CLIENTNAME_MAX_LEN 16
 #define REND_CLIENTNAME_MAX_LEN 16
 
 
+/** Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define REND_COOKIE_LEN DIGEST_LEN
+
+/** Client authorization type that a hidden service performs. */
+typedef enum rend_auth_type_t {
+  REND_NO_AUTH      = 0,
+  REND_BASIC_AUTH   = 1,
+  REND_STEALTH_AUTH = 2,
+} rend_auth_type_t;
+
+/** Client-side configuration of authorization for a hidden service. */
+typedef struct rend_service_authorization_t {
+  char descriptor_cookie[REND_DESC_COOKIE_LEN];
+  char onion_address[REND_SERVICE_ADDRESS_LEN+1];
+  rend_auth_type_t auth_type;
+} rend_service_authorization_t;
+
+/** Client- and server-side data that is used for hidden service connection
+ * establishment. Not all fields contain data depending on where this struct
+ * is used. */
+typedef struct rend_data_t {
+  /** Onion address (without the .onion part) that a client requests. */
+  char onion_address[REND_SERVICE_ID_LEN_BASE32+1];
+
+  /** (Optional) descriptor cookie that is used by a client. */
+  char descriptor_cookie[REND_DESC_COOKIE_LEN];
+
+  /** Authorization type for accessing a service used by a client. */
+  rend_auth_type_t auth_type;
+
+  /** Hash of the hidden service's PK used by a service. */
+  char rend_pk_digest[DIGEST_LEN];
+
+  /** Rendezvous cookie used by both, client and service. */
+  char rend_cookie[REND_COOKIE_LEN];
+
+  /** Rendezvous descriptor version that is used by a service. Used to
+   * distinguish introduction and rendezvous points belonging to the same
+   * rendezvous service ID, but different descriptor versions.
+   */
+  uint8_t rend_desc_version;
+} rend_data_t;
+
+/** Time interval for tracking possible replays of INTRODUCE2 cells.
+ * Incoming cells with timestamps half of this interval in the past or
+ * future are dropped immediately. */
+#define REND_REPLAY_TIME_INTERVAL (60 * 60)
+
 #define CELL_DIRECTION_IN 1
 #define CELL_DIRECTION_IN 1
 #define CELL_DIRECTION_OUT 2
 #define CELL_DIRECTION_OUT 2
 
 
@@ -1025,7 +1074,7 @@ typedef struct edge_connection_t {
   uint32_t n_written;
   uint32_t n_written;
 
 
   /** What rendezvous service are we querying for? (AP only) */
   /** What rendezvous service are we querying for? (AP only) */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
+  rend_data_t *rend_data;
 
 
   /** Number of times we've reassigned this application connection to
   /** Number of times we've reassigned this application connection to
    * a new circuit. We keep track because the timeout is longer if we've
    * a new circuit. We keep track because the timeout is longer if we've
@@ -1078,11 +1127,8 @@ typedef struct dir_connection_t {
   /** The zlib object doing on-the-fly compression for spooled data. */
   /** The zlib object doing on-the-fly compression for spooled data. */
   tor_zlib_state_t *zlib_state;
   tor_zlib_state_t *zlib_state;
 
 
-  /** What hidden service descriptor are we fetching, if any? */
-  int rend_version;
-
   /** What rendezvous service are we querying for? */
   /** What rendezvous service are we querying for? */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
+  rend_data_t *rend_data;
 
 
   char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
   char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
                                      * the directory server's signing key. */
                                      * the directory server's signing key. */
@@ -1747,7 +1793,6 @@ typedef struct crypt_path_t {
                                  CIPHER_KEY_LEN+\
                                  CIPHER_KEY_LEN+\
                                  DH_KEY_LEN)
                                  DH_KEY_LEN)
 #define ONIONSKIN_REPLY_LEN (DH_KEY_LEN+DIGEST_LEN)
 #define ONIONSKIN_REPLY_LEN (DH_KEY_LEN+DIGEST_LEN)
-#define REND_COOKIE_LEN DIGEST_LEN
 
 
 /** Information used to build a circuit. */
 /** Information used to build a circuit. */
 typedef struct {
 typedef struct {
@@ -1883,28 +1928,8 @@ typedef struct origin_circuit_t {
    */
    */
   crypt_path_t *cpath;
   crypt_path_t *cpath;
 
 
-  /** The rend_pk_digest field holds a hash of location-hidden service's
-   * PK if purpose is S_ESTABLISH_INTRO or S_RENDEZVOUSING.
-   */
-  char rend_pk_digest[DIGEST_LEN];
-
-  /** Holds rendezvous cookie if purpose is C_ESTABLISH_REND. Filled with
-   * zeroes otherwise.
-   */
-  char rend_cookie[REND_COOKIE_LEN];
-
-  /**
-   * The rend_query field holds the y portion of y.onion (nul-terminated)
-   * if purpose is C_INTRODUCING or C_ESTABLISH_REND, or is a C_GENERAL
-   * for a hidden service, or is S_*.
-   */
-  char rend_query[REND_SERVICE_ID_LEN_BASE32+1];
-
-  /** Stores the rendezvous descriptor version if purpose is S_*. Used to
-   * distinguish introduction and rendezvous points belonging to the same
-   * rendezvous service ID, but different descriptor versions.
-   */
-  uint8_t rend_desc_version;
+  /** Holds all rendezvous data on either client or service side. */
+  rend_data_t *rend_data;
 
 
   /** How many more relay_early cells can we send on this circuit, according
   /** How many more relay_early cells can we send on this circuit, according
    * to the specification? */
    * to the specification? */
@@ -3179,6 +3204,15 @@ void directory_initiate_command_routerstatus(routerstatus_t *status,
                                              const char *payload,
                                              const char *payload,
                                              size_t payload_len,
                                              size_t payload_len,
                                              time_t if_modified_since);
                                              time_t if_modified_since);
+void directory_initiate_command_routerstatus_rend(routerstatus_t *status,
+                                                  uint8_t dir_purpose,
+                                                  uint8_t router_purpose,
+                                                  int anonymized_connection,
+                                                  const char *resource,
+                                                  const char *payload,
+                                                  size_t payload_len,
+                                                  time_t if_modified_since,
+                                                const rend_data_t *rend_query);
 
 
 int parse_http_response(const char *headers, int *code, time_t *date,
 int parse_http_response(const char *headers, int *code, time_t *date,
                         compress_method_t *compression, char **response);
                         compress_method_t *compression, char **response);
@@ -3835,39 +3869,25 @@ void rend_client_rendcirc_has_opened(origin_circuit_t *circ);
 int rend_client_introduction_acked(origin_circuit_t *circ, const char *request,
 int rend_client_introduction_acked(origin_circuit_t *circ, const char *request,
                                    size_t request_len);
                                    size_t request_len);
 void rend_client_refetch_renddesc(const char *query);
 void rend_client_refetch_renddesc(const char *query);
-void rend_client_refetch_v2_renddesc(const char *query);
+void rend_client_refetch_v2_renddesc(const rend_data_t *rend_query);
 int rend_client_remove_intro_point(extend_info_t *failed_intro,
 int rend_client_remove_intro_point(extend_info_t *failed_intro,
-                                   const char *query);
+                                   const rend_data_t *rend_query);
 int rend_client_rendezvous_acked(origin_circuit_t *circ, const char *request,
 int rend_client_rendezvous_acked(origin_circuit_t *circ, const char *request,
                                  size_t request_len);
                                  size_t request_len);
 int rend_client_receive_rendezvous(origin_circuit_t *circ, const char *request,
 int rend_client_receive_rendezvous(origin_circuit_t *circ, const char *request,
                                    size_t request_len);
                                    size_t request_len);
 void rend_client_desc_trynow(const char *query, int rend_version);
 void rend_client_desc_trynow(const char *query, int rend_version);
 
 
-extend_info_t *rend_client_get_random_intro(const char *query);
+extend_info_t *rend_client_get_random_intro(const rend_data_t *rend_query);
 
 
 int rend_client_send_introduction(origin_circuit_t *introcirc,
 int rend_client_send_introduction(origin_circuit_t *introcirc,
                                   origin_circuit_t *rendcirc);
                                   origin_circuit_t *rendcirc);
-
-/** Client authorization type that a hidden service performs. */
-typedef enum rend_auth_type_t {
-  REND_NO_AUTH      = 0,
-  REND_BASIC_AUTH   = 1,
-  REND_STEALTH_AUTH = 2,
-} rend_auth_type_t;
-
-/** Client-side configuration of authorization for a hidden service. */
-typedef struct rend_service_authorization_t {
-  char descriptor_cookie[REND_DESC_COOKIE_LEN];
-  char onion_address[REND_SERVICE_ADDRESS_LEN+1];
-  rend_auth_type_t auth_type;
-} rend_service_authorization_t;
-
 int rend_parse_service_authorization(or_options_t *options,
 int rend_parse_service_authorization(or_options_t *options,
                                      int validate_only);
                                      int validate_only);
 rend_service_authorization_t *rend_client_lookup_service_authorization(
 rend_service_authorization_t *rend_client_lookup_service_authorization(
                                                 const char *onion_address);
                                                 const char *onion_address);
 void rend_service_authorization_free_all(void);
 void rend_service_authorization_free_all(void);
+rend_data_t *rend_data_dup(const rend_data_t *request);
 
 
 /********************************* rendcommon.c ***************************/
 /********************************* rendcommon.c ***************************/
 
 
@@ -3910,6 +3930,13 @@ typedef struct rend_service_descriptor_t {
   smartlist_t *successful_uploads;
   smartlist_t *successful_uploads;
 } rend_service_descriptor_t;
 } rend_service_descriptor_t;
 
 
+/** Free all storage associated with <b>data</b> */
+static INLINE void
+rend_data_free(rend_data_t *data)
+{
+  tor_free(data);
+}
+
 int rend_cmp_service_ids(const char *one, const char *two);
 int rend_cmp_service_ids(const char *one, const char *two);
 
 
 void rend_process_relay_cell(circuit_t *circ, int command, size_t length,
 void rend_process_relay_cell(circuit_t *circ, int command, size_t length,
@@ -3947,7 +3974,7 @@ int rend_cache_lookup_entry(const char *query, int version,
 int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc);
 int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc);
 int rend_cache_store(const char *desc, size_t desc_len, int published);
 int rend_cache_store(const char *desc, size_t desc_len, int published);
 int rend_cache_store_v2_desc_as_client(const char *desc,
 int rend_cache_store_v2_desc_as_client(const char *desc,
-                               const char *descriptor_cookie);
+                                       const rend_data_t *rend_query);
 int rend_cache_store_v2_desc_as_dir(const char *desc);
 int rend_cache_store_v2_desc_as_dir(const char *desc);
 int rend_cache_size(void);
 int rend_cache_size(void);
 int rend_encode_v2_descriptors(smartlist_t *descs_out,
 int rend_encode_v2_descriptors(smartlist_t *descs_out,

+ 113 - 61
src/or/rendclient.c

@@ -31,16 +31,18 @@ static int
 rend_client_send_establish_rendezvous(origin_circuit_t *circ)
 rend_client_send_establish_rendezvous(origin_circuit_t *circ)
 {
 {
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND);
+  tor_assert(circ->rend_data);
   log_info(LD_REND, "Sending an ESTABLISH_RENDEZVOUS cell");
   log_info(LD_REND, "Sending an ESTABLISH_RENDEZVOUS cell");
 
 
-  if (crypto_rand(circ->rend_cookie, REND_COOKIE_LEN) < 0) {
+  if (crypto_rand(circ->rend_data->rend_cookie, REND_COOKIE_LEN) < 0) {
     log_warn(LD_BUG, "Internal error: Couldn't produce random cookie.");
     log_warn(LD_BUG, "Internal error: Couldn't produce random cookie.");
     circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
     circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
     return -1;
     return -1;
   }
   }
   if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
   if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
                                    RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
                                    RELAY_COMMAND_ESTABLISH_RENDEZVOUS,
-                                   circ->rend_cookie, REND_COOKIE_LEN,
+                                   circ->rend_data->rend_cookie,
+                                   REND_COOKIE_LEN,
                                    circ->cpath->prev)<0) {
                                    circ->cpath->prev)<0) {
     /* circ is already marked for close */
     /* circ is already marked for close */
     log_warn(LD_GENERAL, "Couldn't send ESTABLISH_RENDEZVOUS cell");
     log_warn(LD_GENERAL, "Couldn't send ESTABLISH_RENDEZVOUS cell");
@@ -58,7 +60,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
                               origin_circuit_t *rendcirc)
                               origin_circuit_t *rendcirc)
 {
 {
   size_t payload_len;
   size_t payload_len;
-  int r;
+  int r, v3_shift = 0;
   char payload[RELAY_PAYLOAD_SIZE];
   char payload[RELAY_PAYLOAD_SIZE];
   char tmp[RELAY_PAYLOAD_SIZE];
   char tmp[RELAY_PAYLOAD_SIZE];
   rend_cache_entry_t *entry;
   rend_cache_entry_t *entry;
@@ -68,13 +70,16 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
 
 
   tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
   tor_assert(introcirc->_base.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
   tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY);
   tor_assert(rendcirc->_base.purpose == CIRCUIT_PURPOSE_C_REND_READY);
-  tor_assert(!rend_cmp_service_ids(introcirc->rend_query,
-                                   rendcirc->rend_query));
+  tor_assert(introcirc->rend_data);
+  tor_assert(rendcirc->rend_data);
+  tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
+                                   rendcirc->rend_data->onion_address));
 
 
-  if (rend_cache_lookup_entry(introcirc->rend_query, -1, &entry) < 1) {
+  if (rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
+                              &entry) < 1) {
     log_warn(LD_REND,
     log_warn(LD_REND,
              "query %s didn't have valid rend desc in cache. Failing.",
              "query %s didn't have valid rend desc in cache. Failing.",
-             escaped_safe_str(introcirc->rend_query));
+             escaped_safe_str(introcirc->rend_data->onion_address));
     goto err;
     goto err;
   }
   }
 
 
@@ -117,27 +122,45 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
     }
     }
   }
   }
 
 
+  /* If version is 3, write (optional) auth data and timestamp. */
+  if (entry->parsed->protocols & (1<<3)) {
+    tmp[0] = 3; /* version 3 of the cell format */
+    tmp[1] = (uint8_t)introcirc->rend_data->auth_type; /* auth type, if any */
+    v3_shift = 1;
+    if (introcirc->rend_data->auth_type != REND_NO_AUTH) {
+      set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
+      memcpy(tmp+4, introcirc->rend_data->descriptor_cookie,
+             REND_DESC_COOKIE_LEN);
+      v3_shift += 2+REND_DESC_COOKIE_LEN;
+    }
+    set_uint32(tmp+v3_shift+1, htonl(time(NULL)));
+    v3_shift += 4;
+  } /* if version 2 only write version number */
+  else if (entry->parsed->protocols & (1<<2)) {
+    tmp[0] = 2; /* version 2 of the cell format */
+  }
+
   /* write the remaining items into tmp */
   /* write the remaining items into tmp */
-  if (entry->parsed->protocols & (1<<2)) {
+  if (entry->parsed->protocols & (1<<3) || entry->parsed->protocols & (1<<2)) {
     /* version 2 format */
     /* version 2 format */
     extend_info_t *extend_info = rendcirc->build_state->chosen_exit;
     extend_info_t *extend_info = rendcirc->build_state->chosen_exit;
     int klen;
     int klen;
-    tmp[0] = 2; /* version 2 of the cell format */
     /* nul pads */
     /* nul pads */
-    set_uint32(tmp+1, tor_addr_to_ipv4h(&extend_info->addr));
-    set_uint16(tmp+5, htons(extend_info->port));
-    memcpy(tmp+7, extend_info->identity_digest, DIGEST_LEN);
-    klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+7+DIGEST_LEN+2,
-                                 sizeof(tmp)-(7+DIGEST_LEN+2));
-    set_uint16(tmp+7+DIGEST_LEN, htons(klen));
-    memcpy(tmp+7+DIGEST_LEN+2+klen, rendcirc->rend_cookie,
+    set_uint32(tmp+v3_shift+1, tor_addr_to_ipv4h(&extend_info->addr));
+    set_uint16(tmp+v3_shift+5, htons(extend_info->port));
+    memcpy(tmp+v3_shift+7, extend_info->identity_digest, DIGEST_LEN);
+    klen = crypto_pk_asn1_encode(extend_info->onion_key,
+                                 tmp+v3_shift+7+DIGEST_LEN+2,
+                                 sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2));
+    set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen));
+    memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_data->rend_cookie,
            REND_COOKIE_LEN);
            REND_COOKIE_LEN);
-    dh_offset = 7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
+    dh_offset = v3_shift+7+DIGEST_LEN+2+klen+REND_COOKIE_LEN;
   } else {
   } else {
     /* Version 0. */
     /* Version 0. */
     strncpy(tmp, rendcirc->build_state->chosen_exit->nickname,
     strncpy(tmp, rendcirc->build_state->chosen_exit->nickname,
             (MAX_NICKNAME_LEN+1)); /* nul pads */
             (MAX_NICKNAME_LEN+1)); /* nul pads */
-    memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_cookie,
+    memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_data->rend_cookie,
            REND_COOKIE_LEN);
            REND_COOKIE_LEN);
     dh_offset = MAX_NICKNAME_LEN+1+REND_COOKIE_LEN;
     dh_offset = MAX_NICKNAME_LEN+1+REND_COOKIE_LEN;
   }
   }
@@ -216,6 +239,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
   }
   }
 
 
   tor_assert(circ->build_state->chosen_exit);
   tor_assert(circ->build_state->chosen_exit);
+  tor_assert(circ->rend_data);
 
 
   if (request_len == 0) {
   if (request_len == 0) {
     /* It's an ACK; the introduction point relayed our introduction request. */
     /* It's an ACK; the introduction point relayed our introduction request. */
@@ -224,7 +248,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
      */
      */
     log_info(LD_REND,"Received ack. Telling rend circ...");
     log_info(LD_REND,"Received ack. Telling rend circ...");
     rendcirc = circuit_get_by_rend_query_and_purpose(
     rendcirc = circuit_get_by_rend_query_and_purpose(
-               circ->rend_query, CIRCUIT_PURPOSE_C_REND_READY);
+               circ->rend_data->onion_address, CIRCUIT_PURPOSE_C_REND_READY);
     if (rendcirc) { /* remember the ack */
     if (rendcirc) { /* remember the ack */
       rendcirc->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
       rendcirc->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
     } else {
     } else {
@@ -241,22 +265,22 @@ rend_client_introduction_acked(origin_circuit_t *circ,
      * If none remain, refetch the service descriptor.
      * If none remain, refetch the service descriptor.
      */
      */
     if (rend_client_remove_intro_point(circ->build_state->chosen_exit,
     if (rend_client_remove_intro_point(circ->build_state->chosen_exit,
-                                       circ->rend_query) > 0) {
+                                       circ->rend_data) > 0) {
       /* There are introduction points left. Re-extend the circuit to
       /* There are introduction points left. Re-extend the circuit to
        * another intro point and try again. */
        * another intro point and try again. */
       extend_info_t *extend_info;
       extend_info_t *extend_info;
       int result;
       int result;
-      extend_info = rend_client_get_random_intro(circ->rend_query);
+      extend_info = rend_client_get_random_intro(circ->rend_data);
       if (!extend_info) {
       if (!extend_info) {
         log_warn(LD_REND, "No introduction points left for %s. Closing.",
         log_warn(LD_REND, "No introduction points left for %s. Closing.",
-                 escaped_safe_str(circ->rend_query));
+                 escaped_safe_str(circ->rend_data->onion_address));
         circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
         circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
         return -1;
         return -1;
       }
       }
       log_info(LD_REND,
       log_info(LD_REND,
                "Got nack for %s from %s. Re-extending circ %d, "
                "Got nack for %s from %s. Re-extending circ %d, "
                "this time to %s.",
                "this time to %s.",
-               escaped_safe_str(circ->rend_query),
+               escaped_safe_str(circ->rend_data->onion_address),
                circ->build_state->chosen_exit->nickname, circ->_base.n_circ_id,
                circ->build_state->chosen_exit->nickname, circ->_base.n_circ_id,
                extend_info->nickname);
                extend_info->nickname);
       result = circuit_extend_to_new_exit(circ, extend_info);
       result = circuit_extend_to_new_exit(circ, extend_info);
@@ -337,15 +361,15 @@ directory_clean_last_hid_serv_requests(void)
  * descriptor, return 0, and in case of a failure -1. <b>query</b> is only
  * descriptor, return 0, and in case of a failure -1. <b>query</b> is only
  * passed for pretty log statements. */
  * passed for pretty log statements. */
 static int
 static int
-directory_get_from_hs_dir(const char *desc_id, const char *query)
+directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query)
 {
 {
   smartlist_t *responsible_dirs = smartlist_create();
   smartlist_t *responsible_dirs = smartlist_create();
   routerstatus_t *hs_dir;
   routerstatus_t *hs_dir;
   char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
   char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
   time_t now = time(NULL);
   time_t now = time(NULL);
+  char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
   tor_assert(desc_id);
   tor_assert(desc_id);
-  tor_assert(query);
-  tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32);
+  tor_assert(rend_query);
   /* Determine responsible dirs. Even if we can't get all we want,
   /* Determine responsible dirs. Even if we can't get all we want,
    * work with the ones we have. If it's empty, we'll notice below. */
    * work with the ones we have. If it's empty, we'll notice below. */
   (int) hid_serv_get_responsible_directories(responsible_dirs, desc_id);
   (int) hid_serv_get_responsible_directories(responsible_dirs, desc_id);
@@ -377,17 +401,33 @@ directory_get_from_hs_dir(const char *desc_id, const char *query)
    * directory now. */
    * directory now. */
   lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
   lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1);
 
 
-  /* Send fetch request. (Pass query as payload to write it to the directory
-   * connection so that it can be referred to when the response arrives.) */
-  directory_initiate_command_routerstatus(hs_dir,
+  /* Encode descriptor cookie for logging purposes. */
+  if (rend_query->auth_type != REND_NO_AUTH &&
+      base64_encode(descriptor_cookie_base64, 3*REND_DESC_COOKIE_LEN_BASE64,
+                    rend_query->descriptor_cookie, REND_DESC_COOKIE_LEN) < 0) {
+    log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
+    return 0;
+  }
+  /* Remove == signs and newline. */
+  descriptor_cookie_base64[strlen(descriptor_cookie_base64)-3] = '\0';
+
+  /* Send fetch request. (Pass query and possibly descriptor cookie so that
+   * they can be written to the directory connection and be referred to when
+   * the response arrives. */
+  directory_initiate_command_routerstatus_rend(hs_dir,
                                           DIR_PURPOSE_FETCH_RENDDESC_V2,
                                           DIR_PURPOSE_FETCH_RENDDESC_V2,
                                           ROUTER_PURPOSE_GENERAL,
                                           ROUTER_PURPOSE_GENERAL,
-                                          1, desc_id_base32, query, 0, 0);
+                                          1, desc_id_base32, NULL, 0, 0,
+                                          rend_query);
   log_info(LD_REND, "Sending fetch request for v2 descriptor for "
   log_info(LD_REND, "Sending fetch request for v2 descriptor for "
-                    "service '%s' with descriptor ID '%s' to hidden "
-                    "service directory '%s' on port %d.",
-           safe_str(query), safe_str(desc_id_base32), hs_dir->nickname,
-           hs_dir->dir_port);
+                    "service '%s' with descriptor ID '%s', auth type %d, "
+                    "and descriptor cookie '%s' to hidden service "
+                    "directory '%s' on port %d.",
+           rend_query->onion_address, desc_id_base32,
+           rend_query->auth_type,
+           (rend_query->auth_type == REND_NO_AUTH ? "NULL" :
+           escaped_safe_str(descriptor_cookie_base64)),
+           hs_dir->nickname, hs_dir->dir_port);
   return 1;
   return 1;
 }
 }
 
 
@@ -417,14 +457,13 @@ rend_client_refetch_renddesc(const char *query)
  * <b>query</b>.
  * <b>query</b>.
  */
  */
 void
 void
-rend_client_refetch_v2_renddesc(const char *query)
+rend_client_refetch_v2_renddesc(const rend_data_t *rend_query)
 {
 {
   char descriptor_id[DIGEST_LEN];
   char descriptor_id[DIGEST_LEN];
   int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
   int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
   int i, tries_left;
   int i, tries_left;
   rend_cache_entry_t *e = NULL;
   rend_cache_entry_t *e = NULL;
-  tor_assert(query);
-  tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32);
+  tor_assert(rend_query);
   /* Are we configured to fetch descriptors? */
   /* Are we configured to fetch descriptors? */
   if (!get_options()->FetchHidServDescriptors) {
   if (!get_options()->FetchHidServDescriptors) {
     log_warn(LD_REND, "We received an onion address for a v2 rendezvous "
     log_warn(LD_REND, "We received an onion address for a v2 rendezvous "
@@ -432,13 +471,13 @@ rend_client_refetch_v2_renddesc(const char *query)
     return;
     return;
   }
   }
   /* Before fetching, check if we already have the descriptor here. */
   /* Before fetching, check if we already have the descriptor here. */
-  if (rend_cache_lookup_entry(query, -1, &e) > 0) {
+  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) > 0) {
     log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
     log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
                       "already have that descriptor here. Not fetching.");
                       "already have that descriptor here. Not fetching.");
     return;
     return;
   }
   }
   log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
   log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
-            safe_str(query));
+            safe_str(rend_query->onion_address));
   /* Randomly iterate over the replicas until a descriptor can be fetched
   /* Randomly iterate over the replicas until a descriptor can be fetched
    * from one of the consecutive nodes, or no options are left. */
    * from one of the consecutive nodes, or no options are left. */
   tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
   tries_left = REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
@@ -449,13 +488,15 @@ rend_client_refetch_v2_renddesc(const char *query)
     int chosen_replica = replicas_left_to_try[rand];
     int chosen_replica = replicas_left_to_try[rand];
     replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
     replicas_left_to_try[rand] = replicas_left_to_try[--tries_left];
 
 
-    if (rend_compute_v2_desc_id(descriptor_id, query, NULL, time(NULL),
-                                chosen_replica) < 0) {
+    if (rend_compute_v2_desc_id(descriptor_id, rend_query->onion_address,
+                                rend_query->auth_type == REND_STEALTH_AUTH ?
+                                    rend_query->descriptor_cookie : NULL,
+                                time(NULL), chosen_replica) < 0) {
       log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
       log_warn(LD_REND, "Internal error: Computing v2 rendezvous "
                         "descriptor ID did not succeed.");
                         "descriptor ID did not succeed.");
       return;
       return;
     }
     }
-    if (directory_get_from_hs_dir(descriptor_id, query) != 0)
+    if (directory_get_from_hs_dir(descriptor_id, rend_query) != 0)
       return; /* either success or failure, but we're done */
       return; /* either success or failure, but we're done */
   }
   }
   /* If we come here, there are no hidden service directories left. */
   /* If we come here, there are no hidden service directories left. */
@@ -463,7 +504,7 @@ rend_client_refetch_v2_renddesc(const char *query)
                     "service directories to fetch descriptors, because "
                     "service directories to fetch descriptors, because "
                     "we already tried them all unsuccessfully.");
                     "we already tried them all unsuccessfully.");
   /* Close pending connections (unless a v0 request is still going on). */
   /* Close pending connections (unless a v0 request is still going on). */
-  rend_client_desc_trynow(query, 2);
+  rend_client_desc_trynow(rend_query->onion_address, 2);
   return;
   return;
 }
 }
 
 
@@ -474,24 +515,28 @@ rend_client_refetch_v2_renddesc(const char *query)
  * unrecognized, 1 if recognized and some intro points remain.
  * unrecognized, 1 if recognized and some intro points remain.
  */
  */
 int
 int
-rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query)
+rend_client_remove_intro_point(extend_info_t *failed_intro,
+                               const rend_data_t *rend_query)
 {
 {
   int i, r;
   int i, r;
   rend_cache_entry_t *ent;
   rend_cache_entry_t *ent;
   connection_t *conn;
   connection_t *conn;
 
 
-  r = rend_cache_lookup_entry(query, -1, &ent);
+  r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
   if (r<0) {
   if (r<0) {
-    log_warn(LD_BUG, "Malformed service ID %s.", escaped_safe_str(query));
+    log_warn(LD_BUG, "Malformed service ID %s.",
+             escaped_safe_str(rend_query->onion_address));
     return -1;
     return -1;
   }
   }
   if (r==0) {
   if (r==0) {
     log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
     log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
-             escaped_safe_str(query));
+             escaped_safe_str(rend_query->onion_address));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using client authorization, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(rend_query);
+    if (rend_query->auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(rend_query->onion_address);
     return 0;
     return 0;
   }
   }
 
 
@@ -508,22 +553,26 @@ rend_client_remove_intro_point(extend_info_t *failed_intro, const char *query)
   if (smartlist_len(ent->parsed->intro_nodes) == 0) {
   if (smartlist_len(ent->parsed->intro_nodes) == 0) {
     log_info(LD_REND,
     log_info(LD_REND,
              "No more intro points remain for %s. Re-fetching descriptor.",
              "No more intro points remain for %s. Re-fetching descriptor.",
-             escaped_safe_str(query));
+             escaped_safe_str(rend_query->onion_address));
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
     /* Fetch both, v0 and v2 rend descriptors in parallel. Use whichever
-     * arrives first. */
-    rend_client_refetch_v2_renddesc(query);
-    rend_client_refetch_renddesc(query);
+     * arrives first. Exception: When using client authorization, only
+     * fetch v2 descriptors.*/
+    rend_client_refetch_v2_renddesc(rend_query);
+    if (rend_query->auth_type == REND_NO_AUTH)
+      rend_client_refetch_renddesc(rend_query->onion_address);
 
 
     /* move all pending streams back to renddesc_wait */
     /* move all pending streams back to renddesc_wait */
     while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
     while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
-                                   AP_CONN_STATE_CIRCUIT_WAIT, query, -1))) {
+                                   AP_CONN_STATE_CIRCUIT_WAIT,
+                                   rend_query->onion_address, -1))) {
       conn->state = AP_CONN_STATE_RENDDESC_WAIT;
       conn->state = AP_CONN_STATE_RENDDESC_WAIT;
     }
     }
 
 
     return 0;
     return 0;
   }
   }
   log_info(LD_REND,"%d options left for %s.",
   log_info(LD_REND,"%d options left for %s.",
-           smartlist_len(ent->parsed->intro_nodes), escaped_safe_str(query));
+           smartlist_len(ent->parsed->intro_nodes),
+           escaped_safe_str(rend_query->onion_address));
   return 1;
   return 1;
 }
 }
 
 
@@ -648,10 +697,13 @@ rend_client_desc_trynow(const char *query, int rend_version)
         _conn->marked_for_close)
         _conn->marked_for_close)
       continue;
       continue;
     conn = TO_EDGE_CONN(_conn);
     conn = TO_EDGE_CONN(_conn);
-    if (rend_cmp_service_ids(query, conn->rend_query))
+    if (!conn->rend_data)
+      continue;
+    if (rend_cmp_service_ids(query, conn->rend_data->onion_address))
       continue;
       continue;
     assert_connection_ok(TO_CONN(conn), now);
     assert_connection_ok(TO_CONN(conn), now);
-    if (rend_cache_lookup_entry(conn->rend_query, -1, &entry) == 1 &&
+    if (rend_cache_lookup_entry(conn->rend_data->onion_address, -1,
+                                &entry) == 1 &&
         smartlist_len(entry->parsed->intro_nodes) > 0) {
         smartlist_len(entry->parsed->intro_nodes) > 0) {
       /* either this fetch worked, or it failed but there was a
       /* either this fetch worked, or it failed but there was a
        * valid entry from before which we should reuse */
        * valid entry from before which we should reuse */
@@ -689,17 +741,17 @@ rend_client_desc_trynow(const char *query, int rend_version)
  * have been tried and failed.
  * have been tried and failed.
  */
  */
 extend_info_t *
 extend_info_t *
-rend_client_get_random_intro(const char *query)
+rend_client_get_random_intro(const rend_data_t *rend_query)
 {
 {
   int i;
   int i;
   rend_cache_entry_t *entry;
   rend_cache_entry_t *entry;
   rend_intro_point_t *intro;
   rend_intro_point_t *intro;
   routerinfo_t *router;
   routerinfo_t *router;
 
 
-  if (rend_cache_lookup_entry(query, -1, &entry) < 1) {
+  if (rend_cache_lookup_entry(rend_query->onion_address, -1, &entry) < 1) {
     log_warn(LD_REND,
     log_warn(LD_REND,
              "Query '%s' didn't have valid rend desc in cache. Failing.",
              "Query '%s' didn't have valid rend desc in cache. Failing.",
-             safe_str(query));
+             safe_str(rend_query->onion_address));
     return NULL;
     return NULL;
   }
   }
 
 

+ 36 - 5
src/or/rendcommon.c

@@ -1243,7 +1243,7 @@ rend_cache_store_v2_desc_as_dir(const char *desc)
  */
  */
 int
 int
 rend_cache_store_v2_desc_as_client(const char *desc,
 rend_cache_store_v2_desc_as_client(const char *desc,
-                                   const char *descriptor_cookie)
+                                   const rend_data_t *rend_query)
 {
 {
   /*XXXX this seems to have a bit of duplicate code with
   /*XXXX this seems to have a bit of duplicate code with
    * rend_cache_store_v2_desc_as_dir().  Fix that. */
    * rend_cache_store_v2_desc_as_dir().  Fix that. */
@@ -1272,7 +1272,6 @@ rend_cache_store_v2_desc_as_client(const char *desc,
   rend_cache_entry_t *e;
   rend_cache_entry_t *e;
   tor_assert(rend_cache);
   tor_assert(rend_cache);
   tor_assert(desc);
   tor_assert(desc);
-  (void) descriptor_cookie; /* We don't use it, yet. */
   /* Parse the descriptor. */
   /* Parse the descriptor. */
   if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content,
   if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content,
                                        &intro_size, &encoded_size,
                                        &intro_size, &encoded_size,
@@ -1291,14 +1290,37 @@ rend_cache_store_v2_desc_as_client(const char *desc,
   }
   }
   /* Decode/decrypt introduction points. */
   /* Decode/decrypt introduction points. */
   if (intro_content) {
   if (intro_content) {
+    if (rend_query->auth_type != REND_NO_AUTH &&
+        rend_query->descriptor_cookie) {
+      char *ipos_decrypted;
+      size_t ipos_decrypted_size;
+      if (rend_decrypt_introduction_points(&ipos_decrypted,
+                                           &ipos_decrypted_size,
+                                           rend_query->descriptor_cookie,
+                                           intro_content,
+                                           intro_size) < 0) {
+        log_warn(LD_REND, "Failed to decrypt introduction points. We are "
+                 "probably unable to parse the encoded introduction points.");
+      } else {
+        /* Replace encrypted with decrypted introduction points. */
+        log_info(LD_REND, "Successfully decrypted introduction points.");
+        tor_free(intro_content);
+        intro_content = ipos_decrypted;
+        intro_size = ipos_decrypted_size;
+      }
+    }
     if (rend_parse_introduction_points(parsed, intro_content,
     if (rend_parse_introduction_points(parsed, intro_content,
-                                       intro_size) < 0) {
-      log_warn(LD_PROTOCOL,"Couldn't decode/decrypt introduction points.");
-      rend_service_descriptor_free(parsed);
+                                       intro_size) <= 0) {
+      log_warn(LD_REND, "Failed to parse introduction points. Either the "
+               "service has published a corrupt descriptor or you have "
+               "provided invalid authorization data.");
+      if (parsed)
+        rend_service_descriptor_free(parsed);
       tor_free(intro_content);
       tor_free(intro_content);
       return -2;
       return -2;
     }
     }
   } else {
   } else {
+    log_info(LD_REND, "Descriptor does not contain any introduction points.");
     parsed->intro_nodes = smartlist_create();
     parsed->intro_nodes = smartlist_create();
   }
   }
   /* We don't need the encoded/encrypted introduction points any longer. */
   /* We don't need the encoded/encrypted introduction points any longer. */
@@ -1426,3 +1448,12 @@ rend_cache_size(void)
   return strmap_size(rend_cache);
   return strmap_size(rend_cache);
 }
 }
 
 
+/** Allocate and return a new rend_data_t with the same
+ * contents as <b>query</b>. */
+rend_data_t *
+rend_data_dup(const rend_data_t *data)
+{
+  tor_assert(data);
+  return tor_memdup(data, sizeof(rend_data_t));
+}
+

+ 218 - 50
src/or/rendservice.c

@@ -69,6 +69,11 @@ typedef struct rend_service_t {
                          * up-to-date. */
                          * up-to-date. */
   time_t next_upload_time; /**< Scheduled next hidden service descriptor
   time_t next_upload_time; /**< Scheduled next hidden service descriptor
                             * upload time. */
                             * upload time. */
+  /** Map from digests of diffie-hellman values INTRODUCE2 to time_t of when
+   * they were received; used to prevent replays. */
+  digestmap_t *accepted_intros;
+  /** Time at which we last removed expired values from accepted_intros. */
+  time_t last_cleaned_accepted_intros;
 } rend_service_t;
 } rend_service_t;
 
 
 /** A list of rend_service_t's for services run on this OP.
 /** A list of rend_service_t's for services run on this OP.
@@ -125,6 +130,8 @@ rend_service_free(rend_service_t *service)
       rend_authorized_client_free(c););
       rend_authorized_client_free(c););
     smartlist_free(service->clients);
     smartlist_free(service->clients);
   }
   }
+  if (service->accepted_intros)
+    digestmap_free(service->accepted_intros, _tor_free);
   tor_free(service);
   tor_free(service);
 }
 }
 
 
@@ -360,7 +367,7 @@ rend_config_services(or_options_t *options, int validate_only)
       if (smartlist_len(type_names_split) < 2) {
       if (smartlist_len(type_names_split) < 2) {
         log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains "
         log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains "
                             "auth-type '%s', but no client names.",
                             "auth-type '%s', but no client names.",
-                 service->auth_type == 1 ? "basic" : "stealth");
+                 service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
         SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
         SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
         smartlist_free(type_names_split);
         smartlist_free(type_names_split);
         continue;
         continue;
@@ -423,7 +430,7 @@ rend_config_services(or_options_t *options, int validate_only)
                             "authorization type '%s'.",
                             "authorization type '%s'.",
                  smartlist_len(service->clients),
                  smartlist_len(service->clients),
                  service->auth_type == REND_BASIC_AUTH ? 512 : 16,
                  service->auth_type == REND_BASIC_AUTH ? 512 : 16,
-                 service->auth_type == 1 ? "basic" : "stealth");
+                 service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
         rend_service_free(service);
         rend_service_free(service);
         return -1;
         return -1;
       }
       }
@@ -720,8 +727,10 @@ rend_service_load_keys(void)
       tor_free(client_keys_str);
       tor_free(client_keys_str);
       strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
       strmap_free(parsed_clients, rend_authorized_client_strmap_item_free);
       if (r<0) {
       if (r<0) {
-        abort_writing_to_file(open_cfile);
-        abort_writing_to_file(open_hfile);
+        if (open_cfile)
+          abort_writing_to_file(open_cfile);
+        if (open_hfile)
+          abort_writing_to_file(open_hfile);
         return r;
         return r;
       } else {
       } else {
         finish_writing_to_file(open_cfile);
         finish_writing_to_file(open_cfile);
@@ -764,6 +773,64 @@ rend_service_requires_uptime(rend_service_t *service)
   return 0;
   return 0;
 }
 }
 
 
+/** Check client authorization of a given <b>descriptor_cookie</b> for
+ * <b>service</b>. Return 1 for success and 0 for failure. */
+static int
+rend_check_authorization(rend_service_t *service,
+                         const char *descriptor_cookie)
+{
+  rend_authorized_client_t *auth_client = NULL;
+  tor_assert(service);
+  tor_assert(descriptor_cookie);
+  if (!service->clients) {
+    log_warn(LD_BUG, "Can't check authorization for a service that has no "
+                     "authorized clients configured.");
+    return 0;
+  }
+
+  /* Look up client authorization by descriptor cookie. */
+  SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, client, {
+    if (!memcmp(client->descriptor_cookie, descriptor_cookie,
+                REND_DESC_COOKIE_LEN)) {
+      auth_client = client;
+      break;
+    }
+  });
+  if (!auth_client) {
+    char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+    base64_encode(descriptor_cookie_base64, sizeof(descriptor_cookie_base64),
+                  descriptor_cookie, REND_DESC_COOKIE_LEN);
+    log_info(LD_REND, "No authorization found for descriptor cookie '%s'! "
+                      "Dropping cell!",
+             descriptor_cookie_base64);
+    return 0;
+  }
+
+  /* Allow the request. */
+  log_debug(LD_REND, "Client %s authorized for service %s.",
+            auth_client->client_name, service->service_id);
+  return 1;
+}
+
+/** Remove elements from <b>service</b>'s replay cache that are old enough to
+ * be noticed by timestamp checking. */
+static void
+clean_accepted_intros(rend_service_t *service, time_t now)
+{
+  const time_t cutoff = now - REND_REPLAY_TIME_INTERVAL;
+
+  service->last_cleaned_accepted_intros = now;
+  if (!service->accepted_intros)
+    return;
+
+  DIGESTMAP_FOREACH_MODIFY(service->accepted_intros, digest, time_t *, t) {
+    if (*t < cutoff) {
+      tor_free(t);
+      MAP_DEL_CURRENT(digest);
+    }
+  } DIGESTMAP_FOREACH_END;
+}
+
 /******
 /******
  * Handle cells
  * Handle cells
  ******/
  ******/
@@ -780,7 +847,7 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
   char buf[RELAY_PAYLOAD_SIZE];
   char buf[RELAY_PAYLOAD_SIZE];
   char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
   char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
   rend_service_t *service;
   rend_service_t *service;
-  int r, i;
+  int r, i, v3_shift = 0;
   size_t len, keylen;
   size_t len, keylen;
   crypto_dh_env_t *dh = NULL;
   crypto_dh_env_t *dh = NULL;
   origin_circuit_t *launched = NULL;
   origin_circuit_t *launched = NULL;
@@ -791,9 +858,17 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
   int reason = END_CIRC_REASON_TORPROTOCOL;
   int reason = END_CIRC_REASON_TORPROTOCOL;
   crypto_pk_env_t *intro_key;
   crypto_pk_env_t *intro_key;
   char intro_key_digest[DIGEST_LEN];
   char intro_key_digest[DIGEST_LEN];
+  int auth_type;
+  size_t auth_len = 0;
+  char auth_data[REND_DESC_COOKIE_LEN];
+  crypto_digest_env_t *digest = NULL;
+  time_t now = time(NULL);
+  char diffie_hellman_hash[DIGEST_LEN];
+  time_t *access_time;
+  tor_assert(circuit->rend_data);
 
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
   log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
   log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
            escaped(serviceid), circuit->_base.n_circ_id);
            escaped(serviceid), circuit->_base.n_circ_id);
 
 
@@ -814,7 +889,8 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
 
 
   /* look up service depending on circuit. */
   /* look up service depending on circuit. */
   service = rend_service_get_by_pk_digest_and_version(
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
   if (!service) {
     log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.",
     log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.",
              escaped(serviceid));
              escaped(serviceid));
@@ -822,7 +898,7 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
   }
   }
 
 
   /* if descriptor version is 2, use intro key instead of service key. */
   /* if descriptor version is 2, use intro key instead of service key. */
-  if (circuit->rend_desc_version == 0) {
+  if (circuit->rend_data->rend_desc_version == 0) {
     intro_key = service->private_key;
     intro_key = service->private_key;
   } else {
   } else {
     intro_key = circuit->intro_key;
     intro_key = circuit->intro_key;
@@ -854,33 +930,70 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
     return -1;
     return -1;
   }
   }
   len = r;
   len = r;
-  if (*buf == 2) {
+  if (*buf == 3) {
+    /* Version 3 INTRODUCE2 cell. */
+    time_t ts = 0, now = time(NULL);
+    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);
+          return -1;
+        }
+        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);
+    }
+
+    /* Check timestamp. */
+    memcpy((char*)&ts, buf+1+v3_shift, sizeof(uint32_t));
+    v3_shift += 4;
+    ts = ntohl(ts);
+    if ((now - ts) < -1 * REND_REPLAY_TIME_INTERVAL / 2 ||
+        (now - ts) > REND_REPLAY_TIME_INTERVAL / 2) {
+      log_warn(LD_REND, "INTRODUCE2 cell is too %s. Discarding.",
+          (now - ts) < 0 ? "old" : "new");
+      return -1;
+    }
+  }
+  if (*buf == 2 || *buf == 3) {
     /* Version 2 INTRODUCE2 cell. */
     /* Version 2 INTRODUCE2 cell. */
     int klen;
     int klen;
     extend_info = tor_malloc_zero(sizeof(extend_info_t));
     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);
+    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] = '$';
     extend_info->nickname[0] = '$';
     base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
     base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
                   extend_info->identity_digest, DIGEST_LEN);
                   extend_info->identity_digest, DIGEST_LEN);
 
 
-    klen = ntohs(get_uint16(buf+7+DIGEST_LEN));
-    if ((int)len != 7+DIGEST_LEN+2+klen+20+128) {
-      log_warn(LD_PROTOCOL, "Bad length %u for version 2 INTRODUCE2 cell.",
-               (int)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;
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
       goto err;
     }
     }
-    extend_info->onion_key = crypto_pk_asn1_decode(buf+7+DIGEST_LEN+2, klen);
+    extend_info->onion_key =
+        crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen);
     if (!extend_info->onion_key) {
     if (!extend_info->onion_key) {
-      log_warn(LD_PROTOCOL,
-               "Error decoding onion key in version 2 INTRODUCE2 cell.");
+      log_warn(LD_PROTOCOL, "Error decoding onion key in version %d "
+                            "INTRODUCE2 cell.", *buf);
       reason = END_CIRC_REASON_TORPROTOCOL;
       reason = END_CIRC_REASON_TORPROTOCOL;
       goto err;
       goto err;
     }
     }
-    ptr = buf+7+DIGEST_LEN+2+klen;
-    len -= 7+DIGEST_LEN+2+klen;
+    ptr = buf+v3_shift+7+DIGEST_LEN+2+klen;
+    len -= v3_shift+7+DIGEST_LEN+2+klen;
   } else {
   } else {
     char *rp_nickname;
     char *rp_nickname;
     size_t nickname_field_len;
     size_t nickname_field_len;
@@ -932,6 +1045,54 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
   r_cookie = ptr;
   r_cookie = ptr;
   base16_encode(hexcookie,9,r_cookie,4);
   base16_encode(hexcookie,9,r_cookie,4);
 
 
+  /* Determine hash of Diffie-Hellman, part 1 to detect replays. */
+  digest = crypto_new_digest_env();
+  crypto_digest_add_bytes(digest, ptr+REND_COOKIE_LEN, DH_KEY_LEN);
+  crypto_digest_get_digest(digest, diffie_hellman_hash, DIGEST_LEN);
+  crypto_free_digest_env(digest);
+
+  /* Iterate over past requests, remove those which are older than one hour,
+   * and check whether there is one with same Diffie-Hellman, part 1. */
+  if (!service->accepted_intros)
+    service->accepted_intros = digestmap_new();
+
+  access_time = digestmap_get(service->accepted_intros, diffie_hellman_hash);
+  if (access_time != NULL) {
+    log_warn(LD_REND, "Possible replay detected! We received an "
+             "INTRODUCE2 cell with same first part of "
+             "Diffie-Hellman handshake %d seconds ago. Dropping "
+             "cell.",
+             (int) (now - *access_time));
+    goto err;
+  }
+
+  /* Add request to access history, including time and hash of
+   * Diffie-Hellman, part 1. */
+  access_time = tor_malloc(sizeof(time_t));
+  *access_time = now;
+  digestmap_set(service->accepted_intros, diffie_hellman_hash, access_time);
+  if (service->last_cleaned_accepted_intros + REND_REPLAY_TIME_INTERVAL < now)
+    clean_accepted_intros(service, now);
+
+  /* If the service performs client authorization, check included auth data. */
+  if (service->clients) {
+    if (auth_len > 0) {
+      if (rend_check_authorization(service, auth_data)) {
+        log_info(LD_REND, "Authorization data in INTRODUCE2 cell are valid.");
+      } else {
+        log_info(LD_REND, "The authorization data that are contained in "
+                 "the INTRODUCE2 cell are invalid. Dropping cell.");
+        reason = END_CIRC_REASON_CONNECTFAILED;
+        goto err;
+      }
+    } else {
+      log_info(LD_REND, "INTRODUCE2 cell does not contain authentication "
+               "data, but we require client authorization. Dropping cell.");
+      reason = END_CIRC_REASON_CONNECTFAILED;
+      goto err;
+    }
+  }
+
   /* Try DH handshake... */
   /* Try DH handshake... */
   dh = crypto_dh_new();
   dh = crypto_dh_new();
   if (!dh || crypto_dh_generate_public(dh)<0) {
   if (!dh || crypto_dh_generate_public(dh)<0) {
@@ -976,12 +1137,14 @@ rend_service_introduce(origin_circuit_t *circuit, const char *request,
            escaped_safe_str(extend_info->nickname), hexcookie, serviceid);
            escaped_safe_str(extend_info->nickname), hexcookie, serviceid);
   tor_assert(launched->build_state);
   tor_assert(launched->build_state);
   /* Fill in the circuit's state. */
   /* Fill in the circuit's state. */
-  memcpy(launched->rend_pk_digest, circuit->rend_pk_digest,
+  launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+  memcpy(launched->rend_data->rend_pk_digest,
+         circuit->rend_data->rend_pk_digest,
          DIGEST_LEN);
          DIGEST_LEN);
-  memcpy(launched->rend_cookie, r_cookie, REND_COOKIE_LEN);
-  strlcpy(launched->rend_query, service->service_id,
-          sizeof(launched->rend_query));
-  launched->rend_desc_version = service->descriptor_version;
+  memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
+  strlcpy(launched->rend_data->onion_address, service->service_id,
+          sizeof(launched->rend_data->onion_address));
+  launched->rend_data->rend_desc_version = service->descriptor_version;
   launched->build_state->pending_final_cpath = cpath =
   launched->build_state->pending_final_cpath = cpath =
     tor_malloc_zero(sizeof(crypt_path_t));
     tor_malloc_zero(sizeof(crypt_path_t));
   cpath->magic = CRYPT_PATH_MAGIC;
   cpath->magic = CRYPT_PATH_MAGIC;
@@ -1053,13 +1216,7 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
   newstate->pending_final_cpath = oldstate->pending_final_cpath;
   newstate->pending_final_cpath = oldstate->pending_final_cpath;
   oldstate->pending_final_cpath = NULL;
   oldstate->pending_final_cpath = NULL;
 
 
-  memcpy(newcirc->rend_query, oldcirc->rend_query,
-         REND_SERVICE_ID_LEN_BASE32+1);
-  memcpy(newcirc->rend_pk_digest, oldcirc->rend_pk_digest,
-         DIGEST_LEN);
-  memcpy(newcirc->rend_cookie, oldcirc->rend_cookie,
-         REND_COOKIE_LEN);
-  newcirc->rend_desc_version = oldcirc->rend_desc_version;
+  newcirc->rend_data = rend_data_dup(oldcirc->rend_data);
 }
 }
 
 
 /** Launch a circuit to serve as an introduction point for the service
 /** Launch a circuit to serve as an introduction point for the service
@@ -1105,10 +1262,11 @@ rend_service_launch_establish_intro(rend_service_t *service,
     intro->extend_info = extend_info_dup(launched->build_state->chosen_exit);
     intro->extend_info = extend_info_dup(launched->build_state->chosen_exit);
   }
   }
 
 
-  strlcpy(launched->rend_query, service->service_id,
-          sizeof(launched->rend_query));
-  memcpy(launched->rend_pk_digest, service->pk_digest, DIGEST_LEN);
-  launched->rend_desc_version = service->descriptor_version;
+  launched->rend_data = tor_malloc_zero(sizeof(rend_data_t));
+  strlcpy(launched->rend_data->onion_address, service->service_id,
+          sizeof(launched->rend_data->onion_address));
+  memcpy(launched->rend_data->rend_pk_digest, service->pk_digest, DIGEST_LEN);
+  launched->rend_data->rend_desc_version = service->descriptor_version;
   if (service->descriptor_version == 2)
   if (service->descriptor_version == 2)
     launched->intro_key = crypto_pk_dup_key(intro->intro_key);
     launched->intro_key = crypto_pk_dup_key(intro->intro_key);
   if (launched->_base.state == CIRCUIT_STATE_OPEN)
   if (launched->_base.state == CIRCUIT_STATE_OPEN)
@@ -1133,12 +1291,14 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
 
 
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
   tor_assert(circuit->cpath);
   tor_assert(circuit->cpath);
+  tor_assert(circuit->rend_data);
 
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 
 
   service = rend_service_get_by_pk_digest_and_version(
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
   if (!service) {
     log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
     log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %d.",
              serviceid, circuit->_base.n_circ_id);
              serviceid, circuit->_base.n_circ_id);
@@ -1214,8 +1374,10 @@ rend_service_intro_established(origin_circuit_t *circuit, const char *request,
              "received INTRO_ESTABLISHED cell on non-intro circuit.");
              "received INTRO_ESTABLISHED cell on non-intro circuit.");
     goto err;
     goto err;
   }
   }
+  tor_assert(circuit->rend_data);
   service = rend_service_get_by_pk_digest_and_version(
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
   if (!service) {
     log_warn(LD_REND, "Unknown service on introduction circuit %d.",
     log_warn(LD_REND, "Unknown service on introduction circuit %d.",
              circuit->_base.n_circ_id);
              circuit->_base.n_circ_id);
@@ -1225,7 +1387,7 @@ rend_service_intro_established(origin_circuit_t *circuit, const char *request,
   circuit->_base.purpose = CIRCUIT_PURPOSE_S_INTRO;
   circuit->_base.purpose = CIRCUIT_PURPOSE_S_INTRO;
 
 
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
   log_info(LD_REND,
   log_info(LD_REND,
            "Received INTRO_ESTABLISHED cell on circuit %d for service %s",
            "Received INTRO_ESTABLISHED cell on circuit %d for service %s",
            circuit->_base.n_circ_id, serviceid);
            circuit->_base.n_circ_id, serviceid);
@@ -1252,12 +1414,13 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
   tor_assert(circuit->_base.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
   tor_assert(circuit->cpath);
   tor_assert(circuit->cpath);
   tor_assert(circuit->build_state);
   tor_assert(circuit->build_state);
+  tor_assert(circuit->rend_data);
   hop = circuit->build_state->pending_final_cpath;
   hop = circuit->build_state->pending_final_cpath;
   tor_assert(hop);
   tor_assert(hop);
 
 
-  base16_encode(hexcookie,9,circuit->rend_cookie,4);
+  base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4);
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circuit->rend_pk_digest, REND_SERVICE_ID_LEN);
+                circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
 
 
   log_info(LD_REND,
   log_info(LD_REND,
            "Done building circuit %d to rendezvous with "
            "Done building circuit %d to rendezvous with "
@@ -1265,7 +1428,8 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
            circuit->_base.n_circ_id, hexcookie, serviceid);
            circuit->_base.n_circ_id, hexcookie, serviceid);
 
 
   service = rend_service_get_by_pk_digest_and_version(
   service = rend_service_get_by_pk_digest_and_version(
-              circuit->rend_pk_digest, circuit->rend_desc_version);
+              circuit->rend_data->rend_pk_digest,
+              circuit->rend_data->rend_desc_version);
   if (!service) {
   if (!service) {
     log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
     log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
              "introduction circuit.");
              "introduction circuit.");
@@ -1274,7 +1438,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
   }
   }
 
 
   /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
   /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
-  memcpy(buf, circuit->rend_cookie, REND_COOKIE_LEN);
+  memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN);
   if (crypto_dh_get_public(hop->dh_handshake_state,
   if (crypto_dh_get_public(hop->dh_handshake_state,
                            buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
                            buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
     log_warn(LD_GENERAL,"Couldn't get DH public key.");
     log_warn(LD_GENERAL,"Couldn't get DH public key.");
@@ -1336,7 +1500,8 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest,
                                                   CIRCUIT_PURPOSE_S_INTRO))) {
                                                   CIRCUIT_PURPOSE_S_INTRO))) {
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
-        circ->rend_desc_version == desc_version) {
+        circ->rend_data &&
+        circ->rend_data->rend_desc_version == desc_version) {
       return circ;
       return circ;
     }
     }
   }
   }
@@ -1346,7 +1511,8 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest,
                                         CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
                                         CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
     if (!memcmp(circ->build_state->chosen_exit->identity_digest,
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
                 intro->extend_info->identity_digest, DIGEST_LEN) &&
-        circ->rend_desc_version == desc_version) {
+        circ->rend_data &&
+        circ->rend_data->rend_desc_version == desc_version) {
       return circ;
       return circ;
     }
     }
   }
   }
@@ -1827,11 +1993,13 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
   rend_service_port_config_t *chosen_port;
   rend_service_port_config_t *chosen_port;
 
 
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
   tor_assert(circ->_base.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+  tor_assert(circ->rend_data);
   log_debug(LD_REND,"beginning to hunt for addr/port");
   log_debug(LD_REND,"beginning to hunt for addr/port");
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
   base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                circ->rend_pk_digest, REND_SERVICE_ID_LEN);
-  service = rend_service_get_by_pk_digest_and_version(circ->rend_pk_digest,
-                                                      circ->rend_desc_version);
+                circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+  service = rend_service_get_by_pk_digest_and_version(
+                circ->rend_data->rend_pk_digest,
+                circ->rend_data->rend_desc_version);
   if (!service) {
   if (!service) {
     log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
     log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
              "rendezvous circuit %d; closing.",
              "rendezvous circuit %d; closing.",