Index: /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/doc/tor.1.in (working copy) @@ -472,6 +472,15 @@ ReachableAddresses instead. (Default: 80, 443) .LP .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 A comma-separated list of IP addresses and ports that your firewall allows you to connect to. The format is as @@ -1268,6 +1277,18 @@ service. Possible version numbers are 0 and 2. (Default: 0, 2) .LP .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 Every time the specified period elapses, Tor uploads any rendezvous service descriptors to the directory servers. This information is also @@ -1452,6 +1473,8 @@ .TP .B \fIHiddenServiceDirectory\fP/hostname The .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 .TP .B \fIHiddenServiceDirectory\fP/private_key @@ -1456,6 +1479,11 @@ .TP .B \fIHiddenServiceDirectory\fP/private_key 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 .BR privoxy (1), .BR tsocks (1), Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuitbuild.c (working copy) @@ -2933,7 +2933,8 @@ 1, bridge->identity, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, - 0, "authority.z", NULL, 0, 0); + 0, "authority.z", NULL, 0, 0, + NULL, REND_NO_AUTH, NULL); tor_free(address); } Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuitlist.c (working copy) @@ -1026,7 +1026,8 @@ safe_str(ocirc->rend_query), safe_str(build_state_get_exit_nickname(ocirc->build_state))); rend_client_remove_intro_point(ocirc->build_state->chosen_exit, - ocirc->rend_query); + ocirc->rend_query, ocirc->rend_auth_type, + ocirc->rend_desc_cookie); } if (circ->n_conn) connection_or_send_destroy(circ->n_circ_id, circ->n_conn, reason); Index: /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/circuituse.c (working copy) @@ -1056,8 +1056,14 @@ log_info(LD_REND, "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); + /* 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_query, + conn->rend_auth_type, + conn->rend_desc_cookie); + if (conn->rend_auth_type == REND_NO_AUTH) + rend_client_refetch_renddesc(conn->rend_query); conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } @@ -1139,6 +1145,11 @@ if (circ) { /* write the service_id into circ */ strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query)); + if (conn->rend_auth_type != REND_NO_AUTH) { + memcpy(circ->rend_desc_cookie, conn->rend_desc_cookie, + REND_DESC_COOKIE_LEN); + circ->rend_auth_type = conn->rend_auth_type; + } if (circ->_base.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->_base.state == CIRCUIT_STATE_OPEN) rend_client_rendcirc_has_opened(circ); Index: /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/connection.c (working copy) @@ -487,7 +487,9 @@ 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); + rend_client_refetch_v2_renddesc(dir_conn->rend_query, + dir_conn->rend_auth_type, + dir_conn->rend_desc_cookie); break; case CONN_TYPE_OR: or_conn = TO_OR_CONN(conn); Index: /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/connection_edge.c (working copy) @@ -1582,6 +1582,7 @@ /* it's a hidden-service request */ rend_cache_entry_t *entry; int r; + rend_service_authorization_t *client_auth; tor_assert(!automap); if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { /* if it's a resolve request, fail it right now, rather than @@ -1619,6 +1620,15 @@ * a stable circuit yet, but we know we'll need *something*. */ 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_query); + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + memcpy(conn->rend_desc_cookie, client_auth->descriptor_cookie, + REND_DESC_COOKIE_LEN); + conn->rend_auth_type = client_auth->auth_type; + } if (r==0) { conn->_base.state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", @@ -1624,9 +1634,13 @@ log_info(LD_REND, "Unknown descriptor %s. Fetching.", safe_str(conn->rend_query)); /* 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_query, + conn->rend_auth_type, + conn->rend_desc_cookie); + if (conn->rend_auth_type == REND_NO_AUTH) + rend_client_refetch_renddesc(conn->rend_query); } else { /* r > 0 */ /** How long after we receive a hidden service descriptor do we consider * it valid? */ @@ -1644,9 +1658,13 @@ log_info(LD_REND, "Stale descriptor %s. Refetching.", safe_str(conn->rend_query)); /* 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_query, + conn->rend_auth_type, + conn->rend_desc_cookie); + if (conn->rend_auth_type == REND_NO_AUTH) + rend_client_refetch_renddesc(conn->rend_query); } } return 0; Index: /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/directory.c (working copy) @@ -266,7 +266,8 @@ directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, post_via_tor, - NULL, payload, upload_len, 0); + NULL, payload, upload_len, 0, + NULL, REND_NO_AUTH, NULL); } SMARTLIST_FOREACH_END(ds); if (!found) { char *s = authority_type_to_string(type); @@ -353,7 +354,8 @@ 1, ri->cache_info.identity_digest, dir_purpose, router_purpose, - 0, resource, NULL, 0, if_modified_since); + 0, resource, NULL, 0, if_modified_since, + NULL, REND_NO_AUTH, NULL); } else log_notice(LD_DIR, "Ignoring directory request, since no bridge " "nodes are available yet."); @@ -396,7 +398,8 @@ router_purpose, get_via_tor, resource, NULL, 0, - if_modified_since); + if_modified_since, + NULL, REND_NO_AUTH, NULL); else { log_notice(LD_DIR, "While fetching directory info, " @@ -430,7 +433,8 @@ continue; rs = &ds->fake_status; directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose, - 0, resource, NULL, 0, 0); + 0, resource, NULL, 0, 0, + NULL, REND_NO_AUTH, NULL); }); } @@ -456,7 +460,10 @@ const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since) + time_t if_modified_since, + const char *rend_query, + rend_auth_type_t auth_type, + const char *rend_desc_cookie) { routerinfo_t *router; char address_buf[INET_NTOA_BUF_LEN+1]; @@ -478,7 +485,8 @@ status->identity_digest, dir_purpose, router_purpose, anonymized_connection, resource, - payload, payload_len, if_modified_since); + payload, payload_len, if_modified_since, + rend_query, auth_type, rend_desc_cookie); } /** Return true iff conn is the client side of a directory connection @@ -661,7 +669,10 @@ 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) + time_t if_modified_since, + const char *rend_query, + rend_auth_type_t auth_type, + const char *rend_desc_cookie) { dir_connection_t *conn; or_options_t *options = get_options(); @@ -700,6 +711,13 @@ /* decide whether we can learn our IP address from this conn */ conn->dirconn_direct = !anonymized_connection; + /* copy rendezvous data, if any */ + if (rend_query) + strlcpy(conn->rend_query, rend_query, sizeof(conn->rend_query)); + conn->rend_auth_type = auth_type; + if (rend_desc_cookie) + memcpy(conn->rend_desc_cookie, rend_desc_cookie, REND_DESC_COOKIE_LEN); + if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ @@ -1012,10 +1030,8 @@ case DIR_PURPOSE_FETCH_RENDDESC_V2: tor_assert(resource); 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)); + tor_assert(!payload); conn->rend_version = 2; - payload = NULL; httpcommand = "GET"; len = strlen(resource) + 32; url = tor_malloc(len); @@ -1912,7 +1928,8 @@ (int)body_len, status_code, escaped(reason)); switch (status_code) { case 200: - switch (rend_cache_store_v2_desc_as_client(body, NULL)) { + switch (rend_cache_store_v2_desc_as_client(body, + conn->rend_auth_type, conn->rend_desc_cookie)) { case -2: log_warn(LD_REND,"Fetching v2 rendezvous descriptor failed. " "Retrying at another directory."); Index: /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/networkstatus.c (working copy) @@ -1033,7 +1033,8 @@ 0, /* Not private */ resource, NULL, 0 /* No payload. */, - 0 /* No I-M-S. */); + 0 /* No I-M-S. */, + NULL, REND_NO_AUTH, NULL); } SMARTLIST_FOREACH_END(ds); } else { Index: /home/karsten/tor/tor-trunk-121-patches/src/or/or.h =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/or.h (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/or.h (working copy) @@ -676,6 +676,18 @@ /** Maximum length of authorized client names for a hidden service. */ #define REND_CLIENTNAME_MAX_LEN 16 +/** 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; + +/** 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_OUT 2 @@ -1043,6 +1055,15 @@ /** What rendezvous service are we querying for? (AP only) */ char rend_query[REND_SERVICE_ID_LEN_BASE32+1]; + /** (Optional) descriptor cookie for requesting v2 hidden service + * descriptors, decrypting introduction points, and authenticating at + * hidden service. */ + char rend_desc_cookie[REND_DESC_COOKIE_LEN]; + + /** Client authorization type that is used for this hidden-service + * request, if any. */ + rend_auth_type_t rend_auth_type; + /** Number of times we've reassigned this application connection to * a new circuit. We keep track because the timeout is longer if we've * already retried several times. */ @@ -1100,6 +1121,15 @@ /** What rendezvous service are we querying for? */ char rend_query[REND_SERVICE_ID_LEN_BASE32+1]; + /** (Optional) descriptor cookie for requesting v2 hidden service + * descriptors, decrypting introduction points, and authenticating at + * hidden service. */ + char rend_desc_cookie[REND_DESC_COOKIE_LEN]; + + /** Client authorization type that is used for this hidden-service + * request, if any. */ + rend_auth_type_t rend_auth_type; + char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for * the directory server's signing key. */ @@ -1916,6 +1946,15 @@ */ char rend_query[REND_SERVICE_ID_LEN_BASE32+1]; + /** (Optional) descriptor cookie for requesting v2 hidden service + * descriptors, decrypting introduction points, and authenticating at + * hidden service. */ + char rend_desc_cookie[REND_DESC_COOKIE_LEN]; + + /** Client authorization type that is used for this hidden-service + * request, if any. */ + rend_auth_type_t rend_auth_type; + /** 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. @@ -3181,7 +3220,10 @@ const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since); + time_t if_modified_since, + const char *rend_query, + rend_auth_type_t auth_type, + const char *rend_desc_cookie); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); @@ -3200,7 +3242,10 @@ int anonymized_connection, const char *resource, const char *payload, size_t payload_len, - time_t if_modified_since); + time_t if_modified_since, + const char *rend_query, + rend_auth_type_t auth_type, + const char *rend_desc_cookie); int dir_split_resource_into_fingerprints(const char *resource, smartlist_t *fp_out, int *compresseed_out, @@ -3833,9 +3878,13 @@ int rend_client_introduction_acked(origin_circuit_t *circ, const char *request, size_t request_len); void rend_client_refetch_renddesc(const char *query); -void rend_client_refetch_v2_renddesc(const char *query); +void rend_client_refetch_v2_renddesc(const char *query, + rend_auth_type_t auth_type, + const char *descriptor_cookie); int rend_client_remove_intro_point(extend_info_t *failed_intro, - const char *query); + const char *query, + rend_auth_type_t auth_type, + const char *descriptor_cookie); int rend_client_rendezvous_acked(origin_circuit_t *circ, const char *request, size_t request_len); int rend_client_receive_rendezvous(origin_circuit_t *circ, const char *request, @@ -3847,13 +3896,6 @@ int rend_client_send_introduction(origin_circuit_t *introcirc, 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]; @@ -3938,7 +3980,8 @@ 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_v2_desc_as_client(const char *desc, - const char *descriptor_cookie); + rend_auth_type_t auth_type, + const char *descriptor_cookie); int rend_cache_store_v2_desc_as_dir(const char *desc); int rend_cache_size(void); int rend_encode_v2_descriptors(smartlist_t *descs_out, Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendclient.c (working copy) @@ -58,7 +58,7 @@ origin_circuit_t *rendcirc) { size_t payload_len; - int r; + int r, v3_shift = 0; char payload[RELAY_PAYLOAD_SIZE]; char tmp[RELAY_PAYLOAD_SIZE]; rend_cache_entry_t *entry; @@ -117,22 +117,39 @@ } } + /* 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_auth_type; /* auth type, if any */ + v3_shift = 1; + if (introcirc->rend_auth_type != REND_NO_AUTH) { + set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN)); + memcpy(tmp+4, introcirc->rend_desc_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 */ - if (entry->parsed->protocols & (1<<2)) { + if (entry->parsed->protocols & (1<<3) || entry->parsed->protocols & (1<<2)) { /* version 2 format */ extend_info_t *extend_info = rendcirc->build_state->chosen_exit; int klen; - tmp[0] = 2; /* version 2 of the cell format */ /* 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_cookie, 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 { /* Version 0. */ strncpy(tmp, rendcirc->build_state->chosen_exit->nickname, @@ -241,7 +258,9 @@ * If none remain, refetch the service descriptor. */ if (rend_client_remove_intro_point(circ->build_state->chosen_exit, - circ->rend_query) > 0) { + circ->rend_query, + circ->rend_auth_type, + circ->rend_desc_cookie) > 0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ extend_info_t *extend_info; @@ -337,7 +356,9 @@ * descriptor, return 0, and in case of a failure -1. query is only * passed for pretty log statements. */ static int -directory_get_from_hs_dir(const char *desc_id, const char *query) +directory_get_from_hs_dir(const char *desc_id, const char *query, + rend_auth_type_t auth_type, + const char *descriptor_cookie) { smartlist_t *responsible_dirs = smartlist_create(); routerstatus_t *hs_dir; @@ -343,6 +364,7 @@ routerstatus_t *hs_dir; char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; time_t now = time(NULL); + char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64]; tor_assert(desc_id); tor_assert(query); tor_assert(strlen(query) == REND_SERVICE_ID_LEN_BASE32); @@ -376,17 +398,33 @@ * directory now. */ 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.) */ + /* Encode descriptor cookie for logging purposes. */ + if (descriptor_cookie && + base64_encode(descriptor_cookie_base64, 3*REND_DESC_COOKIE_LEN_BASE64, + 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(hs_dir, DIR_PURPOSE_FETCH_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, - 1, desc_id_base32, query, 0, 0); + 1, desc_id_base32, NULL, 0, 0, + query, auth_type, + descriptor_cookie); 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.", + query, desc_id_base32, auth_type, + (descriptor_cookie == NULL ? "NULL" : + escaped_safe_str(descriptor_cookie_base64)), + hs_dir->nickname, hs_dir->dir_port); return 1; } @@ -416,7 +454,9 @@ * query. */ void -rend_client_refetch_v2_renddesc(const char *query) +rend_client_refetch_v2_renddesc(const char *query, + rend_auth_type_t auth_type, + const char *descriptor_cookie) { char descriptor_id[DIGEST_LEN]; int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS]; @@ -448,8 +488,10 @@ int chosen_replica = replicas_left_to_try[rand]; 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, query, + auth_type == REND_STEALTH_AUTH ? + descriptor_cookie : NULL, + time(NULL), chosen_replica) < 0) { log_warn(LD_REND, "Internal error: Computing v2 rendezvous " "descriptor ID did not succeed."); return; @@ -454,7 +496,8 @@ "descriptor ID did not succeed."); return; } - if (directory_get_from_hs_dir(descriptor_id, query) != 0) + if (directory_get_from_hs_dir(descriptor_id, query, auth_type, + descriptor_cookie) != 0) return; /* either success or failure, but we're done */ } /* If we come here, there are no hidden service directories left. */ @@ -471,7 +514,9 @@ * unrecognized, 1 if recognized and some intro points remain. */ 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 char *query, + rend_auth_type_t auth_type, + const char *descriptor_cookie) { int i, r; rend_cache_entry_t *ent; @@ -486,9 +531,11 @@ log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.", escaped_safe_str(query)); /* 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(query, auth_type, descriptor_cookie); + if (auth_type == REND_NO_AUTH) + rend_client_refetch_renddesc(query); return 0; } @@ -507,9 +554,11 @@ "No more intro points remain for %s. Re-fetching descriptor.", escaped_safe_str(query)); /* 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(query, auth_type, descriptor_cookie); + if (auth_type == REND_NO_AUTH) + rend_client_refetch_renddesc(query); /* move all pending streams back to renddesc_wait */ while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendcommon.c (working copy) @@ -1236,6 +1236,7 @@ */ int rend_cache_store_v2_desc_as_client(const char *desc, + rend_auth_type_t auth_type, const char *descriptor_cookie) { /*XXXX this seems to have a bit of duplicate code with @@ -1265,7 +1266,6 @@ rend_cache_entry_t *e; tor_assert(rend_cache); tor_assert(desc); - (void) descriptor_cookie; /* We don't use it, yet. */ /* Parse the descriptor. */ if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content, &intro_size, &encoded_size, @@ -1284,10 +1284,31 @@ } /* Decode/decrypt introduction points. */ if (intro_content) { + if (auth_type != REND_NO_AUTH && descriptor_cookie) { + char *ipos_decrypted; + size_t ipos_decrypted_size; + if (rend_decrypt_introduction_points(&ipos_decrypted, + &ipos_decrypted_size, + 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, - 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); return -2; } @@ -1292,6 +1313,7 @@ return -2; } } else { + log_info(LD_REND, "Descriptor does not contain any introduction points."); parsed->intro_nodes = smartlist_create(); } /* We don't need the encoded/encrypted introduction points any longer. */ Index: /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/rendservice.c (working copy) @@ -69,8 +69,16 @@ * up-to-date. */ time_t next_upload_time; /**< Scheduled next hidden service descriptor * upload time. */ + smartlist_t *accepted_intros; /**< List of client_access_event_t's for + * accepted and answered INTRODUCE2 cells. */ } rend_service_t; +/** The event of a client accessing our hidden service. */ +typedef struct client_access_event_t { + time_t access_time; + char diffie_hellman_hash[DIGEST_LEN]; +} client_access_event_t; + /** A list of rend_service_t's for services run on this OP. */ static smartlist_t *rend_service_list = NULL; @@ -360,7 +368,7 @@ if (smartlist_len(type_names_split) < 2) { log_warn(LD_CONFIG, "HiddenServiceAuthorizeClient contains " "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_free(type_names_split); continue; @@ -423,7 +431,7 @@ "authorization type '%s'.", smartlist_len(service->clients), 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); return -1; } @@ -717,8 +725,10 @@ tor_free(client_keys_str); strmap_free(parsed_clients, rend_authorized_client_strmap_item_free); 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; } else { finish_writing_to_file(open_cfile); @@ -761,6 +771,45 @@ return 0; } +/** Check client authorization of a given descriptor_cookie for + * service. 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_info(LD_REND, "Client %s could be identified for service %s.", + auth_client->client_name, service->service_id); + return 1; +} + /****** * Handle cells ******/ @@ -777,7 +826,7 @@ char buf[RELAY_PAYLOAD_SIZE]; char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */ rend_service_t *service; - int r, i; + int r, i, v3_shift = 0; size_t len, keylen; crypto_dh_env_t *dh = NULL; origin_circuit_t *launched = NULL; @@ -788,6 +837,13 @@ int reason = END_CIRC_REASON_TORPROTOCOL; crypto_pk_env_t *intro_key; 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]; + client_access_event_t *event = NULL; base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, circuit->rend_pk_digest, REND_SERVICE_ID_LEN); @@ -851,13 +907,49 @@ return -1; } 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. */ int klen; 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] = '$'; base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1, extend_info->identity_digest, DIGEST_LEN); @@ -862,22 +954,23 @@ base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1, extend_info->identity_digest, DIGEST_LEN); - klen = ntohs(get_uint16(buf+7+DIGEST_LEN)); - 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; 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) { - 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; 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 { char *rp_nickname; size_t nickname_field_len; @@ -929,6 +1022,58 @@ r_cookie = ptr; 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 = smartlist_create(); + SMARTLIST_FOREACH(service->accepted_intros, client_access_event_t *, + access, { + if (access->access_time + REND_REPLAY_TIME_INTERVAL < now) { + tor_free(access); + SMARTLIST_DEL_CURRENT(service->accepted_intros, access); + } else if (!memcmp(access->diffie_hellman_hash, diffie_hellman_hash, + DIGEST_LEN)) { + 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.", + (uint32_t) (now - access->access_time)); + return 0; + } + }); + + /* Add request to access history, including time and hash of + * Diffie-Hellman, part 1. */ + event = tor_malloc_zero(sizeof(client_access_event_t)); + event->access_time = now; + memcpy(event->diffie_hellman_hash, diffie_hellman_hash, DIGEST_LEN); + smartlist_add(service->accepted_intros, event); + + /* 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... */ dh = crypto_dh_new(); if (!dh || crypto_dh_generate_public(dh)<0) { @@ -1379,7 +1524,8 @@ DIR_PURPOSE_UPLOAD_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, 1, NULL, desc->desc_str, - strlen(desc->desc_str), 0); + strlen(desc->desc_str), 0, + NULL, REND_NO_AUTH, NULL); base32_encode(desc_id_base32, sizeof(desc_id_base32), desc->desc_id, DIGEST_LEN); log_info(LD_REND, "Sending publish request for v2 descriptor for " Index: /home/karsten/tor/tor-trunk-121-patches/src/or/router.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/router.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/router.c (working copy) @@ -763,7 +763,8 @@ 0, me->cache_info.identity_digest, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_GENERAL, - 1, "authority.z", NULL, 0, 0); + 1, "authority.z", NULL, 0, 0, + NULL, REND_NO_AUTH, NULL); control_event_server_status(LOG_NOTICE, "CHECKING_REACHABILITY DIRADDRESS=%s:%d", Index: /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c =================================================================== --- /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c (revision 16614) +++ /home/karsten/tor/tor-trunk-121-patches/src/or/routerlist.c (working copy) @@ -3757,7 +3757,8 @@ directory_initiate_command_routerstatus(source, purpose, ROUTER_PURPOSE_GENERAL, 0, /* not private */ - resource, NULL, 0, 0); + resource, NULL, 0, 0, + NULL, REND_NO_AUTH, NULL); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, 1); }