diff options
Diffstat (limited to 'src/or')
65 files changed, 3300 insertions, 914 deletions
diff --git a/src/or/buffers.c b/src/or/buffers.c index 12a6c0239b..d5ecfb8488 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -1478,6 +1478,32 @@ socks_request_set_socks5_error(socks_request_t *req, req->reply[3] = 0x01; // ATYP field. } +static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor is not an HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor is not an HTTP Proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor as " + "an HTTP proxy.\n\n" + "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Implementation helper to implement fetch_from_*_socks. Instead of looking * at a buffer's contents, we look at the <b>datalen</b> bytes of data in * <b>data</b>. Instead of removing data from the buffer, we set @@ -1684,15 +1710,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, req->port = ntohs(get_uint16(data+5+len)); *drain_out = 5+len+2; - if (string_is_valid_ipv4_address(req->address) || - string_is_valid_ipv6_address(req->address)) { - log_unsafe_socks_warning(5,req->address,req->port,safe_socks); - - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } else if (!string_is_valid_hostname(req->address)) { + if (!string_is_valid_hostname(req->address)) { socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); log_warn(LD_PROTOCOL, @@ -1814,7 +1832,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, log_debug(LD_APP,"socks4: Everything is here. Success."); strlcpy(req->address, startaddr ? startaddr : tmpbuf, sizeof(req->address)); - if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { + if (!string_is_valid_hostname(req->address)) { log_warn(LD_PROTOCOL, "Your application (using socks4 to port %d) gave Tor " "a malformed hostname: %s. Rejecting the connection.", @@ -1834,32 +1852,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, case 'H': /* head */ case 'P': /* put/post */ case 'C': /* connect */ - strlcpy((char*)req->reply, -"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" -"Content-Type: text/html; charset=iso-8859-1\r\n\r\n" -"<html>\n" -"<head>\n" -"<title>Tor is not an HTTP Proxy</title>\n" -"</head>\n" -"<body>\n" -"<h1>Tor is not an HTTP Proxy</h1>\n" -"<p>\n" -"It appears you have configured your web browser to use Tor as an HTTP proxy." -"\n" -"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" -"Please configure your client accordingly.\n" -"</p>\n" -"<p>\n" -"See <a href=\"https://www.torproject.org/documentation.html\">" - "https://www.torproject.org/documentation.html</a> for more " - "information.\n" -"<!-- Plus this comment, to make the body response more than 512 bytes, so " -" IE will be willing to display it. Comment comment comment comment " -" comment comment comment comment comment comment comment comment.-->\n" -"</p>\n" -"</body>\n" -"</html>\n" - , MAX_SOCKS_REPLY_LEN); + strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, + MAX_SOCKS_REPLY_LEN); req->replylen = strlen((char*)req->reply)+1; /* fall through */ default: /* version is not socks4 or socks5 */ @@ -2022,6 +2016,34 @@ parse_socks_client(const uint8_t *data, size_t datalen, return -1; } +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (peek_buf_startswith(buf, "CONNECT ") || + peek_buf_startswith(buf, "DELETE ") || + peek_buf_startswith(buf, "GET ") || + peek_buf_startswith(buf, "POST ") || + peek_buf_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null + * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */ +int +peek_buf_startswith(const buf_t *buf, const char *cmd) +{ + char tmp[PEEK_BUF_STARTSWITH_MAX]; + size_t clen = strlen(cmd); + if (BUG(clen > sizeof(tmp))) + return 0; + if (buf->datalen < clen) + return 0; + peek_from_buf(tmp, clen, buf); + return fast_memeq(tmp, cmd, clen); +} + /** Return 1 iff buf looks more like it has an (obsolete) v0 controller * command on it than any valid v1 controller command. */ int diff --git a/src/or/buffers.h b/src/or/buffers.h index 23b58a571a..d884084385 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -53,6 +53,9 @@ int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); int peek_buf_has_control0_command(buf_t *buf); +#define PEEK_BUF_STARTSWITH_MAX 16 +int peek_buf_startswith(const buf_t *buf, const char *cmd); +int peek_buf_has_http_command(const buf_t *buf); int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); diff --git a/src/or/channel.c b/src/or/channel.c index df6d7d3423..9f8a03683f 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -2086,8 +2086,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) * are appropriate to the state transition in question. */ -void -channel_change_state(channel_t *chan, channel_state_t to_state) +static void +channel_change_state_(channel_t *chan, channel_state_t to_state) { channel_state_t from_state; unsigned char was_active, is_active; @@ -2206,18 +2206,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state) estimated_total_queue_size += chan->bytes_in_queue; } - /* Tell circuits if we opened and stuff */ - if (to_state == CHANNEL_STATE_OPEN) { - channel_do_open_actions(chan); - chan->has_been_open = 1; - - /* Check for queued cells to process */ - if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) - channel_process_cells(chan); - if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) - channel_flush_cells(chan); - } else if (to_state == CHANNEL_STATE_CLOSED || - to_state == CHANNEL_STATE_ERROR) { + if (to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { /* Assert that all queues are empty */ tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)); tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)); @@ -2225,6 +2215,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state) } /** + * As channel_change_state_, but change the state to any state but open. + */ +void +channel_change_state(channel_t *chan, channel_state_t to_state) +{ + tor_assert(to_state != CHANNEL_STATE_OPEN); + channel_change_state_(chan, to_state); +} + +/** + * As channel_change_state, but change the state to open. + */ +void +channel_change_state_open(channel_t *chan) +{ + channel_change_state_(chan, CHANNEL_STATE_OPEN); + + /* Tell circuits if we opened and stuff */ + channel_do_open_actions(chan); + chan->has_been_open = 1; + + /* Check for queued cells to process */ + if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) + channel_process_cells(chan); + if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) + channel_flush_cells(chan); +} + +/** * Change channel listener state * * This internal and subclass use only function is used to change channel diff --git a/src/or/channel.h b/src/or/channel.h index ea280f2fd2..2d0ec39924 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -522,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l); /* State/metadata setters */ void channel_change_state(channel_t *chan, channel_state_t to_state); +void channel_change_state_open(channel_t *chan); void channel_clear_identity_digest(channel_t *chan); void channel_clear_remote_end(channel_t *chan); void channel_mark_local(channel_t *chan); diff --git a/src/or/channeltls.c b/src/or/channeltls.c index f44e4fc8ea..6547451181 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -993,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, * We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or * CHANNEL_STATE_MAINT on this. */ - channel_change_state(base_chan, CHANNEL_STATE_OPEN); + channel_change_state_open(base_chan); /* We might have just become writeable; check and tell the scheduler */ if (connection_or_num_cells_writeable(conn) > 0) { scheduler_channel_wants_writes(base_chan); @@ -1915,7 +1915,6 @@ certs_cell_typenum_to_cert_type(int typenum) * of the connection, we then authenticate the server or mark the connection. * If it's the server side, wait for an AUTHENTICATE cell. */ - STATIC void channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) { diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 16cef0e56b..257edab50b 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -46,6 +46,7 @@ #include "crypto.h" #include "directory.h" #include "entrynodes.h" +#include "hs_ntor.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -74,6 +75,10 @@ static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); +static int circuit_send_first_onion_skin(origin_circuit_t *circ); +static int circuit_build_no_more_hops(origin_circuit_t *circ); +static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -912,234 +917,275 @@ circuit_purpose_may_omit_guard(int purpose) * If circ's first hop is closed, then we need to build a create * cell and send it forward. * - * Otherwise, we need to build a relay extend cell and send it - * forward. + * Otherwise, if circ's cpath still has any non-open hops, we need to + * build a relay extend cell and send it forward to the next non-open hop. + * + * If all hops on the cpath are open, we're done building the circuit + * and we should do housekeeping for the newly opened circuit. * * Return -reason if we want to tear down circ, else return 0. */ int circuit_send_next_onion_skin(origin_circuit_t *circ) { - crypt_path_t *hop; - const node_t *node; - tor_assert(circ); if (circ->cpath->state == CPATH_STATE_CLOSED) { - /* This is the first hop. */ - create_cell_t cc; - int fast; - int len; - log_debug(LD_CIRC,"First skin; sending create cell."); - memset(&cc, 0, sizeof(cc)); - if (circ->build_state->onehop_tunnel) - control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); - else { - control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - - /* If this is not a one-hop tunnel, the channel is being used - * for traffic that wants anonymity and protection from traffic - * analysis (such as netflow record retention). That means we want - * to pad it. - */ - if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) - circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; - } + /* Case one: we're on the first hop. */ + return circuit_send_first_onion_skin(circ); + } - node = node_get_by_id(circ->base_.n_chan->identity_digest); - fast = should_use_create_fast_for_circuit(circ); - if (!fast) { - /* We are an OR and we know the right onion key: we should - * send a create cell. - */ - circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, - circ->cpath->extend_info); - } else { - /* We are not an OR, and we're building the first hop of a circuit to a - * new OR: we can be speedy and use CREATE_FAST to save an RSA operation - * and a DH operation. */ - cc.cell_type = CELL_CREATE_FAST; - cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; - } + tor_assert(circ->cpath->state == CPATH_STATE_OPEN); + tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); + crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath); - len = onion_skin_create(cc.handshake_type, - circ->cpath->extend_info, - &circ->cpath->handshake_state, - cc.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); - return - END_CIRC_REASON_INTERNAL; - } - cc.handshake_len = len; + if (hop) { + /* Case two: we're on a hop after the first. */ + return circuit_send_intermediate_onion_skin(circ, hop); + } - if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) - return - END_CIRC_REASON_RESOURCELIMIT; + /* Case three: the circuit is finished. Do housekeeping tasks on it. */ + return circuit_build_no_more_hops(circ); +} - circ->cpath->state = CPATH_STATE_AWAITING_KEYS; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", - fast ? "CREATE_FAST" : "CREATE", - node ? node_describe(node) : "<unnamed>"); +/** + * Called from circuit_send_next_onion_skin() when we find ourselves connected + * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST + * cell to that hop. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_send_first_onion_skin(origin_circuit_t *circ) +{ + int fast; + int len; + const node_t *node; + create_cell_t cc; + memset(&cc, 0, sizeof(cc)); + + log_debug(LD_CIRC,"First skin; sending create cell."); + + if (circ->build_state->onehop_tunnel) { + control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); } else { - extend_cell_t ec; - int len; - tor_assert(circ->cpath->state == CPATH_STATE_OPEN); - tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); - log_debug(LD_CIRC,"starting to send subsequent skin."); - hop = onion_next_hop_in_cpath(circ->cpath); - memset(&ec, 0, sizeof(ec)); - if (!hop) { - /* done building the circuit. whew. */ - guard_usable_t r; - if (! circ->guard_state) { - if (circuit_get_cpath_len(circ) != 1 && - ! circuit_purpose_may_omit_guard(circ->base_.purpose) && - get_options()->UseEntryGuards) { - log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " - "guard state", - circuit_get_cpath_len(circ), circ, circ->base_.purpose); - } - r = GUARD_USABLE_NOW; - } else { - r = entry_guard_succeeded(&circ->guard_state); - } - const int is_usable_for_streams = (r == GUARD_USABLE_NOW); - if (r == GUARD_USABLE_NOW) { - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - } else if (r == GUARD_MAYBE_USABLE_LATER) { - // Wait till either a better guard succeeds, or till - // all better guards fail. - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); - } else { - tor_assert_nonfatal(r == GUARD_USABLE_NEVER); - return - END_CIRC_REASON_INTERNAL; - } + control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - /* XXXX #21422 -- the rest of this branch needs careful thought! - * Some of the things here need to happen when a circuit becomes - * mechanically open; some need to happen when it is actually usable. - * I think I got them right, but more checking would be wise. -NM - */ + /* If this is not a one-hop tunnel, the channel is being used + * for traffic that wants anonymity and protection from traffic + * analysis (such as netflow record retention). That means we want + * to pad it. + */ + if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) + circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + } - if (circuit_timeout_want_to_count_circ(circ)) { - struct timeval end; - long timediff; - tor_gettimeofday(&end); - timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + node = node_get_by_id(circ->base_.n_chan->identity_digest); + fast = should_use_create_fast_for_circuit(circ); + if (!fast) { + /* We know the right onion key: we should send a create cell. */ + circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, + circ->cpath->extend_info); + } else { + /* We don't know an onion key, so we need to fall back to CREATE_FAST. */ + cc.cell_type = CELL_CREATE_FAST; + cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; + } - /* - * If the circuit build time is much greater than we would have cut - * it off at, we probably had a suspend event along this codepath, - * and we should discard the value. - */ - if (timediff < 0 || - timediff > 2*get_circuit_build_close_time_ms()+1000) { - log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " - "Assuming clock jump. Purpose %d (%s)", timediff, - circ->base_.purpose, - circuit_purpose_to_string(circ->base_.purpose)); - } else if (!circuit_build_times_disabled(get_options())) { - /* Only count circuit times if the network is live */ - if (circuit_build_times_network_check_live( - get_circuit_build_times())) { - circuit_build_times_add_time(get_circuit_build_times_mutable(), - (build_time_t)timediff); - circuit_build_times_set_timeout(get_circuit_build_times_mutable()); - } - - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_build_times_network_circ_success( - get_circuit_build_times_mutable()); - } - } - } - log_info(LD_CIRC,"circuit built!"); - circuit_reset_failure_count(0); + len = onion_skin_create(cc.handshake_type, + circ->cpath->extend_info, + &circ->cpath->handshake_state, + cc.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); + return - END_CIRC_REASON_INTERNAL; + } + cc.handshake_len = len; - if (circ->build_state->onehop_tunnel || circ->has_opened) { - control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); - } + if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) + return - END_CIRC_REASON_RESOURCELIMIT; - pathbias_count_build_success(circ); - circuit_rep_hist_note_result(circ); - if (is_usable_for_streams) - circuit_has_opened(circ); /* do other actions as necessary */ - - if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { - const or_options_t *options = get_options(); - note_that_we_completed_a_circuit(); - /* FFFF Log a count of known routers here */ - log_notice(LD_GENERAL, - "Tor has successfully opened a circuit. " - "Looks like client functionality is working."); - if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { - log_notice(LD_GENERAL, - "Tor has successfully opened a circuit. " - "Looks like client functionality is working."); - } - control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); - clear_broken_connection_map(1); - if (server_mode(options) && !check_whether_orport_reachable(options)) { - inform_testing_reachability(); - consider_testing_reachability(1, 1); - } + circ->cpath->state = CPATH_STATE_AWAITING_KEYS; + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); + log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", + fast ? "CREATE_FAST" : "CREATE", + node ? node_describe(node) : "<unnamed>"); + return 0; +} + +/** + * Called from circuit_send_next_onion_skin() when we find that we have no + * more hops: mark the circuit as finished, and perform the necessary + * bookkeeping. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_build_no_more_hops(origin_circuit_t *circ) +{ + guard_usable_t r; + if (! circ->guard_state) { + if (circuit_get_cpath_len(circ) != 1 && + ! circuit_purpose_may_omit_guard(circ->base_.purpose) && + get_options()->UseEntryGuards) { + log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " + "guard state", + circuit_get_cpath_len(circ), circ, circ->base_.purpose); + } + r = GUARD_USABLE_NOW; + } else { + r = entry_guard_succeeded(&circ->guard_state); + } + const int is_usable_for_streams = (r == GUARD_USABLE_NOW); + if (r == GUARD_USABLE_NOW) { + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + } else if (r == GUARD_MAYBE_USABLE_LATER) { + // Wait till either a better guard succeeds, or till + // all better guards fail. + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); + } else { + tor_assert_nonfatal(r == GUARD_USABLE_NEVER); + return - END_CIRC_REASON_INTERNAL; + } + + /* XXXX #21422 -- the rest of this branch needs careful thought! + * Some of the things here need to happen when a circuit becomes + * mechanically open; some need to happen when it is actually usable. + * I think I got them right, but more checking would be wise. -NM + */ + + if (circuit_timeout_want_to_count_circ(circ)) { + struct timeval end; + long timediff; + tor_gettimeofday(&end); + timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + + /* + * If the circuit build time is much greater than we would have cut + * it off at, we probably had a suspend event along this codepath, + * and we should discard the value. + */ + if (timediff < 0 || + timediff > 2*get_circuit_build_close_time_ms()+1000) { + log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " + "Assuming clock jump. Purpose %d (%s)", timediff, + circ->base_.purpose, + circuit_purpose_to_string(circ->base_.purpose)); + } else if (!circuit_build_times_disabled(get_options())) { + /* Only count circuit times if the network is live */ + if (circuit_build_times_network_check_live( + get_circuit_build_times())) { + circuit_build_times_add_time(get_circuit_build_times_mutable(), + (build_time_t)timediff); + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } - /* We're done with measurement circuits here. Just close them */ - if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_build_times_network_circ_success( + get_circuit_build_times_mutable()); } - return 0; } - - if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { - log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); - return - END_CIRC_REASON_INTERNAL; + } + log_info(LD_CIRC,"circuit built!"); + circuit_reset_failure_count(0); + + if (circ->build_state->onehop_tunnel || circ->has_opened) { + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); + } + + pathbias_count_build_success(circ); + circuit_rep_hist_note_result(circ); + if (is_usable_for_streams) + circuit_has_opened(circ); /* do other actions as necessary */ + + if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { + const or_options_t *options = get_options(); + note_that_we_completed_a_circuit(); + /* FFFF Log a count of known routers here */ + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); + if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); } - - circuit_pick_extend_handshake(&ec.cell_type, - &ec.create_cell.cell_type, - &ec.create_cell.handshake_type, - hop->extend_info); - - tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); - ec.orport_ipv4.port = hop->extend_info->port; - tor_addr_make_unspec(&ec.orport_ipv6.addr); - memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); - /* Set the ED25519 identity too -- it will only get included - * in the extend2 cell if we're configured to use it, though. */ - ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); - - len = onion_skin_create(ec.create_cell.handshake_type, - hop->extend_info, - &hop->handshake_state, - ec.create_cell.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create failed."); - return - END_CIRC_REASON_INTERNAL; + control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); + clear_broken_connection_map(1); + if (server_mode(options) && !check_whether_orport_reachable(options)) { + inform_testing_reachability(); + consider_testing_reachability(1, 1); } - ec.create_cell.handshake_len = len; + } - log_info(LD_CIRC,"Sending extend relay cell."); - { - uint8_t command = 0; - uint16_t payload_len=0; - uint8_t payload[RELAY_PAYLOAD_SIZE]; - if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { - log_warn(LD_CIRC,"Couldn't format extend cell"); - return -END_CIRC_REASON_INTERNAL; - } + /* We're done with measurement circuits here. Just close them */ + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + } + return 0; +} - /* send it to hop->prev, because it will transfer - * it to a create cell and then send to hop */ - if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), - command, - (char*)payload, payload_len, - hop->prev) < 0) - return 0; /* circuit is closed */ +/** + * Called from circuit_send_next_onion_skin() when we find that we have a hop + * other than the first that we need to extend to: use <b>hop</b>'s + * information to extend the circuit another step. Return 0 on success; + * -reason on failure (if the circuit should be torn down). + */ +static int +circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop) +{ + int len; + extend_cell_t ec; + memset(&ec, 0, sizeof(ec)); + + log_debug(LD_CIRC,"starting to send subsequent skin."); + + if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { + log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); + return - END_CIRC_REASON_INTERNAL; + } + + circuit_pick_extend_handshake(&ec.cell_type, + &ec.create_cell.cell_type, + &ec.create_cell.handshake_type, + hop->extend_info); + + tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); + ec.orport_ipv4.port = hop->extend_info->port; + tor_addr_make_unspec(&ec.orport_ipv6.addr); + memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); + /* Set the ED25519 identity too -- it will only get included + * in the extend2 cell if we're configured to use it, though. */ + ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); + + len = onion_skin_create(ec.create_cell.handshake_type, + hop->extend_info, + &hop->handshake_state, + ec.create_cell.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create failed."); + return - END_CIRC_REASON_INTERNAL; + } + ec.create_cell.handshake_len = len; + + log_info(LD_CIRC,"Sending extend relay cell."); + { + uint8_t command = 0; + uint16_t payload_len=0; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { + log_warn(LD_CIRC,"Couldn't format extend cell"); + return -END_CIRC_REASON_INTERNAL; } - hop->state = CPATH_STATE_AWAITING_KEYS; + + /* send it to hop->prev, because that relay will transfer + * it to a create cell and then send to hop */ + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), + command, + (char*)payload, payload_len, + hop->prev) < 0) + return 0; /* circuit is closed */ } + hop->state = CPATH_STATE_AWAITING_KEYS; return 0; } @@ -1325,40 +1371,77 @@ circuit_extend(cell_t *cell, circuit_t *circ) return 0; } -/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in - * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are - * used as follows: +/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data. + * + * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden + * service circuits and <b>key_data</b> must be at least + * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length. + * + * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN + * bytes, which are used as follows: * - 20 to initialize f_digest * - 20 to initialize b_digest * - 16 to key f_crypto * - 16 to key b_crypto * * (If 'reverse' is true, then f_XX and b_XX are swapped.) + * + * Return 0 if init was successful, else -1 if it failed. */ int -circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse) +circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3) { crypto_digest_t *tmp_digest; crypto_cipher_t *tmp_crypto; + size_t digest_len = 0; + size_t cipher_key_len = 0; tor_assert(cpath); tor_assert(key_data); tor_assert(!(cpath->f_crypto || cpath->b_crypto || cpath->f_digest || cpath->b_digest)); - cpath->f_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN); - cpath->b_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN); + /* Basic key size validation */ + if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) { + return -1; + } - if (!(cpath->f_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)))) { + /* If we are using this cpath for next gen onion services use SHA3-256, + otherwise use good ol' SHA1 */ + if (is_hs_v3) { + digest_len = DIGEST256_LEN; + cipher_key_len = CIPHER256_KEY_LEN; + cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256); + cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256); + } else { + digest_len = DIGEST_LEN; + cipher_key_len = CIPHER_KEY_LEN; + cpath->f_digest = crypto_digest_new(); + cpath->b_digest = crypto_digest_new(); + } + + tor_assert(digest_len != 0); + tor_assert(cipher_key_len != 0); + const int cipher_key_bits = (int) cipher_key_len * 8; + + crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len); + crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len); + + cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len), + cipher_key_bits); + if (!cpath->f_crypto) { log_warn(LD_BUG,"Forward cipher initialization failed."); return -1; } - if (!(cpath->b_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) { + + cpath->b_crypto = crypto_cipher_new_with_bits( + key_data+(2*digest_len)+cipher_key_len, + cipher_key_bits); + if (!cpath->b_crypto) { log_warn(LD_BUG,"Backward cipher initialization failed."); return -1; } @@ -1424,7 +1507,7 @@ circuit_finish_handshake(origin_circuit_t *circ, onion_handshake_state_release(&hop->handshake_state); - if (circuit_init_cpath_crypto(hop, keys, 0)<0) { + if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) { return -END_CIRC_REASON_TORPROTOCOL; } @@ -1491,12 +1574,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) int onionskin_answer(or_circuit_t *circ, const created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce) { cell_t cell; crypt_path_t *tmp_cpath; + tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN); + if (created_cell_format(&cell, created_cell) < 0) { log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)", (int)created_cell->cell_type, (int)created_cell->handshake_len); @@ -1512,7 +1597,7 @@ onionskin_answer(or_circuit_t *circ, log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", (unsigned int)get_uint32(keys), (unsigned int)get_uint32(keys+20)); - if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) { + if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) { log_warn(LD_BUG,"Circuit initialization failed"); tor_free(tmp_cpath); return -1; @@ -1956,9 +2041,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) } if (options->ExitNodes) { log_warn(LD_CIRC, - "No specified %sexit routers seem to be running: " + "No exits in ExitNodes%s seem to be running: " "can't choose an exit.", - options->ExcludeExitNodesUnion_ ? "non-excluded " : ""); + options->ExcludeExitNodesUnion_ ? + ", except possibly those excluded by your configuration, " : ""); } return NULL; } @@ -2311,6 +2397,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) } } +#ifdef TOR_UNIT_TESTS + +/** Unittest helper function: Count number of hops in cpath linked list. */ +unsigned int +cpath_get_n_hops(crypt_path_t **head_ptr) +{ + unsigned int n_hops = 0; + crypt_path_t *tmp; + + if (!*head_ptr) { + return 0; + } + + tmp = *head_ptr; + if (tmp) { + n_hops++; + tmp = (*head_ptr)->next; + } + + return n_hops; +} + +#endif + /** A helper function used by onion_extend_cpath(). Use <b>purpose</b> * and <b>state</b> and the cpath <b>head</b> (currently populated only * to length <b>cur_len</b> to decide a suitable middle hop for a @@ -2580,7 +2690,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect) ed_pubkey = node_get_ed25519_id(node); } else if (node_get_ed25519_id(node)) { log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't " - " be able to authenticate it.", + "be able to authenticate it.", node_describe(node)); } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 45d9b2fb75..62a6367ed2 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); void circuit_note_clock_jumped(int seconds_elapsed); int circuit_extend(cell_t *cell, circuit_t *circ); -int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse); +int circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3); struct created_cell_t; int circuit_finish_handshake(origin_circuit_t *circ, const struct created_cell_t *created_cell); @@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason); int onionskin_answer(or_circuit_t *circ, const struct created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce); MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now, int *need_uptime, @@ -83,6 +84,8 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes)); #if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags, const or_options_t *options); +unsigned int cpath_get_n_hops(crypt_path_t **head_ptr); + #endif #endif diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 86b0aa097a..d11e128787 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -67,6 +67,7 @@ #include "main.h" #include "hs_circuitmap.h" #include "hs_common.h" +#include "hs_ident.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -957,6 +958,7 @@ circuit_free(circuit_t *circ) crypto_pk_free(ocirc->intro_key); rend_data_free(ocirc->rend_data); + hs_ident_circuit_free(ocirc->hs_ident); tor_free(ocirc->dest_address); if (ocirc->socks_username) { diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index d647062f46..2f76252563 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -48,6 +48,7 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const uint8_t *digest, uint8_t purpose); origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start); origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); void circuit_mark_all_unused_circs(void); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 9f9d3abf7c..a3b7066b18 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -42,6 +42,8 @@ #include "control.h" #include "entrynodes.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -55,6 +57,36 @@ static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); +/** Check whether the hidden service destination of the stream at + * <b>edge_conn</b> is the same as the destination of the circuit at + * <b>origin_circ</b>. */ +static int +circuit_matches_with_rend_stream(const edge_connection_t *edge_conn, + const origin_circuit_t *origin_circ) +{ + /* Check if this is a v2 rendezvous circ/stream */ + if ((edge_conn->rend_data && !origin_circ->rend_data) || + (!edge_conn->rend_data && origin_circ->rend_data) || + (edge_conn->rend_data && origin_circ->rend_data && + rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), + rend_data_get_address(origin_circ->rend_data)))) { + /* this circ is not for this conn */ + return 0; + } + + /* Check if this is a v3 rendezvous circ/stream */ + if ((edge_conn->hs_ident && !origin_circ->hs_ident) || + (!edge_conn->hs_ident && origin_circ->hs_ident) || + (edge_conn->hs_ident && origin_circ->hs_ident && + !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + &origin_circ->hs_ident->identity_pk))) { + /* this circ is not for this conn */ + return 0; + } + + return 1; +} + /** Return 1 if <b>circ</b> could be returned by circuit_get_best(). * Else return 0. */ @@ -169,14 +201,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, /* can't exit from this router */ return 0; } - } else { /* not general */ + } else { /* not general: this might be a rend circuit */ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); - if ((edge_conn->rend_data && !origin_circ->rend_data) || - (!edge_conn->rend_data && origin_circ->rend_data) || - (edge_conn->rend_data && origin_circ->rend_data && - rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), - rend_data_get_address(origin_circ->rend_data)))) { - /* this circ is not for this conn */ + if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) { return 0; } } @@ -2348,8 +2375,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, /* We are attaching a stream to a rendezvous circuit. That means * that an attempt to connect to a hidden service just * succeeded. Tell rendclient.c. */ - rend_client_note_connection_attempt_ended( - ENTRY_TO_EDGE_CONN(apconn)->rend_data); + hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn)); } if (cpath) { /* we were given one; use it */ diff --git a/src/or/command.c b/src/or/command.c index c667cbbe52..2c82984901 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -381,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan) created_cell.handshake_len = len; if (onionskin_answer(circ, &created_cell, - (const char *)keys, rend_circ_nonce)<0) { + (const char *)keys, sizeof(keys), + rend_circ_nonce)<0) { log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return; diff --git a/src/or/config.c b/src/or/config.c index 67bf3b9671..58d22d9f18 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -91,6 +91,7 @@ #include "relay.h" #include "rendclient.h" #include "rendservice.h" +#include "hs_config.h" #include "rephist.h" #include "router.h" #include "sandbox.h" @@ -674,6 +675,13 @@ static const config_deprecation_t option_deprecation_notes_[] = { "easier to fingerprint, and may open you to esoteric attacks." }, /* End of options deprecated since 0.2.9.2-alpha. */ + /* Deprecated since 0.3.2.0-alpha. */ + { "HTTPProxy", "It only applies to direct unencrypted HTTP connections " + "to your directory server, which your Tor probably wasn't using." }, + { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy " + "which should be used with HTTPSProxyAuthenticator." }, + /* End of options deprecated since 0.3.2.0-alpha. */ + { NULL, NULL } }; @@ -1675,7 +1683,7 @@ options_act(const or_options_t *old_options) sweep_bridge_list(); } - if (running_tor && rend_config_services(options, 0)<0) { + if (running_tor && hs_config_service_all(options, 0)<0) { log_warn(LD_BUG, "Previously validated hidden services line could not be added!"); return -1; @@ -1792,7 +1800,7 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); /* reload keys as needed for rendezvous services. */ - if (rend_service_load_all_keys(NULL)<0) { + if (hs_service_load_all_keys() < 0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } @@ -2798,10 +2806,6 @@ compute_publishserverdescriptor(or_options_t *options) * will generate too many circuits and potentially overload the network. */ #define MIN_CIRCUIT_STREAM_TIMEOUT 10 -/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might - * expose more information than we're comfortable with. */ -#define MIN_HEARTBEAT_PERIOD (30*60) - /** Lowest recommended value for CircuitBuildTimeout; if it is set too low * and LearnCircuitBuildTimeout is off, the failure rate for circuit * construction may be very high. In that case, if it is set below this @@ -3155,7 +3159,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "UseEntryGuards. Disabling."); options->UseEntryGuards = 0; } - if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) { + if (!options->DownloadExtraInfo && authdir_mode_v3(options)) { log_info(LD_CONFIG, "Authoritative directories always try to download " "extra-info documents. Setting DownloadExtraInfo."); options->DownloadExtraInfo = 1; @@ -4003,7 +4007,7 @@ options_validate(or_options_t *old_options, or_options_t *options, COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours."); } - if (rend_config_services(options, 1) < 0) + if (hs_config_service_all(options, 1) < 0) REJECT("Failed to configure rendezvous options. See logs for details."); /* Parse client-side authorization for hidden services. */ @@ -6252,8 +6256,9 @@ port_cfg_free(port_cfg_t *port) /** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is * on a publicly routable address. */ static void -warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, - int listener_type) +warn_nonlocal_client_ports(const smartlist_t *ports, + const char *portname, + const int listener_type) { SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { if (port->type != listener_type) @@ -6938,7 +6943,8 @@ parse_ports(or_options_t *options, int validate_only, options->SocksPort_lines, "Socks", CONN_TYPE_AP_LISTENER, "127.0.0.1", 9050, - CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) { + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { *msg = tor_strdup("Invalid SocksPort configuration"); goto err; } diff --git a/src/or/config.h b/src/or/config.h index 27aec7fe3d..3cfa7c4e5b 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -18,6 +18,10 @@ #define KERNEL_MAY_SUPPORT_IPFW #endif +/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might + * expose more information than we're comfortable with. */ +#define MIN_HEARTBEAT_PERIOD (30*60) + MOCK_DECL(const char*, get_dirportfrontpage, (void)); MOCK_DECL(const or_options_t *, get_options, (void)); MOCK_DECL(or_options_t *, get_options_mutable, (void)); diff --git a/src/or/connection.c b/src/or/connection.c index 4e890497e9..5c65e886c0 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -84,6 +84,7 @@ #include "geoip.h" #include "main.h" #include "hs_common.h" +#include "hs_ident.h" #include "nodelist.h" #include "policies.h" #include "reasons.h" @@ -605,6 +606,7 @@ connection_free_(connection_t *conn) } if (CONN_IS_EDGE(conn)) { rend_data_free(TO_EDGE_CONN(conn)->rend_data); + hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident); } if (conn->type == CONN_TYPE_CONTROL) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); @@ -636,6 +638,7 @@ connection_free_(connection_t *conn) } rend_data_free(dir_conn->rend_data); + hs_ident_dir_conn_free(dir_conn->hs_ident); if (dir_conn->guard_state) { /* Cancel before freeing, if it's still there. */ entry_guard_cancel(&dir_conn->guard_state); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8480a35458..ddcff6aa94 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -2455,8 +2455,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) * * If ap_conn is broken, mark it for close and return -1. Else return 0. */ -int -connection_ap_handshake_send_begin(entry_connection_t *ap_conn) +MOCK_IMPL(int, +connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)) { char payload[CELL_PAYLOAD_SIZE]; int payload_len; @@ -3007,7 +3007,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, * <0 and set *<b>end_reason_out</b> to the end reason we should send back to * the client. * - * Return -1 in the case where want to send a RELAY_END cell, and < -1 when + * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when * we don't. **/ STATIC int @@ -3566,8 +3566,12 @@ int connection_edge_is_rendezvous_stream(const edge_connection_t *conn) { tor_assert(conn); - if (conn->rend_data) + /* It should not be possible to set both of these structs */ + tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident)); + + if (conn->rend_data || conn->hs_ident) { return 1; + } return 0; } diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index e4780b3c7d..9987f88b85 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn); void connection_ap_about_to_close(entry_connection_t *edge_conn); void connection_exit_about_to_close(edge_connection_t *edge_conn); -int connection_ap_handshake_send_begin(entry_connection_t *ap_conn); +MOCK_DECL(int, + connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)); int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn); entry_connection_t *connection_ap_make_link(connection_t *partner, diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 753148291c..051bf9a176 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1369,7 +1369,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving)) connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT, conn->base_.s); - note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); if (connection_tls_continue_handshake(conn) < 0) return -1; diff --git a/src/or/control.c b/src/or/control.c index 9bcf1ee364..724d4b35c0 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -60,6 +60,7 @@ #include "hibernate.h" #include "hs_common.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" #include "policies.h" @@ -802,7 +803,7 @@ queued_events_flush_all(int force) } /** Libevent callback: Flushes pending events to controllers that are - * interested in them */ + * interested in them. */ static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg) { @@ -1892,6 +1893,12 @@ getinfo_helper_dir(control_connection_t *control_conn, const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/id/* " + "instead of desc/id/*."; + return 0; } } else if (!strcmpstart(question, "desc/name/")) { const routerinfo_t *ri = NULL; @@ -1905,7 +1912,16 @@ getinfo_helper_dir(control_connection_t *control_conn, const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/name/* " + "instead of desc/name/*."; + return 0; } + } else if (!strcmp(question, "desc/download-enabled")) { + int r = we_fetch_router_descriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmp(question, "desc/all-recent")) { routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_new(); @@ -1991,6 +2007,9 @@ getinfo_helper_dir(control_connection_t *control_conn, if (md && md->body) { *answer = tor_strndup(md->body, md->bodylen); } + } else if (!strcmp(question, "md/download-enabled")) { + int r = we_fetch_microdescriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmpstart(question, "desc-annotations/id/")) { const routerinfo_t *ri = NULL; const node_t *node = @@ -2907,7 +2926,8 @@ getinfo_helper_sr(control_connection_t *control_conn, * *<b>a</b>. If an internal error occurs, return -1 and optionally set * *<b>error_out</b> to point to an error message to be delivered to the * controller. On success, _or if the key is not recognized_, return 0. Do not - * set <b>a</b> if the key is not recognized. + * set <b>a</b> if the key is not recognized but you may set <b>error_out</b> + * to improve the error message. */ typedef int (*getinfo_helper_t)(control_connection_t *, const char *q, char **a, @@ -3012,9 +3032,13 @@ static const getinfo_item_t getinfo_items[] = { PREFIX("desc/name/", dir, "Router descriptors by nickname."), ITEM("desc/all-recent", dir, "All non-expired, non-superseded router descriptors."), + ITEM("desc/download-enabled", dir, + "Do we try to download router descriptors?"), ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */ PREFIX("md/id/", dir, "Microdescriptors by ID"), PREFIX("md/name/", dir, "Microdescriptors by name"), + ITEM("md/download-enabled", dir, + "Do we try to download microdescriptors?"), PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), PREFIX("hs/client/desc/id", dir, "Hidden Service descriptor in client's cache by onion."), @@ -3162,7 +3186,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_t *questions = smartlist_new(); smartlist_t *answers = smartlist_new(); smartlist_t *unrecognized = smartlist_new(); - char *msg = NULL, *ans = NULL; + char *ans = NULL; int i; (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ @@ -3177,20 +3201,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, goto done; } if (!ans) { - smartlist_add(unrecognized, (char*)q); + if (errmsg) /* use provided error message */ + smartlist_add_strdup(unrecognized, errmsg); + else /* use default error message */ + smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q); } else { smartlist_add_strdup(answers, q); smartlist_add(answers, ans); } } SMARTLIST_FOREACH_END(q); + if (smartlist_len(unrecognized)) { + /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */ for (i=0; i < smartlist_len(unrecognized)-1; ++i) connection_printf_to_buf(conn, - "552-Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552-%s\r\n", + (char *)smartlist_get(unrecognized, i)); + connection_printf_to_buf(conn, - "552 Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552 %s\r\n", + (char *)smartlist_get(unrecognized, i)); goto done; } @@ -3217,8 +3247,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_free(answers); SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); smartlist_free(questions); + SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp)); smartlist_free(unrecognized); - tor_free(msg); return 0; } @@ -4882,6 +4912,38 @@ peek_connection_has_control0_command(connection_t *conn) return peek_buf_has_control0_command(conn->inbuf); } +static int +peek_connection_has_http_command(connection_t *conn) +{ + return peek_buf_has_http_command(conn->inbuf); +} + +static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" + "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor's ControlPort is not an HTTP proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor's control port" + " as an HTTP proxy.\n" + "This is not correct: Tor's default SOCKS proxy port is 9050.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -4921,6 +4983,15 @@ connection_control_process_inbuf(control_connection_t *conn) return 0; } + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); + return 0; + } + again: while (1) { size_t last_idx; diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index f5fff2b331..d9371b3446 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -382,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_) if (onionskin_answer(circ, &rpl.created_cell, - (const char*)rpl.keys, + (const char*)rpl.keys, sizeof(rpl.keys), rpl.rend_auth_material) < 0) { log_warn(LD_OR,"onionskin_answer failed. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); @@ -484,7 +484,7 @@ queue_pending_tasks(void) if (!circ) return; - if (assign_onionskin_to_cpuworker(circ, onionskin)) + if (assign_onionskin_to_cpuworker(circ, onionskin) < 0) log_info(LD_OR,"assign_to_cpuworker failed. Ignoring."); } } diff --git a/src/or/directory.c b/src/or/directory.c index 45fbd1dd33..13daea354c 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -18,7 +18,6 @@ #include "consdiffmgr.h" #include "control.h" #include "compat.h" -#define DIRECTORY_PRIVATE #include "directory.h" #include "dirserv.h" #include "dirvote.h" @@ -4918,7 +4917,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, goto done; } - if (authdir_mode_handles_descs(options, -1) && + if (authdir_mode(options) && !strcmp(url,"/tor/")) { /* server descriptor post */ const char *msg = "[None]"; uint8_t purpose = authdir_mode_bridge(options) ? @@ -5124,7 +5123,7 @@ connection_dir_finished_connecting(dir_connection_t *conn) * Helper function for download_status_increment_failure(), * download_status_reset(), and download_status_increment_attempt(). */ STATIC const smartlist_t * -find_dl_schedule(download_status_t *dls, const or_options_t *options) +find_dl_schedule(const download_status_t *dls, const or_options_t *options) { const int dir_server = dir_server_mode(options); const int multi_d = networkstatus_consensus_can_use_multiple_directories( @@ -5193,6 +5192,8 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, const smartlist_t *schedule = find_dl_schedule(dls, options); tor_assert(schedule != NULL && smartlist_len(schedule) >= 2); *min = *((int *)(smartlist_get(schedule, 0))); + /* Increment on failure schedules always use exponential backoff, but they + * have a smaller limit when they're deterministic */ if (dls->backoff == DL_SCHED_DETERMINISTIC) *max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1)))); else @@ -5201,8 +5202,9 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, /** Advance one delay step. The algorithm is to use the previous delay to * compute an increment, we construct a value uniformly at random between - * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger - * than max_delay, and return it. + * delay+1 and (delay*(DIR_DEFAULT_RANDOM_MULTIPLIER+1))+1 (or + * DIR_TEST_NET_RANDOM_MULTIPLIER in test networks). + * We then clamp that value to be no larger than max_delay, and return it. * * Requires that delay is less than INT_MAX, and delay is in [0,max_delay]. */ @@ -5221,11 +5223,11 @@ next_random_exponential_delay(int delay, int max_delay) /* How much are we willing to add to the delay? */ int max_increment; - int multiplier = 3; /* no more than quadruple the previous delay */ + int multiplier = DIR_DEFAULT_RANDOM_MULTIPLIER; if (get_options()->TestingTorNetwork) { /* Decrease the multiplier in testing networks. This reduces the variance, * so that bootstrap is more reliable. */ - multiplier = 2; /* no more than triple the previous delay */ + multiplier = DIR_TEST_NET_RANDOM_MULTIPLIER; } if (delay && delay < (INT_MAX-1) / multiplier) { @@ -5377,6 +5379,11 @@ download_status_increment_failure(download_status_t *dls, int status_code, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + /* count the failure */ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) { ++dls->n_download_failures; @@ -5401,14 +5408,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, download_status_log_helper(item, !dls->increment_on, "failed", "concurrently", dls->n_download_failures, - increment, dls->next_attempt_at, now); + increment, + download_status_get_next_attempt_at(dls), + now); if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { /* stop this schedule retrying on failure, it will launch concurrent * connections instead */ return TIME_MAX; } else { - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); } } @@ -5429,6 +5438,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { /* this schedule should retry on failure, and not launch any concurrent attempts */ @@ -5447,9 +5461,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item, download_status_log_helper(item, dls->increment_on, "attempted", "on failure", dls->n_download_attempts, - delay, dls->next_attempt_at, now); + delay, download_status_get_next_attempt_at(dls), + now); - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); +} + +static time_t +download_status_get_initial_delay_from_now(const download_status_t *dls) +{ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + /* We use constant initial delays, even in exponential backoff + * schedules. */ + return time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Reset <b>dls</b> so that it will be considered downloadable @@ -5470,11 +5494,9 @@ download_status_reset(download_status_t *dls) || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule(dls, get_options()); - dls->n_download_failures = 0; dls->n_download_attempts = 0; - dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + dls->next_attempt_at = download_status_get_initial_delay_from_now(dls); dls->last_backoff_position = 0; dls->last_delay_used = 0; /* Don't reset dls->want_authority or dls->increment_on */ @@ -5501,6 +5523,12 @@ download_status_get_n_attempts(const download_status_t *dls) time_t download_status_get_next_attempt_at(const download_status_t *dls) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + /* so give the answer we would have given if it had been */ + return download_status_get_initial_delay_from_now(dls); + } + return dls->next_attempt_at; } diff --git a/src/or/directory.h b/src/or/directory.h index 14d5ae9ef4..3e574cc6ac 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -123,12 +123,19 @@ time_t download_status_increment_attempt(download_status_t *dls, void download_status_reset(download_status_t *dls); static int download_status_is_ready(download_status_t *dls, time_t now, int max_failures); +time_t download_status_get_next_attempt_at(const download_status_t *dls); + /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is * ready to get its download reattempted. */ static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->backoff == DL_SCHED_DETERMINISTIC) { /* Deterministic schedules can hit an endpoint; exponential backoff * schedules just wait longer and longer. */ @@ -137,7 +144,7 @@ download_status_is_ready(download_status_t *dls, time_t now, if (!under_failure_limit) return 0; } - return dls->next_attempt_at <= now; + return download_status_get_next_attempt_at(dls) <= now; } static void download_status_mark_impossible(download_status_t *dl); @@ -151,7 +158,6 @@ download_status_mark_impossible(download_status_t *dl) int download_status_get_n_failures(const download_status_t *dls); int download_status_get_n_attempts(const download_status_t *dls); -time_t download_status_get_next_attempt_at(const download_status_t *dls); int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); @@ -193,7 +199,7 @@ STATIC char* authdir_type_to_string(dirinfo_type_t auth); STATIC const char * dir_conn_purpose_to_string(int purpose); STATIC int should_use_directory_guards(const or_options_t *options); STATIC compression_level_t choose_compression_level(ssize_t n_bytes); -STATIC const smartlist_t *find_dl_schedule(download_status_t *dls, +STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls, const or_options_t *options); STATIC void find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, @@ -206,5 +212,15 @@ STATIC int parse_hs_version_from_post(const char *url, const char *prefix, STATIC unsigned parse_accept_encoding_header(const char *h); #endif +#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) +/* Used only by directory.c and test_dir.c */ + +/* no more than quadruple the previous delay (multiplier + 1) */ +#define DIR_DEFAULT_RANDOM_MULTIPLIER (3) +/* no more than triple the previous delay */ +#define DIR_TEST_NET_RANDOM_MULTIPLIER (2) + +#endif + #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 468fdbd488..e5654e3b90 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -704,10 +704,22 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) /* Do keypinning again ... this time, to add the pin if appropriate */ int keypin_status; if (ri->cache_info.signing_key_cert) { + ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key; + /* First let's validate this pubkey before pinning it */ + if (ed25519_validate_pubkey(pkey) < 0) { + log_warn(LD_DIRSERV, "Received bad key from %s (source %s)", + router_describe(ri), source); + control_event_or_authdir_new_descriptor("REJECTED", + ri->cache_info.signed_descriptor_body, + desclen, *msg); + routerinfo_free(ri); + return ROUTER_AUTHDIR_REJECTS; + } + + /* Now pin it! */ keypin_status = keypin_check_and_add( (const uint8_t*)ri->cache_info.identity_digest, - ri->cache_info.signing_key_cert->signing_key.pubkey, - ! key_pinning); + pkey->pubkey, ! key_pinning); } else { keypin_status = keypin_check_lone_rsa( (const uint8_t*)ri->cache_info.identity_digest); diff --git a/src/or/dirvote.c b/src/or/dirvote.c index f5e29eb786..c65945fea7 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -306,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, signing_key_fingerprint); } - note_crypto_pk_op(SIGN_DIR); { char *sig = router_get_dirobj_signature(digest, DIGEST_LEN, private_signing_key); @@ -737,12 +736,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list, } } SMARTLIST_FOREACH_END(k_v_pair); - if (n_found == 1) + if (n_found == 1) { return value; - else if (BUG(n_found > 1)) - return default_val; - else + } else { + tor_assert_nonfatal(n_found == 0); return default_val; + } } /** Minimum number of directory authorities voting for a parameter to diff --git a/src/or/dns.c b/src/or/dns.c index 98b684c904..cc062e30ef 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -182,6 +182,18 @@ evdns_log_cb(int warn, const char *msg) } else if (!strcmp(msg, "All nameservers have failed")) { control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN"); all_down = 1; + } else if (!strcmpstart(msg, "Address mismatch on received DNS")) { + static ratelim_t mismatch_limit = RATELIM_INIT(3600); + const char *src = strstr(msg, " Apparent source"); + if (!src || get_options()->SafeLogging) { + src = ""; + } + log_fn_ratelim(&mismatch_limit, severity, LD_EXIT, + "eventdns: Received a DNS packet from " + "an IP address to which we did not send a request. This " + "could be a DNS spoofing attempt, or some kind of " + "misconfiguration.%s", src); + return; } tor_log(severity, LD_EXIT, "eventdns: %s", msg); } @@ -1928,7 +1940,7 @@ dns_launch_wildcard_checks(void) launch_wildcard_check(8, 16, ipv6, ".com"); launch_wildcard_check(8, 16, ipv6, ".org"); launch_wildcard_check(8, 16, ipv6, ".net"); - } + } } } diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index fa768fc4a6..739ec82484 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -813,7 +813,7 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", + log_info(LD_GUARD, "Adding %s to the entry guard sample set.", node_describe(node)); /* make sure that the guard is not already sampled. */ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c new file mode 100644 index 0000000000..2f595d72e5 --- /dev/null +++ b/src/or/hs_circuit.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.c + **/ + +#include "or.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" + +#include "hs_circuit.h" +#include "hs_ident.h" +#include "hs_ntor.h" + +/* A circuit is about to become an e2e rendezvous circuit. Check + * <b>circ_purpose</b> and ensure that it's properly set. Return true iff + * circuit purpose is properly set, otherwise return false. */ +static int +circuit_purpose_is_correct_for_rend(unsigned int circ_purpose, + int is_service_side) +{ + if (is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) { + log_warn(LD_BUG, + "HS e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + if (!is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY && + circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_BUG, + "Client e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + return 1; +} + +/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous + * circuit. Initialize the crypt path crypto using the output material from the + * ntor key exchange at <b>ntor_key_seed</b>. + * + * If <b>is_service_side</b> is set, we are the hidden service and the final + * hop of the rendezvous circuit is the client on the other side. */ +static crypt_path_t * +create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN]; + crypt_path_t *cpath = NULL; + + /* Do the key expansion */ + if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len, + keys, sizeof(keys)) < 0) { + goto err; + } + + /* Setup the cpath */ + cpath = tor_malloc_zero(sizeof(crypt_path_t)); + cpath->magic = CRYPT_PATH_MAGIC; + + if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys), + is_service_side, 1) < 0) { + tor_free(cpath); + goto err; + } + + err: + memwipe(keys, 0, sizeof(keys)); + return cpath; +} + +/* We are a v2 legacy HS client: Create and return a crypt path for the hidden + * service on the other side of the rendezvous circuit <b>circ</b>. Initialize + * the crypt path crypto using the body of the RENDEZVOUS1 cell at + * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes). + */ +static crypt_path_t * +create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) +{ + crypt_path_t *hop = NULL; + char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; + + /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh + * handshake...*/ + tor_assert(circ->build_state); + tor_assert(circ->build_state->pending_final_cpath); + hop = circ->build_state->pending_final_cpath; + + tor_assert(hop->rend_dh_handshake_state); + if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state, + (char*)rend_cell_body, DH_KEY_LEN, + keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { + log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + goto err; + } + /* ... and set up cpath. */ + if (circuit_init_cpath_crypto(hop, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 0, 0) < 0) + goto err; + + /* Check whether the digest is right... */ + if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) { + log_warn(LD_PROTOCOL, "Incorrect digest of key material."); + goto err; + } + + /* clean up the crypto stuff we just made */ + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; + + goto done; + + err: + hop = NULL; + + done: + memwipe(keys, 0, sizeof(keys)); + return hop; +} + +/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark + * <b>circ</b> ready for use to transfer HS relay cells. */ +static void +finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, + int is_service_side) +{ + tor_assert(circ); + tor_assert(hop); + + /* Notify the circuit state machine that we are splicing this circuit */ + int new_circ_purpose = is_service_side ? + CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED; + circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose); + + /* All is well. Extend the circuit. */ + hop->state = CPATH_STATE_OPEN; + /* Set the windows to default. */ + hop->package_window = circuit_initial_package_window(); + hop->deliver_window = CIRCWINDOW_START; + + /* Now that this circuit has finished connecting to its destination, + * make sure circuit_get_open_circ_or_launch is willing to return it + * so we can actually use it. */ + circ->hs_circ_has_timed_out = 0; + + /* Append the hop to the cpath of this circuit */ + onion_append_to_cpath(&circ->cpath, hop); + + /* In legacy code, 'pending_final_cpath' points to the final hop we just + * appended to the cpath. We set the original pointer to NULL so that we + * don't double free it. */ + if (circ->build_state) { + circ->build_state->pending_final_cpath = NULL; + } + + /* Finally, mark circuit as ready to be used for client streams */ + if (!is_service_side) { + circuit_try_attaching_streams(circ); + } +} + +/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key + * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to + * serve as a rendezvous end-to-end circuit between the client and the + * service. If <b>is_service_side</b> is set, then we are the hidden service + * and the other side is the client. + * + * Return 0 if the operation went well; in case of error return -1. */ +int +hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose, + is_service_side))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len, + is_service_side); + if (!hop) { + log_warn(LD_REND, "Couldn't get v3 %s cpath!", + is_service_side ? "service-side" : "client-side"); + return -1; + } + + finalize_rend_circuit(circ, hop, is_service_side); + + return 0; +} + +/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell + * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then + * extend the crypt path of <b>circ</b> so that the hidden service is on the + * other side. */ +int +hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body) +{ + + if (BUG(!circuit_purpose_is_correct_for_rend( + TO_CIRCUIT(circ)->purpose, 0))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body); + if (!hop) { + log_warn(LD_GENERAL, "Couldn't get v2 cpath."); + return -1; + } + + finalize_rend_circuit(circ, hop, 0); + + return 0; +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h new file mode 100644 index 0000000000..71ce5c3331 --- /dev/null +++ b/src/or/hs_circuit.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.h + * \brief Header file containing circuit data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CIRCUIT_H +#define TOR_HS_CIRCUIT_H + +#include "or.h" + +/* e2e circuit API. */ + +int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, + size_t seed_len, + int is_service_side); +int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body); + +#endif /* TOR_HS_CIRCUIT_H */ + diff --git a/src/or/hs_client.c b/src/or/hs_client.c new file mode 100644 index 0000000000..051490aaaf --- /dev/null +++ b/src/or/hs_client.c @@ -0,0 +1,48 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_service.c + * \brief Implement next generation hidden service client functionality + **/ + +#include "or.h" +#include "hs_circuit.h" +#include "hs_ident.h" +#include "connection_edge.h" +#include "rendclient.h" + +#include "hs_client.h" + +/** A prop224 v3 HS circuit successfully connected to the hidden + * service. Update the stream state at <b>hs_conn_ident</b> appropriately. */ +static void +hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) +{ + (void) hs_conn_ident; + + /* TODO: When implementing client side */ + return; +} + +/** A circuit just finished connecting to a hidden service that the stream + * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */ +void +hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) +{ + tor_assert(connection_edge_is_rendezvous_stream(conn)); + + if (BUG(conn->rend_data && conn->hs_ident)) { + log_warn(LD_BUG, "Stream had both rend_data and hs_ident..." + "Prioritizing hs_ident"); + } + + if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */ + hs_client_attempt_succeeded(conn->hs_ident); + return; + } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */ + rend_client_note_connection_attempt_ended(conn->rend_data); + return; + } +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h new file mode 100644 index 0000000000..4f28937b03 --- /dev/null +++ b/src/or/hs_client.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.h + * \brief Header file containing client data for the HS subsytem. + **/ + +#ifndef TOR_HS_CLIENT_H +#define TOR_HS_CLIENT_H + +void hs_client_note_connection_attempt_succeeded( + const edge_connection_t *conn); + +#endif /* TOR_HS_CLIENT_H */ + diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 42508126f8..27330bfcdb 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -15,9 +15,25 @@ #include "config.h" #include "networkstatus.h" +#include "hs_cache.h" #include "hs_common.h" +#include "hs_service.h" #include "rendcommon.h" +/* Allocate and return a string containing the path to filename in directory. + * This function will never return NULL. The caller must free this path. */ +char * +hs_path_from_filename(const char *directory, const char *filename) +{ + char *file_path = NULL; + + tor_assert(directory); + tor_assert(filename); + + tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename); + return file_path; +} + /* Make sure that the directory for <b>service</b> is private, using the config * <b>username</b>. * If <b>create</b> is true: @@ -344,3 +360,204 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* Using an ed25519 public key and version to build the checksum of an + * address. Put in checksum_out. Format is: + * SHA3-256(".onion checksum" || PUBKEY || VERSION) + * + * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */ +static void +build_hs_checksum(const ed25519_public_key_t *key, uint8_t version, + uint8_t *checksum_out) +{ + size_t offset = 0; + char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN]; + + /* Build checksum data. */ + memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX, + HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN); + offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN; + memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + set_uint8(data + offset, version); + offset += sizeof(version); + tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN); + + /* Hash the data payload to create the checksum. */ + crypto_digest256((char *) checksum_out, data, sizeof(data), + DIGEST_SHA3_256); +} + +/* Using an ed25519 public key, checksum and version to build the binary + * representation of a service address. Put in addr_out. Format is: + * addr_out = PUBKEY || CHECKSUM || VERSION + * + * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */ +static void +build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum, + uint8_t version, char *addr_out) +{ + size_t offset = 0; + + tor_assert(key); + tor_assert(checksum); + + memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + set_uint8(addr_out + offset, version); + offset += sizeof(uint8_t); + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Helper for hs_parse_address(): Using a binary representation of a service + * address, parse its content into the key_out, checksum_out and version_out. + * Any out variable can be NULL in case the caller would want only one field. + * checksum_out MUST at least be 2 bytes long. address must be at least + * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */ +static void +hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + size_t offset = 0; + + tor_assert(address); + + if (key_out) { + /* First is the key. */ + memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN); + } + offset += ED25519_PUBKEY_LEN; + if (checksum_out) { + /* Followed by a 2 bytes checksum. */ + memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + } + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + if (version_out) { + /* Finally, version value is 1 byte. */ + *version_out = get_uint8(address + offset); + } + offset += sizeof(uint8_t); + /* Extra safety. */ + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Using a base32 representation of a service address, parse its content into + * the key_out, checksum_out and version_out. Any out variable can be NULL in + * case the caller would want only one field. checksum_out MUST at least be 2 + * bytes long. + * + * Return 0 if parsing went well; return -1 in case of error. */ +int +hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + char decoded[HS_SERVICE_ADDR_LEN]; + + tor_assert(address); + + /* Obvious length check. */ + if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { + log_warn(LD_REND, "Service address %s has an invalid length. " + "Expected %lu but got %lu.", + escaped_safe_str(address), + (unsigned long) HS_SERVICE_ADDR_LEN_BASE32, + (unsigned long) strlen(address)); + goto invalid; + } + + /* Decode address so we can extract needed fields. */ + if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) { + log_warn(LD_REND, "Service address %s can't be decoded.", + escaped_safe_str(address)); + goto invalid; + } + + /* Parse the decoded address into the fields we need. */ + hs_parse_address_impl(decoded, key_out, checksum_out, version_out); + + return 0; + invalid: + return -1; +} + +/* Validate a given onion address. The length, the base32 decoding and + * checksum are validated. Return 1 if valid else 0. */ +int +hs_address_is_valid(const char *address) +{ + uint8_t version; + uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED]; + uint8_t target_checksum[DIGEST256_LEN]; + ed25519_public_key_t key; + + /* Parse the decoded address into the fields we need. */ + if (hs_parse_address(address, &key, checksum, &version) < 0) { + goto invalid; + } + + /* Get the checksum it's suppose to be and compare it with what we have + * encoded in the address. */ + build_hs_checksum(&key, version, target_checksum); + if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) { + log_warn(LD_REND, "Service address %s invalid checksum.", + escaped_safe_str(address)); + goto invalid; + } + + /* Valid address. */ + return 1; + invalid: + return 0; +} + +/* Build a service address using an ed25519 public key and a given version. + * The returned address is base32 encoded and put in addr_out. The caller MUST + * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long. + * + * Format is as follow: + * base32(PUBKEY || CHECKSUM || VERSION) + * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION) + * */ +void +hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out) +{ + uint8_t checksum[DIGEST256_LEN]; + char address[HS_SERVICE_ADDR_LEN]; + + tor_assert(key); + tor_assert(addr_out); + + /* Get the checksum of the address. */ + build_hs_checksum(key, version, checksum); + /* Get the binary address representation. */ + build_hs_address(key, checksum, version, address); + + /* Encode the address. addr_out will be NUL terminated after this. */ + base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address, + sizeof(address)); + /* Validate what we just built. */ + tor_assert(hs_address_is_valid(addr_out)); +} + +/* Initialize the entire HS subsytem. This is called in tor_init() before any + * torrc options are loaded. Only for >= v3. */ +void +hs_init(void) +{ + hs_circuitmap_init(); + hs_service_init(); + hs_cache_init(); +} + +/* Release and cleanup all memory of the HS subsystem (all version). This is + * called by tor_free_all(). */ +void +hs_free_all(void) +{ + hs_circuitmap_free_all(); + hs_service_free_all(); + hs_cache_free_all(); +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a8fded652a..203a5d0818 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -16,10 +16,13 @@ #define HS_VERSION_TWO 2 /* Version 3 of the protocol (prop224). */ #define HS_VERSION_THREE 3 +/* Earliest and latest version we support. */ +#define HS_VERSION_MIN HS_VERSION_TWO +#define HS_VERSION_MAX HS_VERSION_THREE /** Try to maintain this many intro points per service by default. */ #define NUM_INTRO_POINTS_DEFAULT 3 -/** Maximum number of intro points per service. */ +/** Maximum number of intro points per generic and version 2 service. */ #define NUM_INTRO_POINTS_MAX 10 /** Number of extra intro points we launch if our set of intro nodes is empty. * See proposal 155, section 4. */ @@ -49,9 +52,48 @@ /* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ +/* Prefix of the onion address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" +/* Length of the checksum prefix minus the NUL terminated byte. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \ + (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1) +/* Length of the resulting checksum of the address. The construction of this + * checksum looks like: + * CHECKSUM = ".onion checksum" || PUBKEY || VERSION + * where VERSION is 1 byte. This is pre-hashing. */ +#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \ + (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t)) +/* The amount of bytes we use from the address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2 +/* Length of the binary encoded service address which is of course before the + * base32 encoding. Construction is: + * PUBKEY || CHECKSUM || VERSION + * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */ +#define HS_SERVICE_ADDR_LEN \ + (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t)) +/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the + * length ends up to 56 bytes (not counting the terminated NUL byte.) */ +#define HS_SERVICE_ADDR_LEN_BASE32 \ + (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) + +/* Type of authentication key used by an introduction point. */ +typedef enum { + HS_AUTH_KEY_TYPE_LEGACY = 1, + HS_AUTH_KEY_TYPE_ED25519 = 2, +} hs_auth_key_type_t; + +void hs_init(void); +void hs_free_all(void); + int hs_check_service_private_dir(const char *username, const char *path, unsigned int dir_group_readable, unsigned int create); +char *hs_path_from_filename(const char *directory, const char *filename); +void hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out); +int hs_address_is_valid(const char *address); +int hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out); void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); diff --git a/src/or/hs_config.c b/src/or/hs_config.c new file mode 100644 index 0000000000..5f9282ea79 --- /dev/null +++ b/src/or/hs_config.c @@ -0,0 +1,582 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.c + * \brief Implement hidden service configuration subsystem. + * + * \details + * + * This file has basically one main entry point: hs_config_service_all(). It + * takes the torrc options and configure hidden service from it. In validate + * mode, nothing is added to the global service list or keys are not generated + * nor loaded. + * + * A service is configured in two steps. It is first created using the tor + * options and then put in a staging list. It will stay there until + * hs_service_load_all_keys() is called. That function is responsible to + * load/generate the keys for the service in the staging list and if + * successful, transfert the service to the main global service list where + * at that point it is ready to be used. + * + * Configuration functions are per-version and there is a main generic one for + * every option that is common to all version (config_generic_service). + **/ + +#define HS_CONFIG_PRIVATE + +#include "hs_common.h" +#include "hs_config.h" +#include "hs_service.h" +#include "rendservice.h" + +/* Using the given list of services, stage them into our global state. Every + * service version are handled. This function can remove entries in the given + * service_list. + * + * Staging a service means that we take all services in service_list and we + * put them in the staging list (global) which acts as a temporary list that + * is used by the service loading key process. In other words, staging a + * service puts it in a list to be considered when loading the keys and then + * moved to the main global list. */ +static void +stage_services(smartlist_t *service_list) +{ + tor_assert(service_list); + + /* This is v2 specific. Trigger service pruning which will make sure the + * just configured services end up in the main global list. It should only + * be done in non validation mode because v2 subsystem handles service + * object differently. */ + rend_service_prune_list(); + + /* Cleanup v2 service from the list, we don't need those object anymore + * because we validated them all against the others and we want to stage + * only >= v3 service. And remember, v2 has a different object type which is + * shadow copied from an hs_service_t type. */ + SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) { + if (s->config.version == HS_VERSION_TWO) { + SMARTLIST_DEL_CURRENT(service_list, s); + hs_service_free(s); + } + } SMARTLIST_FOREACH_END(s); + + /* This is >= v3 specific. Using the newly configured service list, stage + * them into our global state. Every object ownership is lost after. */ + hs_service_stage_services(service_list); +} + +/* Validate the given service against all service in the given list. If the + * service is ephemeral, this function ignores it. Services with the same + * directory path aren't allowed and will return an error. If a duplicate is + * found, 1 is returned else 0 if none found. */ +static int +service_is_duplicate_in_list(const smartlist_t *service_list, + const hs_service_t *service) +{ + int ret = 0; + + tor_assert(service_list); + tor_assert(service); + + /* Ephemeral service don't have a directory configured so no need to check + * for a service in the list having the same path. */ + if (service->config.is_ephemeral) { + goto end; + } + + /* XXX: Validate if we have any service that has the given service dir path. + * This has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. */ + SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) { + if (!strcmp(s->config.directory_path, service->config.directory_path)) { + log_warn(LD_REND, "Another hidden service is already configured " + "for directory %s", + escaped(service->config.directory_path)); + ret = 1; + goto end; + } + } SMARTLIST_FOREACH_END(s); + + end: + return ret; +} + +/* Helper function: Given an configuration option name, its value, a minimum + * min and a maxium max, parse the value as a uint64_t. On success, ok is set + * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be + * ignored. This function logs both on error and success. */ +static uint64_t +helper_parse_uint64(const char *opt, const char *value, uint64_t min, + uint64_t max, int *ok) +{ + uint64_t ret = 0; + + tor_assert(opt); + tor_assert(value); + tor_assert(ok); + + *ok = 0; + ret = tor_parse_uint64(value, 10, min, max, ok, NULL); + if (!*ok) { + log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64 + ", not %s.", + opt, min, max, value); + goto err; + } + log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret); + err: + return ret; +} + +/* Return true iff the given options starting at line_ for a hidden service + * contains at least one invalid option. Each hidden service option don't + * apply to all versions so this function can find out. The line_ MUST start + * right after the HiddenServiceDir line of this service. + * + * This is mainly for usability so we can inform the user of any invalid + * option for the hidden service version instead of silently ignoring. */ +static int +config_has_invalid_options(const config_line_t *line_, + const hs_service_t *service) +{ + int ret = 0; + const char **optlist; + const config_line_t *line; + + tor_assert(service); + tor_assert(service->config.version <= HS_VERSION_MAX); + + /* List of options that a v3 service doesn't support thus must exclude from + * its configuration. */ + const char *opts_exclude_v3[] = { + "HiddenServiceAuthorizeClient", + NULL /* End marker. */ + }; + + /* Defining the size explicitly allows us to take advantage of the compiler + * which warns us if we ever bump the max version but forget to grow this + * array. The plus one is because we have a version 0 :). */ + struct { + const char **list; + } exclude_lists[HS_VERSION_MAX + 1] = { + { NULL }, /* v0. */ + { NULL }, /* v1. */ + { NULL }, /* v2 */ + { opts_exclude_v3 }, /* v3. */ + }; + + optlist = exclude_lists[service->config.version].list; + if (optlist == NULL) { + /* No exclude options to look at for this version. */ + goto end; + } + for (int i = 0; optlist[i]; i++) { + const char *opt = optlist[i]; + for (line = line_; line; line = line->next) { + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + goto end; + } + if (!strcasecmp(line->key, opt)) { + log_warn(LD_CONFIG, "Hidden service option %s is incompatible with " + "version %" PRIu32 " of service in %s", + opt, service->config.version, + service->config.directory_path); + ret = 1; + /* Continue the loop so we can find all possible options. */ + continue; + } + } + } + end: + return ret; +} + +/* Validate service configuration. This is used when loading the configuration + * and once we've setup a service object, it's config object is passed to this + * function for further validation. This does not validate service key + * material. Return 0 if valid else -1 if invalid. */ +static int +config_validate_service(const hs_service_config_t *config) +{ + tor_assert(config); + + /* Amount of ports validation. */ + if (!config->ports || smartlist_len(config->ports) == 0) { + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", + escaped(config->directory_path)); + goto invalid; + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/* Configuration funcion for a version 3 service. The line_ must be pointing + * to the directive directly after a HiddenServiceDir. That way, when hitting + * the next HiddenServiceDir line or reaching the end of the list of lines, we + * know that we have to stop looking for more options. The given service + * object must be already allocated and passed through + * config_generic_service() prior to calling this function. + * + * Return 0 on success else a negative value. */ +static int +config_service_v3(const config_line_t *line_, + hs_service_config_t *config) +{ + int have_num_ip = 0; + const char *dup_opt_seen = NULL; + const config_line_t *line; + + tor_assert(config); + + for (line = line_; line; line = line->next) { + int ok = 0; + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + break; + } + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + config->num_intro_points = + (unsigned int) helper_parse_uint64(line->key, line->value, + NUM_INTRO_POINTS_DEFAULT, + HS_CONFIG_V3_MAX_INTRO_POINTS, + &ok); + if (!ok || have_num_ip) { + if (have_num_ip) + dup_opt_seen = line->key; + goto err; + } + have_num_ip = 1; + continue; + } + } + + /* We do not load the key material for the service at this stage. This is + * done later once tor can confirm that it is in a running state. */ + + /* We are about to return a fully configured service so do one last pass of + * validation at it. */ + if (config_validate_service(config) < 0) { + goto err; + } + + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given options in line_ and options. This is + * called for any service regardless of its version which means that all + * directives in this function are generic to any service version. This + * function will also check the validity of the service directory path. + * + * The line_ must be pointing to the directive directly after a + * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or + * reaching the end of the list of lines, we know that we have to stop looking + * for more options. + * + * Return 0 on success else -1. */ +static int +config_generic_service(const config_line_t *line_, + const or_options_t *options, + hs_service_t *service) +{ + int dir_seen = 0; + const config_line_t *line; + hs_service_config_t *config; + /* If this is set, we've seen a duplicate of this option. Keep the string + * so we can log the directive. */ + const char *dup_opt_seen = NULL; + /* These variables will tell us if we ever have duplicate. */ + int have_version = 0, have_allow_unknown_ports = 0; + int have_dir_group_read = 0, have_max_streams = 0; + int have_max_streams_close = 0; + + tor_assert(line_); + tor_assert(options); + tor_assert(service); + + /* Makes thing easier. */ + config = &service->config; + + /* The first line starts with HiddenServiceDir so we consider what's next is + * the configuration of the service. */ + for (line = line_; line ; line = line->next) { + int ok = 0; + + /* This indicate that we have a new service to configure. */ + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* This function only configures one service at a time so if we've + * already seen one, stop right now. */ + if (dir_seen) { + break; + } + /* Ok, we've seen one and we are about to configure it. */ + dir_seen = 1; + config->directory_path = tor_strdup(line->value); + log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...", + escaped(config->directory_path)); + continue; + } + if (BUG(!dir_seen)) { + goto err; + } + /* Version of the service. */ + if (!strcasecmp(line->key, "HiddenServiceVersion")) { + service->config.version = + (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN, + HS_VERSION_MAX, &ok); + if (!ok || have_version) { + if (have_version) + dup_opt_seen = line->key; + goto err; + } + have_version = 1; + continue; + } + /* Virtual port. */ + if (!strcasecmp(line->key, "HiddenServicePort")) { + char *err_msg = NULL; + /* XXX: Can we rename this? */ + rend_service_port_config_t *portcfg = + rend_service_parse_port_config(line->value, " ", &err_msg); + if (!portcfg) { + if (err_msg) { + log_warn(LD_CONFIG, "%s", err_msg); + } + tor_free(err_msg); + goto err; + } + tor_assert(!err_msg); + smartlist_add(config->ports, portcfg); + log_info(LD_CONFIG, "HiddenServicePort=%s for %s", + line->value, escaped(config->directory_path)); + continue; + } + /* Do we allow unknown ports. */ + if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + config->allow_unknown_ports = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_allow_unknown_ports) { + if (have_allow_unknown_ports) + dup_opt_seen = line->key; + goto err; + } + have_allow_unknown_ports = 1; + continue; + } + /* Directory group readable. */ + if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) { + config->dir_group_readable = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_dir_group_read) { + if (have_dir_group_read) + dup_opt_seen = line->key; + goto err; + } + have_dir_group_read = 1; + continue; + } + /* Maximum streams per circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { + config->max_streams_per_rdv_circuit = + helper_parse_uint64(line->key, line->value, 0, + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok); + if (!ok || have_max_streams) { + if (have_max_streams) + dup_opt_seen = line->key; + goto err; + } + have_max_streams = 1; + continue; + } + /* Maximum amount of streams before we close the circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { + config->max_streams_close_circuit = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_max_streams_close) { + if (have_max_streams_close) + dup_opt_seen = line->key; + goto err; + } + have_max_streams_close = 1; + continue; + } + } + + /* Check if we are configured in non anonymous mode and single hop mode + * meaning every service become single onion. */ + if (rend_service_allow_non_anonymous_connection(options) && + rend_service_non_anonymous_mode_enabled(options)) { + config->is_single_onion = 1; + } + + /* Success */ + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given line and options. This function will + * call the corresponding configuration function for a specific service + * version and validate the service against the other ones. On success, add + * the service to the given list and return 0. On error, nothing is added to + * the list and a negative value is returned. */ +static int +config_service(const config_line_t *line, const or_options_t *options, + smartlist_t *service_list) +{ + int ret; + hs_service_t *service = NULL; + + tor_assert(line); + tor_assert(options); + tor_assert(service_list); + + /* We have a new hidden service. */ + service = hs_service_new(options); + /* We'll configure that service as a generic one and then pass it to a + * specific function according to the configured version number. */ + if (config_generic_service(line, options, service) < 0) { + goto err; + } + tor_assert(service->config.version <= HS_VERSION_MAX); + /* Before we configure the service on a per-version basis, we'll make + * sure that this set of options for a service are valid that is for + * instance an option only for v2 is not used for v3. */ + if (config_has_invalid_options(line->next, service)) { + goto err; + } + /* Check permission on service directory that was just parsed. And this must + * be done regardless of the service version. Do not ask for the directory + * to be created, this is done when the keys are loaded because we could be + * in validation mode right now. */ + if (hs_check_service_private_dir(options->User, + service->config.directory_path, + service->config.dir_group_readable, + 0) < 0) { + goto err; + } + /* Different functions are in charge of specific options for a version. We + * start just after the service directory line so once we hit another + * directory line, the function knows that it has to stop parsing. */ + switch (service->config.version) { + case HS_VERSION_TWO: + ret = rend_config_service(line->next, options, &service->config); + break; + case HS_VERSION_THREE: + ret = config_service_v3(line->next, &service->config); + break; + default: + /* We do validate before if we support the parsed version. */ + tor_assert_nonfatal_unreached(); + goto err; + } + if (ret < 0) { + goto err; + } + /* We'll check if this service can be kept depending on the others + * configured previously. */ + if (service_is_duplicate_in_list(service_list, service)) { + goto err; + } + /* Passes, add it to the given list. */ + smartlist_add(service_list, service); + return 0; + + err: + hs_service_free(service); + return -1; +} + +/* From a set of <b>options</b>, setup every hidden service found. Return 0 on + * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and + * return as normal, but don't actually change the configured services. */ +int +hs_config_service_all(const or_options_t *options, int validate_only) +{ + int dir_option_seen = 0, ret = -1; + const config_line_t *line; + smartlist_t *new_service_list = NULL; + + tor_assert(options); + + /* Newly configured service are put in that list which is then used for + * validation and staging for >= v3. */ + new_service_list = smartlist_new(); + + for (line = options->RendConfigLines; line; line = line->next) { + /* Ignore all directives that aren't the start of a service. */ + if (strcasecmp(line->key, "HiddenServiceDir")) { + if (!dir_option_seen) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + goto err; + } + continue; + } + /* Flag that we've seen a directory directive and we'll use it to make + * sure that the torrc options ordering is actually valid. */ + dir_option_seen = 1; + + /* Try to configure this service now. On success, it will be added to the + * list and validated against the service in that same list. */ + if (config_service(line, options, new_service_list) < 0) { + goto err; + } + } + + /* In non validation mode, we'll stage those services we just successfully + * configured. Service ownership is transfered from the list to the global + * state. If any service is invalid, it will be removed from the list and + * freed. All versions are handled in that function. */ + if (!validate_only) { + stage_services(new_service_list); + } else { + /* We've just validated that we were able to build a clean working list of + * services. We don't need those objects anymore. */ + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, + hs_service_free(s)); + /* For the v2 subsystem, the configuration function adds the service + * object to the staging list and it is transferred in the main list + * through the prunning process. In validation mode, we thus have to purge + * the staging list so it's not kept in memory as valid service. */ + rend_service_free_staging_list(); + } + + /* Success. Note that the service list has no ownership of its content. */ + ret = 0; + goto end; + + err: + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s)); + + end: + smartlist_free(new_service_list); + /* Tor main should call the free all function on error. */ + return ret; +} + diff --git a/src/or/hs_config.h b/src/or/hs_config.h new file mode 100644 index 0000000000..2f8cbdc130 --- /dev/null +++ b/src/or/hs_config.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.h + * \brief Header file containing configuration ABI/API for the HS subsytem. + **/ + +#ifndef TOR_HS_CONFIG_H +#define TOR_HS_CONFIG_H + +#include "or.h" + +/* Max value for HiddenServiceMaxStreams */ +#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 +/* Maximum number of intro points per version 3 services. */ +#define HS_CONFIG_V3_MAX_INTRO_POINTS 20 + +/* API */ + +int hs_config_service_all(const or_options_t *options, int validate_only); + +#endif /* TOR_HS_CONFIG_H */ + diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 2a000f5002..2393eac252 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -62,6 +62,7 @@ #include "parsecommon.h" #include "rendcache.h" #include "hs_cache.h" +#include "hs_config.h" #include "torcert.h" /* tor_cert_encode_ed22519() */ /* Constant string value used for the descriptor format. */ @@ -1747,18 +1748,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Given a descriptor string at <b>data</b>, decode all possible introduction * points that we can find. Add the introduction point object to desc_enc as we - * find them. Return 0 on success. - * - * On error, a negative value is returned. It is possible that some intro - * point object have been added to the desc_enc, they should be considered - * invalid. One single bad encoded introduction point will make this function - * return an error. */ -STATIC int + * find them. This function can't fail and it is possible that zero + * introduction points can be decoded. */ +static void decode_intro_points(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_enc, const char *data) { - int retval = -1; smartlist_t *chunked_desc = smartlist_new(); smartlist_t *intro_points = smartlist_new(); @@ -1799,22 +1795,19 @@ decode_intro_points(const hs_descriptor_t *desc, SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) { hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point); if (!ip) { - /* Malformed introduction point section. Stop right away, this - * descriptor shouldn't be used. */ - goto err; + /* Malformed introduction point section. We'll ignore this introduction + * point and continue parsing. New or unknown fields are possible for + * forward compatibility. */ + continue; } smartlist_add(desc_enc->intro_points, ip); } SMARTLIST_FOREACH_END(intro_point); done: - retval = 0; - - err: SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a)); smartlist_free(chunked_desc); SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a)); smartlist_free(intro_points); - return retval; } /* Return 1 iff the given base64 encoded signature in b64_sig from the encoded * descriptor in encoded_desc validates the descriptor content. */ @@ -2040,14 +2033,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, /* Initialize the descriptor's introduction point list before we start * decoding. Having 0 intro point is valid. Then decode them all. */ desc_encrypted_out->intro_points = smartlist_new(); - if (decode_intro_points(desc, desc_encrypted_out, message) < 0) { - goto err; - } + decode_intro_points(desc, desc_encrypted_out, message); + /* Validation of maximum introduction points allowed. */ - if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) { + if (smartlist_len(desc_encrypted_out->intro_points) > + HS_CONFIG_V3_MAX_INTRO_POINTS) { log_warn(LD_REND, "Service descriptor contains too many introduction " "points. Maximum allowed is %d but we have %d", - MAX_INTRO_POINTS, + HS_CONFIG_V3_MAX_INTRO_POINTS, smartlist_len(desc_encrypted_out->intro_points)); goto err; } diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index b8b94792de..58c4089795 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -223,9 +223,6 @@ STATIC smartlist_t *decode_link_specifiers(const char *encoded); STATIC hs_desc_intro_point_t *decode_introduction_point( const hs_descriptor_t *desc, const char *text); -STATIC int decode_intro_points(const hs_descriptor_t *desc, - hs_desc_encrypted_data_t *desc_enc, - const char *data); STATIC int encrypted_data_length_is_valid(size_t len); STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type); diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c new file mode 100644 index 0000000000..5b5dc9aaff --- /dev/null +++ b/src/or/hs_ident.c @@ -0,0 +1,81 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.c + * \brief Contains circuit and connection identifier code for the whole HS + * subsytem. + **/ + +#include "hs_ident.h" + +/* Return a newly allocated circuit identifier. The given public key is copied + * identity_pk into the identifier. */ +hs_ident_circuit_t * +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type) +{ + tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || + circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ident->circuit_type = circuit_type; + return ident; +} + +/* Free the given circuit identifier. */ +void +hs_ident_circuit_free(hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + return; + } + if (ident->auth_key_type == HS_AUTH_KEY_TYPE_LEGACY) { + crypto_pk_free(ident->auth_rsa_pk); + } + memwipe(ident, 0, sizeof(hs_ident_circuit_t)); + tor_free(ident); +} + +/* For a given directory connection identifier src, return a newly allocated + * copy of it. This can't fail. */ +hs_ident_dir_conn_t * +hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) +{ + hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* Free the given directory connection identifier. */ +void +hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_dir_conn_t)); + tor_free(ident); +} + +/* Return a newly allocated edge connection identifier. The given public key + * identity_pk is copied into the identifier. */ +hs_ident_edge_conn_t * +hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) +{ + hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + return ident; +} + +/* Free the given edge connection identifier. */ +void +hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_edge_conn_t)); + tor_free(ident); +} + diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h new file mode 100644 index 0000000000..8a7c3598cf --- /dev/null +++ b/src/or/hs_ident.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.h + * \brief Header file containing circuit and connection identifier data for + * the whole HS subsytem. + * + * \details + * This interface is used to uniquely identify a hidden service on a circuit + * or connection using the service identity public key. Once the circuit or + * connection subsystem calls in the hidden service one, we use those + * identifiers to lookup the corresponding objects like service, intro point + * and descriptor. + * + * Furthermore, the circuit identifier holds cryptographic material needed for + * the e2e encryption on the rendezvous circuit which is set once the + * rendezvous circuit has opened and ready to be used. + **/ + +#ifndef TOR_HS_IDENT_H +#define TOR_HS_IDENT_H + +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_common.h" + +/* Length of the rendezvous cookie that is used to connect circuits at the + * rendezvous point. */ +#define HS_REND_COOKIE_LEN DIGEST_LEN + +/* Type of circuit an hs_ident_t object is associated with. */ +typedef enum { + HS_IDENT_CIRCUIT_INTRO = 1, + HS_IDENT_CIRCUIT_RENDEZVOUS = 2, +} hs_ident_circuit_type_t; + +/* Client and service side circuit identifier that is used for hidden service + * circuit establishment. Not all fields contain data, it depends on the + * circuit purpose. This is attached to an origin_circuit_t. All fields are + * used by both client and service. */ +typedef struct hs_ident_circuit_t { + /* (All circuit) The public key used to uniquely identify the service. It is + * the one found in the onion address. */ + ed25519_public_key_t identity_pk; + + /* (All circuit) The type of circuit this identifier is attached to. + * Accessors of the fields in this object assert non fatal on this circuit + * type. In other words, if a rendezvous field is being accessed, the + * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is + * set when an object is initialized in its constructor. */ + hs_ident_circuit_type_t circuit_type; + + /* (Only intro point circuit) Which type of authentication key this + * circuit identifier is using. */ + hs_auth_key_type_t auth_key_type; + + /* (Only intro point circuit) Introduction point authentication key. In + * legacy mode, we use an RSA key else an ed25519 public key. */ + crypto_pk_t *auth_rsa_pk; + ed25519_public_key_t auth_ed25519_pk; + + /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + * service with an INTRODUCE1 cell and used by the service in an + * RENDEZVOUS1 cell. */ + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + + /* (Only rendezvous circuit) The HANDSHAKE_INFO needed in the RENDEZVOUS1 + * cell of the service. The construction is as follows: + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] + */ + uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for + * the e2e encryption with the client on the circuit. */ + uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; + + /* (Only rendezvous circuit) Number of streams associated with this + * rendezvous circuit. We track this because there is a check on a maximum + * value. */ + uint64_t num_rdv_streams; +} hs_ident_circuit_t; + +/* Client and service side directory connection identifier used for a + * directory connection to identify which service is being queried. This is + * attached to a dir_connection_t. */ +typedef struct hs_ident_dir_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* XXX: Client authorization. */ +} hs_ident_dir_conn_t; + +/* Client and service side edge connection identifier used for an edge + * connection to identify which service is being queried. This is attached to + * a edge_connection_t. */ +typedef struct hs_ident_edge_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* XXX: Client authorization. */ +} hs_ident_edge_conn_t; + +/* Circuit identifier API. */ +hs_ident_circuit_t *hs_ident_circuit_new( + const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type); +void hs_ident_circuit_free(hs_ident_circuit_t *ident); + +/* Directory connection identifier API. */ +hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); +void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident); + +/* Edge connection identifier API. */ +hs_ident_edge_conn_t *hs_ident_edge_conn_new( + const ed25519_public_key_t *identity_pk); +void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident); + +#endif /* TOR_HS_IDENT_H */ + diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index 163ed810e7..bfb1331ba0 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -9,6 +9,9 @@ #ifndef TOR_HS_INTRO_H #define TOR_HS_INTRO_H +#include "crypto_curve25519.h" +#include "torcert.h" + /* Authentication key type in an ESTABLISH_INTRO cell. */ enum hs_intro_auth_key_type { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, @@ -24,6 +27,15 @@ typedef enum { HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003, } hs_intro_ack_status_t; +/* Object containing introduction point common data between the service and + * the client side. */ +typedef struct hs_intropoint_t { + /* Authentication key certificate from the descriptor. */ + tor_cert_t *auth_key_cert; + /* A list of link specifier. */ + smartlist_t *link_specifiers; +} hs_intropoint_t; + int hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request, size_t request_len); diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c index 119899817e..a416bc46c3 100644 --- a/src/or/hs_ntor.c +++ b/src/or/hs_ntor.c @@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good( /* Input length to KDF for key expansion */ #define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN) -/* Output length of KDF for key expansion */ -#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2) - -/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the - * circuit key expansion as specified by section '4.2.1. Key expansion' and - * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */ -hs_ntor_rend_circuit_keys_t * -hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys) + +/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size + * DIGEST256_LEN), do the circuit key expansion as specified by section + * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be + * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN). + * + * Return 0 if things went well, else return -1. */ +int +hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len, + uint8_t *keys_out, size_t keys_out_len) { uint8_t *ptr; uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; - uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN]; crypto_xof_t *xof; - hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL; + + /* Sanity checks on lengths to make sure we are good */ + if (BUG(seed_len != DIGEST256_LEN)) { + return -1; + } + if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } /* Let's build the input to the KDF */ ptr = kdf_input; - APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN); + APPEND(ptr, ntor_key_seed, DIGEST256_LEN); APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); tor_assert(ptr == kdf_input + sizeof(kdf_input)); /* Generate the keys */ xof = crypto_xof_new(); crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); - crypto_xof_squeeze_bytes(xof, keys, sizeof(keys)); + crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN); crypto_xof_free(xof); - /* Generate keys structure and assign keys to it */ - rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t)); - ptr = keys; - memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN;; - memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - tor_assert(ptr == keys + sizeof(keys)); - - return rend_circuit_keys; + return 0; } diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h index cd75f46a4c..d07bff8cf0 100644 --- a/src/or/hs_ntor.h +++ b/src/or/hs_ntor.h @@ -6,6 +6,10 @@ #include "or.h" +/* Output length of KDF for key expansion */ +#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \ + (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2) + /* Key material needed to encode/decode INTRODUCE1 cells */ typedef struct { /* Key used for encryption of encrypted INTRODUCE1 blob */ @@ -23,21 +27,6 @@ typedef struct { uint8_t ntor_key_seed[DIGEST256_LEN]; } hs_ntor_rend_cell_keys_t; -/* Key material resulting from key expansion as detailed in section "4.2.1. Key - * expansion" of rend-spec-ng.txt. */ -typedef struct { - /* Per-circuit key material used in ESTABLISH_INTRO cell */ - uint8_t KH[DIGEST256_LEN]; - /* Authentication key for outgoing RELAY cells */ - uint8_t Df[DIGEST256_LEN]; - /* Authentication key for incoming RELAY cells */ - uint8_t Db[DIGEST256_LEN]; - /* Encryption key for outgoing RELAY cells */ - uint8_t Kf[CIPHER256_KEY_LEN]; - /* Decryption key for incoming RELAY cells */ - uint8_t Kb[CIPHER256_KEY_LEN]; -} hs_ntor_rend_circuit_keys_t; - int hs_ntor_client_get_introduce1_keys( const ed25519_public_key_t *intro_auth_pubkey, const curve25519_public_key_t *intro_enc_pubkey, @@ -66,8 +55,9 @@ int hs_ntor_service_get_rendezvous1_keys( const curve25519_public_key_t *client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); -hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys); +int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, + size_t seed_len, + uint8_t *keys_out, size_t keys_out_len); int hs_ntor_client_rendezvous2_mac_is_good( const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 205ef11c92..5fde42ddbb 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -6,19 +6,644 @@ * \brief Implement next generation hidden service functionality **/ +#define HS_SERVICE_PRIVATE + #include "or.h" +#include "circuitlist.h" +#include "config.h" #include "relay.h" #include "rendservice.h" -#include "circuitlist.h" -#include "circpathbias.h" +#include "router.h" +#include "routerkeys.h" +#include "hs_common.h" +#include "hs_config.h" #include "hs_intropoint.h" #include "hs_service.h" -#include "hs_common.h" #include "hs/cell_establish_intro.h" #include "hs/cell_common.h" +/* Onion service directory file names. */ +static const char *fname_keyfile_prefix = "hs_ed25519"; +static const char *fname_hostname = "hostname"; +static const char *address_tld = "onion"; + +/* Staging list of service object. When configuring service, we add them to + * this list considered a staging area and they will get added to our global + * map once the keys have been loaded. These two steps are seperated because + * loading keys requires that we are an actual running tor process. */ +static smartlist_t *hs_service_staging_list; + +/* Helper: Function to compare two objects in the service map. Return 1 if the + * two service have the same master public identity key. */ +static inline int +hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second) +{ + tor_assert(first); + tor_assert(second); + /* Simple key compare. */ + return ed25519_pubkey_eq(&first->keys.identity_pk, + &second->keys.identity_pk); +} + +/* Helper: Function for the service hash table code below. The key used is the + * master public identity key which is ultimately the onion address. */ +static inline unsigned int +hs_service_ht_hash(const hs_service_t *service) +{ + tor_assert(service); + return (unsigned int) siphash24g(service->keys.identity_pk.pubkey, + sizeof(service->keys.identity_pk.pubkey)); +} + +/* This is _the_ global hash map of hidden services which indexed the service + * contained in it by master public identity key which is roughly the onion + * address of the service. */ +static struct hs_service_ht *hs_service_map; + +/* Register the service hash table. */ +HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */ + hs_service_t, /* Object contained in the map. */ + hs_service_node, /* The name of the HT_ENTRY member. */ + hs_service_ht_hash, /* Hashing function. */ + hs_service_ht_eq) /* Compare function for objects. */ + +HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node, + hs_service_ht_hash, hs_service_ht_eq, + 0.6, tor_reallocarray, tor_free_) + +/* Query the given service map with a public key and return a service object + * if found else NULL. It is also possible to set a directory path in the + * search query. If pk is NULL, then it will be set to zero indicating the + * hash table to compare the directory path instead. */ +STATIC hs_service_t * +find_service(hs_service_ht *map, const ed25519_public_key_t *pk) +{ + hs_service_t dummy_service; + tor_assert(map); + tor_assert(pk); + memset(&dummy_service, 0, sizeof(dummy_service)); + ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk); + return HT_FIND(hs_service_ht, map, &dummy_service); +} + +/* Register the given service in the given map. If the service already exists + * in the map, -1 is returned. On success, 0 is returned and the service + * ownership has been transfered to the global map. */ +STATIC int +register_service(hs_service_ht *map, hs_service_t *service) +{ + tor_assert(map); + tor_assert(service); + tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk)); + + if (find_service(map, &service->keys.identity_pk)) { + /* Existing service with the same key. Do not register it. */ + return -1; + } + /* Taking ownership of the object at this point. */ + HT_INSERT(hs_service_ht, map, service); + return 0; +} + +/* Remove a given service from the given map. If service is NULL or the + * service key is unset, return gracefully. */ +STATIC void +remove_service(hs_service_ht *map, hs_service_t *service) +{ + hs_service_t *elm; + + tor_assert(map); + + /* Ignore if no service or key is zero. */ + if (BUG(service == NULL) || + BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) { + return; + } + + elm = HT_REMOVE(hs_service_ht, map, service); + if (elm) { + tor_assert(elm == service); + } else { + log_warn(LD_BUG, "Could not find service in the global map " + "while removing service %s", + escaped(service->config.directory_path)); + } +} + +/* Set the default values for a service configuration object <b>c</b>. */ +static void +set_service_default_config(hs_service_config_t *c, + const or_options_t *options) +{ + tor_assert(c); + c->ports = smartlist_new(); + c->directory_path = NULL; + c->descriptor_post_period = options->RendPostPeriod; + c->max_streams_per_rdv_circuit = 0; + c->max_streams_close_circuit = 0; + c->num_intro_points = NUM_INTRO_POINTS_DEFAULT; + c->allow_unknown_ports = 0; + c->is_single_onion = 0; + c->dir_group_readable = 0; + c->is_ephemeral = 0; +} + +/* From a service configuration object config, clear everything from it + * meaning free allocated pointers and reset the values. */ +static void +service_clear_config(hs_service_config_t *config) +{ + if (config == NULL) { + return; + } + tor_free(config->directory_path); + if (config->ports) { + SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p, + rend_service_port_config_free(p);); + smartlist_free(config->ports); + } + memset(config, 0, sizeof(*config)); +} + +/* Helper: Function that needs to return 1 for the HT for each loop which + * frees every service in an hash map. */ +static int +ht_free_service_(struct hs_service_t *service, void *data) +{ + (void) data; + hs_service_free(service); + /* This function MUST return 1 so the given object is then removed from the + * service map leading to this free of the object being safe. */ + return 1; +} + +/* Free every service that can be found in the global map. Once done, clear + * and free the global map. */ +static void +service_free_all(void) +{ + if (hs_service_map) { + /* The free helper function returns 1 so this is safe. */ + hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL); + HT_CLEAR(hs_service_ht, hs_service_map); + tor_free(hs_service_map); + hs_service_map = NULL; + } + + if (hs_service_staging_list) { + /* Cleanup staging list. */ + SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s, + hs_service_free(s)); + smartlist_free(hs_service_staging_list); + hs_service_staging_list = NULL; + } +} + +/* Close all rendezvous circuits for the given service. */ +static void +close_service_rp_circuits(hs_service_t *service) +{ + tor_assert(service); + /* XXX: To implement. */ + return; +} + +/* Close the circuit(s) for the given map of introduction points. */ +static void +close_intro_circuits(hs_service_intropoints_t *intro_points) +{ + tor_assert(intro_points); + + DIGEST256MAP_FOREACH(intro_points->map, key, + const hs_service_intro_point_t *, ip) { + origin_circuit_t *ocirc = + hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + if (ocirc) { + hs_circuitmap_remove_circuit(TO_CIRCUIT(ocirc)); + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Close all introduction circuits for the given service. */ +static void +close_service_intro_circuits(hs_service_t *service) +{ + tor_assert(service); + + if (service->desc_current) { + close_intro_circuits(&service->desc_current->intro_points); + } + if (service->desc_next) { + close_intro_circuits(&service->desc_next->intro_points); + } +} + +/* Close any circuits related to the given service. */ +static void +close_service_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* Only support for version >= 3. */ + if (BUG(service->config.version < HS_VERSION_THREE)) { + return; + } + /* Close intro points. */ + close_service_intro_circuits(service); + /* Close rendezvous points. */ + close_service_rp_circuits(service); +} + +/* Move introduction points from the src descriptor to the dst descriptor. The + * destination service intropoints are wiped out if any before moving. */ +static void +move_descriptor_intro_points(hs_service_descriptor_t *src, + hs_service_descriptor_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + /* XXX: Free dst introduction points. */ + dst->intro_points.map = src->intro_points.map; + /* Nullify the source. */ + src->intro_points.map = NULL; +} + +/* Move introduction points from the src service to the dst service. The + * destination service intropoints are wiped out if any before moving. */ +static void +move_intro_points(hs_service_t *src, hs_service_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + /* Cleanup destination. */ + if (src->desc_current && dst->desc_current) { + move_descriptor_intro_points(src->desc_current, dst->desc_current); + } + if (src->desc_next && dst->desc_next) { + move_descriptor_intro_points(src->desc_next, dst->desc_next); + } +} + +/* Move every ephemeral services from the src service map to the dst service + * map. It is possible that a service can't be register to the dst map which + * won't stop the process of moving them all but will trigger a log warn. */ +static void +move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst) +{ + hs_service_t **iter, **next; + + tor_assert(src); + tor_assert(dst); + + /* Iterate over the map to find ephemeral service and move them to the other + * map. We loop using this method to have a safe removal process. */ + for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) { + hs_service_t *s = *iter; + if (!s->config.is_ephemeral) { + /* Yeah, we are in a very manual loop :). */ + next = HT_NEXT(hs_service_ht, src, iter); + continue; + } + /* Remove service from map and then register to it to the other map. + * Reminder that "*iter" and "s" are the same thing. */ + next = HT_NEXT_RMV(hs_service_ht, src, iter); + if (register_service(dst, s) < 0) { + log_warn(LD_BUG, "Ephemeral service key is already being used. " + "Skipping."); + } + } +} + +/* Return a const string of the directory path escaped. If this is an + * ephemeral service, it returns "[EPHEMERAL]". This can only be called from + * the main thread because escaped() uses a static variable. */ +static const char * +service_escaped_dir(const hs_service_t *s) +{ + return (s->config.is_ephemeral) ? "[EPHEMERAL]" : + escaped(s->config.directory_path); +} + +/* Register services that are in the staging list. Once this function returns, + * the global service map will be set with the right content and all non + * surviving services will be cleaned up. */ +static void +register_all_services(void) +{ + struct hs_service_ht *new_service_map; + hs_service_t *s, **iter; + + tor_assert(hs_service_staging_list); + + /* We'll save us some allocation and computing time. */ + if (smartlist_len(hs_service_staging_list) == 0) { + return; + } + + /* Allocate a new map that will replace the current one. */ + new_service_map = tor_malloc_zero(sizeof(*new_service_map)); + HT_INIT(hs_service_ht, new_service_map); + + /* First step is to transfer all ephemeral services from the current global + * map to the new one we are constructing. We do not prune ephemeral + * services as the only way to kill them is by deleting it from the control + * port or stopping the tor daemon. */ + move_ephemeral_services(hs_service_map, new_service_map); + + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) { + /* Check if that service is already in our global map and if so, we'll + * transfer the intro points to it. */ + s = find_service(hs_service_map, &snew->keys.identity_pk); + if (s) { + /* Pass ownership of intro points from s (the current service) to snew + * (the newly configured one). */ + move_intro_points(s, snew); + /* Remove the service from the global map because after this, we need to + * go over the remaining service in that map that aren't surviving the + * reload to close their circuits. */ + remove_service(hs_service_map, s); + } + /* Great, this service is now ready to be added to our new map. */ + if (BUG(register_service(new_service_map, snew) < 0)) { + /* This should never happen because prior to registration, we validate + * every service against the entire set. Not being able to register a + * service means we failed to validate correctly. In that case, don't + * break tor and ignore the service but tell user. */ + log_warn(LD_BUG, "Unable to register service with directory %s", + service_escaped_dir(snew)); + SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew); + hs_service_free(snew); + } + } SMARTLIST_FOREACH_END(snew); + + /* Close any circuits associated with the non surviving services. Every + * service in the current global map are roaming. */ + HT_FOREACH(iter, hs_service_ht, hs_service_map) { + close_service_circuits(*iter); + } + + /* Time to make the switch. We'll clear the staging list because its content + * has now changed ownership to the map. */ + smartlist_clear(hs_service_staging_list); + service_free_all(); + hs_service_map = new_service_map; +} + +/* Write the onion address of a given service to the given filename fname_ in + * the service directory. Return 0 on success else -1 on error. */ +static int +write_address_to_file(const hs_service_t *service, const char *fname_) +{ + int ret = -1; + char *fname = NULL; + /* Length of an address plus the sizeof the address tld (onion) which counts + * the NUL terminated byte so we keep it for the "." and the newline. */ + char buf[HS_SERVICE_ADDR_LEN_BASE32 + sizeof(address_tld) + 1]; + + tor_assert(service); + tor_assert(fname_); + + /* Construct the full address with the onion tld and write the hostname file + * to disk. */ + tor_snprintf(buf, sizeof(buf), "%s.%s\n", service->onion_address, + address_tld); + /* Notice here that we use the given "fname_". */ + fname = hs_path_from_filename(service->config.directory_path, fname_); + if (write_str_to_file(fname, buf, 0) < 0) { + log_warn(LD_REND, "Could not write onion address to hostname file %s", + escaped(fname)); + goto end; + } + +#ifndef _WIN32 + if (service->config.dir_group_readable) { + /* Mode to 0640. */ + if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { + log_warn(LD_FS, "Unable to make onion service hostname file %s " + "group-readable.", escaped(fname)); + } + } +#endif /* _WIN32 */ + + /* Success. */ + ret = 0; + end: + tor_free(fname); + return ret; +} + +/* Load and/or generate private keys for the given service. On success, the + * hostname file will be written to disk along with the master private key iff + * the service is not configured for offline keys. Return 0 on success else -1 + * on failure. */ +static int +load_service_keys(hs_service_t *service) +{ + int ret = -1; + char *fname = NULL; + ed25519_keypair_t *kp; + const hs_service_config_t *config; + + tor_assert(service); + + config = &service->config; + + /* Create and fix permission on service directory. We are about to write + * files to that directory so make sure it exists and has the right + * permissions. We do this here because at this stage we know that Tor is + * actually running and the service we have has been validated. */ + if (BUG(hs_check_service_private_dir(get_options()->User, + config->directory_path, + config->dir_group_readable, 1) < 0)) { + goto end; + } + + /* Try to load the keys from file or generate it if not found. */ + fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix); + /* Don't ask for key creation, we want to know if we were able to load it or + * we had to generate it. Better logging! */ + kp = ed_key_init_from_file(fname, 0, LOG_INFO, NULL, 0, 0, 0, NULL); + if (!kp) { + log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname); + /* We'll now try to generate the keys and for it we want the strongest + * randomness for it. The keypair will be written in different files. */ + uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG | + INIT_ED_KEY_SPLIT; + kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0, + NULL); + if (!kp) { + log_warn(LD_REND, "Unable to generate keys and save in %s.", fname); + goto end; + } + } + + /* Copy loaded or generated keys to service object. */ + ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey); + memcpy(&service->keys.identity_sk, &kp->seckey, + sizeof(service->keys.identity_sk)); + /* This does a proper memory wipe. */ + ed25519_keypair_free(kp); + + /* Build onion address from the newly loaded keys. */ + tor_assert(service->config.version <= UINT8_MAX); + hs_build_address(&service->keys.identity_pk, + (uint8_t) service->config.version, + service->onion_address); + + /* Write onion address to hostname file. */ + if (write_address_to_file(service, fname_hostname) < 0) { + goto end; + } + + /* Succes. */ + ret = 0; + end: + tor_free(fname); + return ret; +} + +/* Load and/or generate keys for all onion services including the client + * authorization if any. Return 0 on success, -1 on failure. */ +int +hs_service_load_all_keys(void) +{ + /* Load v2 service keys if we have v2. */ + if (num_rend_services() != 0) { + if (rend_service_load_all_keys(NULL) < 0) { + goto err; + } + } + + /* Load or/and generate them for v3+. */ + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) { + /* Ignore ephemeral service, they already have their keys set. */ + if (service->config.is_ephemeral) { + continue; + } + log_info(LD_REND, "Loading v3 onion service keys from %s", + service_escaped_dir(service)); + if (load_service_keys(service) < 0) { + goto err; + } + /* XXX: Load/Generate client authorization keys. (#20700) */ + } SMARTLIST_FOREACH_END(service); + + /* Final step, the staging list contains service in a quiescent state that + * is ready to be used. Register them to the global map. Once this is over, + * the staging list will be cleaned up. */ + register_all_services(); + + /* All keys have been loaded successfully. */ + return 0; + err: + return -1; +} + +/* Put all service object in the given service list. After this, the caller + * looses ownership of every elements in the list and responsible to free the + * list pointer. */ +void +hs_service_stage_services(const smartlist_t *service_list) +{ + tor_assert(service_list); + /* This list is freed at registration time but this function can be called + * multiple time. */ + if (hs_service_staging_list == NULL) { + hs_service_staging_list = smartlist_new(); + } + /* Add all service object to our staging list. Caller is responsible for + * freeing the service_list. */ + smartlist_add_all(hs_service_staging_list, service_list); +} + +/* Allocate and initilize a service object. The service configuration will + * contain the default values. Return the newly allocated object pointer. This + * function can't fail. */ +hs_service_t * +hs_service_new(const or_options_t *options) +{ + hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t)); + /* Set default configuration value. */ + set_service_default_config(&service->config, options); + /* Set the default service version. */ + service->config.version = HS_SERVICE_DEFAULT_VERSION; + return service; +} + +/* Free the given <b>service</b> object and all its content. This function + * also takes care of wiping service keys from memory. It is safe to pass a + * NULL pointer. */ +void +hs_service_free(hs_service_t *service) +{ + if (service == NULL) { + return; + } + + /* Free descriptors. */ + if (service->desc_current) { + hs_descriptor_free(service->desc_current->desc); + /* Wipe keys. */ + memwipe(&service->desc_current->signing_kp, 0, + sizeof(service->desc_current->signing_kp)); + memwipe(&service->desc_current->blinded_kp, 0, + sizeof(service->desc_current->blinded_kp)); + /* XXX: Free intro points. */ + tor_free(service->desc_current); + } + if (service->desc_next) { + hs_descriptor_free(service->desc_next->desc); + /* Wipe keys. */ + memwipe(&service->desc_next->signing_kp, 0, + sizeof(service->desc_next->signing_kp)); + memwipe(&service->desc_next->blinded_kp, 0, + sizeof(service->desc_next->blinded_kp)); + /* XXX: Free intro points. */ + tor_free(service->desc_next); + } + + /* Free service configuration. */ + service_clear_config(&service->config); + + /* Wipe service keys. */ + memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); + + tor_free(service); +} + +/* Initialize the service HS subsystem. */ +void +hs_service_init(void) +{ + /* Should never be called twice. */ + tor_assert(!hs_service_map); + tor_assert(!hs_service_staging_list); + + /* v2 specific. */ + rend_service_init(); + + hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht)); + HT_INIT(hs_service_ht, hs_service_map); + + hs_service_staging_list = smartlist_new(); +} + +/* Release all global storage of the hidden service subsystem. */ +void +hs_service_free_all(void) +{ + rend_service_free_all(); + service_free_all(); +} + /* XXX We don't currently use these functions, apart from generating unittest data. When we start implementing the service-side support for prop224 we should revisit these functions and use them. */ @@ -172,3 +797,37 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, return NULL; } +#ifdef TOR_UNIT_TESTS + +/* Return the global service map size. Only used by unit test. */ +STATIC unsigned int +get_hs_service_map_size(void) +{ + return HT_SIZE(hs_service_map); +} + +/* Return the staging list size. Only used by unit test. */ +STATIC int +get_hs_service_staging_list_size(void) +{ + return smartlist_len(hs_service_staging_list); +} + +STATIC hs_service_ht * +get_hs_service_map(void) +{ + return hs_service_map; +} + +STATIC hs_service_t * +get_first_service(void) +{ + hs_service_t **obj = HT_START(hs_service_ht, hs_service_map); + if (obj == NULL) { + return NULL; + } + return *obj; +} + +#endif /* TOR_UNIT_TESTS */ + diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 3302592762..54b9e69724 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -3,15 +3,222 @@ /** * \file hs_service.h - * \brief Header file for hs_service.c. + * \brief Header file containing service data for the HS subsytem. **/ #ifndef TOR_HS_SERVICE_H #define TOR_HS_SERVICE_H -#include "or.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "replaycache.h" + +#include "hs_common.h" +#include "hs_descriptor.h" +#include "hs_intropoint.h" + +/* Trunnel */ #include "hs/cell_establish_intro.h" +/* When loading and configuring a service, this is the default version it will + * be configured for as it is possible that no HiddenServiceVersion is + * present. */ +#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO + +/* Service side introduction point. */ +typedef struct hs_service_intro_point_t { + /* Top level intropoint "shared" data between client/service. */ + hs_intropoint_t base; + + /* Authentication keypair used to create the authentication certificate + * which is published in the descriptor. */ + ed25519_keypair_t auth_key_kp; + + /* Encryption private key. */ + curve25519_secret_key_t enc_key_sk; + + /* Amount of INTRODUCE2 cell accepted from this intro point. */ + uint64_t introduce2_count; + + /* Maximum number of INTRODUCE2 cell this intro point should accept. */ + uint64_t introduce2_max; + + /* The time at which this intro point should expire and stop being used. */ + time_t time_to_expire; + + /* The amount of circuit creation we've made to this intro point. This is + * incremented every time we do a circuit relaunch on this intro point which + * is triggered when the circuit dies but the node is still in the + * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */ + uint32_t circuit_retries; + + /* Set if this intro point has an established circuit. */ + unsigned int circuit_established : 1; + + /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the + * circuit associated with this intro point has received. This is used to + * prevent replay attacks. */ + replaycache_t *replay_cache; +} hs_service_intro_point_t; + +/* Object handling introduction points of a service. */ +typedef struct hs_service_intropoints_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t retry_period_started; + + /* Number of circuit we've launched during a single retry period. */ + unsigned int num_circuits_launched; + + /* Contains the current hs_service_intro_point_t objects indexed by + * authentication public key. */ + digest256map_t *map; +} hs_service_intropoints_t; + +/* Representation of a service descriptor. */ +typedef struct hs_service_descriptor_t { + /* Decoded descriptor. This object is used for encoding when the service + * publishes the descriptor. */ + hs_descriptor_t *desc; + + /* Descriptor signing keypair. */ + ed25519_keypair_t signing_kp; + + /* Blinded keypair derived from the master identity public key. */ + ed25519_keypair_t blinded_kp; + + /* When is the next time when we should upload the descriptor. */ + time_t next_upload_time; + + /* Introduction points assign to this descriptor which contains + * hs_service_intropoints_t object indexed by authentication key (the RSA + * key if the node is legacy). */ + hs_service_intropoints_t intro_points; +} hs_service_descriptor_t; + +/* Service key material. */ +typedef struct hs_service_keys_t { + /* Master identify public key. */ + ed25519_public_key_t identity_pk; + /* Master identity private key. */ + ed25519_secret_key_t identity_sk; + /* True iff the key is kept offline which means the identity_sk MUST not be + * used in that case. */ + unsigned int is_identify_key_offline : 1; +} hs_service_keys_t; + +/* Service configuration. The following are set from the torrc options either + * set by the configuration file or by the control port. Nothing else should + * change those values. */ +typedef struct hs_service_config_t { + /* Protocol version of the service. Specified by HiddenServiceVersion + * option. */ + uint32_t version; + + /* List of rend_service_port_config_t */ + smartlist_t *ports; + + /* Path on the filesystem where the service persistent data is stored. NULL + * if the service is ephemeral. Specified by HiddenServiceDir option. */ + char *directory_path; + + /* The time period after which a descriptor is uploaded to the directories + * in seconds. Specified by RendPostPeriod option. */ + uint32_t descriptor_post_period; + + /* The maximum number of simultaneous streams per rendezvous circuit that + * are allowed to be created. No limit if 0. Specified by + * HiddenServiceMaxStreams option. */ + uint64_t max_streams_per_rdv_circuit; + + /* If true, we close circuits that exceed the max_streams_per_rdv_circuit + * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */ + unsigned int max_streams_close_circuit : 1; + + /* How many introduction points this service has. Specified by + * HiddenServiceNumIntroductionPoints option. */ + unsigned int num_intro_points; + + /* True iff we allow request made on unknown ports. Specified by + * HiddenServiceAllowUnknownPorts option. */ + unsigned int allow_unknown_ports : 1; + + /* If true, this service is a Single Onion Service. Specified by + * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */ + unsigned int is_single_onion : 1; + + /* If true, allow group read permissions on the directory_path. Specified by + * HiddenServiceDirGroupReadable option. */ + unsigned int dir_group_readable : 1; + + /* Is this service ephemeral? */ + unsigned int is_ephemeral : 1; +} hs_service_config_t; + +/* Service state. */ +typedef struct hs_service_state_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t intro_circ_retry_started_time; + + /* Number of circuit we've launched during a single retry period. This + * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ + unsigned int num_intro_circ_launched; + + /* Indicate that the service has entered the overlap period. We use this + * flag to check for descriptor rotation. */ + unsigned int in_overlap_period : 1; +} hs_service_state_t; + +/* Representation of a service running on this tor instance. */ +typedef struct hs_service_t { + /* Onion address base32 encoded and NUL terminated. We keep it for logging + * purposes so we don't have to build it everytime. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + /* Hashtable node: use to look up the service by its master public identity + * key in the service global map. */ + HT_ENTRY(hs_service_t) hs_service_node; + + /* Service state which contains various flags and counters. */ + hs_service_state_t state; + + /* Key material of the service. */ + hs_service_keys_t keys; + + /* Configuration of the service. */ + hs_service_config_t config; + + /* Current descriptor. */ + hs_service_descriptor_t *desc_current; + /* Next descriptor that we need for the overlap period for which we have to + * keep two sets of opened introduction point circuits. */ + hs_service_descriptor_t *desc_next; + + /* XXX: Credential (client auth.) #20700. */ + +} hs_service_t; + +/* For the service global hash map, we define a specific type for it which + * will make it safe to use and specific to some controlled parameters such as + * the hashing function and how to compare services. */ +typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht; + +/* API */ + +/* Global initializer and cleanup function. */ +void hs_service_init(void); +void hs_service_free_all(void); + +/* Service new/free functions. */ +hs_service_t *hs_service_new(const or_options_t *options); +void hs_service_free(hs_service_t *service); + +void hs_service_stage_services(const smartlist_t *service_list); +int hs_service_load_all_keys(void); + /* These functions are only used by unit tests and we need to expose them else * hs_service.o ends up with no symbols in libor.a which makes clang throw a * warning at compile time. See #21825. */ @@ -23,5 +230,25 @@ ssize_t get_establish_intro_payload(uint8_t *buf, size_t buf_len, const trn_cell_establish_intro_t *cell); +#ifdef HS_SERVICE_PRIVATE + +#ifdef TOR_UNIT_TESTS + +/* Useful getters for unit tests. */ +STATIC unsigned int get_hs_service_map_size(void); +STATIC int get_hs_service_staging_list_size(void); +STATIC hs_service_ht *get_hs_service_map(void); +STATIC hs_service_t *get_first_service(void); + +/* Service accessors. */ +STATIC hs_service_t *find_service(hs_service_ht *map, + const ed25519_public_key_t *pk); +STATIC void remove_service(hs_service_ht *map, hs_service_t *service); +STATIC int register_service(hs_service_ht *map, hs_service_t *service); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* HS_SERVICE_PRIVATE */ + #endif /* TOR_HS_SERVICE_H */ diff --git a/src/or/include.am b/src/or/include.am index 1ef5afa013..15b86ef50b 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -50,16 +50,20 @@ LIBTOR_A_SOURCES = \ src/or/dnsserv.c \ src/or/fp_pair.c \ src/or/geoip.c \ - src/or/hs_intropoint.c \ - src/or/hs_circuitmap.c \ - src/or/hs_ntor.c \ - src/or/hs_service.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/hs_cache.c \ + src/or/hs_circuit.c \ + src/or/hs_circuitmap.c \ + src/or/hs_client.c \ src/or/hs_common.c \ + src/or/hs_config.c \ src/or/hs_descriptor.c \ + src/or/hs_ident.c \ + src/or/hs_intropoint.c \ + src/or/hs_ntor.c \ + src/or/hs_service.c \ src/or/keypin.c \ src/or/main.c \ src/or/microdesc.c \ @@ -180,12 +184,16 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_circuit.h \ + src/or/hs_circuitmap.h \ + src/or/hs_client.h \ src/or/hs_common.h \ + src/or/hs_config.h \ src/or/hs_descriptor.h \ - src/or/hs_intropoint.h \ - src/or/hs_circuitmap.h \ - src/or/hs_ntor.h \ - src/or/hs_service.h \ + src/or/hs_ident.h \ + src/or/hs_intropoint.h \ + src/or/hs_ntor.h \ + src/or/hs_service.h \ src/or/keypin.h \ src/or/main.h \ src/or/microdesc.h \ diff --git a/src/or/main.c b/src/or/main.c index cb24fd18c8..dc23184961 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -2060,6 +2060,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) /** * Periodic callback: write the heartbeat message in the logs. + * + * If writing the heartbeat message to the logs fails for some reason, retry + * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds. */ static int heartbeat_callback(time_t now, const or_options_t *options) @@ -2071,14 +2074,20 @@ heartbeat_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } - /* Write the heartbeat message */ + /* Skip the first one. */ if (first) { - first = 0; /* Skip the first one. */ - } else { - log_heartbeat(now); + first = 0; + return options->HeartbeatPeriod; } - return options->HeartbeatPeriod; + /* Write the heartbeat message */ + if (log_heartbeat(now) == 0) { + return options->HeartbeatPeriod; + } else { + /* If we couldn't write the heartbeat log message, try again in the minimum + * interval of time. */ + return MIN_HEARTBEAT_PERIOD; + } } #define CDM_CLEAN_CALLBACK_INTERVAL 600 @@ -2355,7 +2364,7 @@ do_hup(void) tor_free(msg); } } - if (authdir_mode_handles_descs(options, -1)) { + if (authdir_mode(options)) { /* reload the approved-routers file */ if (dirserv_load_fingerprint_file() < 0) { /* warnings are logged from dirserv_load_fingerprint_file() directly */ @@ -2499,9 +2508,6 @@ do_main_loop(void) } } - /* Initialize relay-side HS circuitmap */ - hs_circuitmap_init(); - /* set up once-a-second callback. */ if (! second_timer) { struct timeval one_second; @@ -2877,7 +2883,6 @@ dumpstats(int severity) rep_hist_dump_stats(now,severity); rend_service_dump_stats(severity); - dump_pk_ops(severity); dump_distinct_digest_count(severity); } @@ -3014,9 +3019,10 @@ tor_init(int argc, char *argv[]) rep_hist_init(); /* Initialize the service cache. */ rend_cache_init(); - hs_cache_init(); addressmap_init(); /* Init the client dns cache. Do it always, since it's * cheap. */ + /* Initialize the HS subsystem. */ + hs_init(); { /* We search for the "quiet" option first, since it decides whether we @@ -3216,10 +3222,8 @@ tor_free_all(int postfork) networkstatus_free_all(); addressmap_free_all(); dirserv_free_all(); - rend_service_free_all(); rend_cache_free_all(); rend_service_authorization_free_all(); - hs_cache_free_all(); rep_hist_free_all(); dns_free_all(); clear_pending_onions(); @@ -3232,7 +3236,6 @@ tor_free_all(int postfork) connection_edge_free_all(); scheduler_free_all(); nodelist_free_all(); - hs_circuitmap_free_all(); microdesc_free_all(); routerparse_free_all(); ext_orport_free_all(); @@ -3241,6 +3244,7 @@ tor_free_all(int postfork) protover_free_all(); bridges_free_all(); consdiffmgr_free_all(); + hs_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -3478,7 +3482,7 @@ sandbox_init_filter(void) if (options->BridgeAuthoritativeDir) OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp"); - if (authdir_mode_handles_descs(options, -1)) + if (authdir_mode(options)) OPEN_DATADIR("approved-routers"); if (options->ServerDNSResolvConfFile) diff --git a/src/or/microdesc.c b/src/or/microdesc.c index a4e6b409c4..18a6fbded7 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -876,7 +876,7 @@ update_microdesc_downloads(time_t now) smartlist_free(missing); } -/** For every microdescriptor listed in the current microdecriptor consensus, +/** For every microdescriptor listed in the current microdescriptor consensus, * update its last_listed field to be at least as recent as the publication * time of the current microdescriptor consensus. */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 25d79139b9..aff36b4c0b 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -76,7 +76,7 @@ static strmap_t *unnamed_server_map = NULL; * status. */ STATIC networkstatus_t *current_ns_consensus = NULL; -/** Most recently received and validated v3 "microdec"-flavored consensus +/** Most recently received and validated v3 "microdesc"-flavored consensus * network status. */ STATIC networkstatus_t *current_md_consensus = NULL; @@ -1783,7 +1783,7 @@ networkstatus_set_current_consensus(const char *consensus, if (from_cache && !was_waiting_for_certs) { /* We previously stored this; check _now_ to make sure that version-kills - * really work. This happens even before we check signatures: we did so + * really work. This happens even before we check signatures: we did so * before when we stored this to disk. This does mean an attacker who can * write to the datadir can make us not start: such an attacker could * already harm us by replacing our guards, which would be worse. */ diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 3ac5c3e302..dafeb9f12d 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -630,11 +630,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) if (! node->name_lookup_warned) { base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); log_warn(LD_CONFIG, - "You specified a server \"%s\" by name, but the directory " - "authorities do not have any key registered for this " - "nickname -- so it could be used by any server, not just " - "the one you meant. " - "To make sure you get the same server in the future, refer " + "You specified a relay \"%s\" by name, but nicknames can be " + "used by any relay, not just the one you meant. " + "To make sure you get the same relay in the future, refer " "to it by key, as \"$%s\".", nickname, fp); node->name_lookup_warned = 1; } @@ -707,6 +705,48 @@ node_supports_ed25519_link_authentication(const node_t *node) return 0; } +/** Return true iff <b>node</b> supports the hidden service directory version + * 3 protocol (proposal 224). */ +int +node_supports_v3_hsdir(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_hsdir; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSDIR, PROTOVER_HSDIR_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return true iff <b>node</b> supports ed25519 authentication as an hidden + * service introduction point.*/ +int +node_supports_ed25519_hs_intro(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_ed25519_hs_intro; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSINTRO, PROTOVER_HS_INTRO_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + /** Return the RSA ID key's SHA1 digest for the provided node. */ const uint8_t * node_get_rsa_id_digest(const node_t *node) diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 95ae778a5b..405b79d820 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -58,6 +58,8 @@ const ed25519_public_key_t *node_get_ed25519_id(const node_t *node); int node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id); int node_supports_ed25519_link_authentication(const node_t *node); +int node_supports_v3_hsdir(const node_t *node); +int node_supports_ed25519_hs_intro(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); int node_has_ipv6_addr(const node_t *node); diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 294fc0df6d..c71fa236ed 100644 --- a/src/or/onion_tap.c +++ b/src/or/onion_tap.c @@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key, if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; - note_crypto_pk_op(ENC_ONIONSKIN); - /* set meeting point, meeting cookie, etc here. Leave zero for now. */ - if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, + if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) @@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake( k = i==0?private_key:prev_private_key; if (!k) break; - note_crypto_pk_op(DEC_ONIONSKIN); - len = crypto_pk_private_hybrid_decrypt(k, challenge, + len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge, TAP_ONIONSKIN_CHALLENGE_LEN, onion_skin, TAP_ONIONSKIN_CHALLENGE_LEN, diff --git a/src/or/or.h b/src/or/or.h index 77207bc031..f6c42b7a99 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -846,6 +846,11 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) return DOWNCAST(rend_data_v2_t, d); } +/* Stub because we can't include hs_ident.h. */ +struct hs_ident_edge_conn_t; +struct hs_ident_dir_conn_t; +struct hs_ident_circuit_t; + /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple * simultaneous attempts to connect to the same rendezvous point. */ @@ -1633,6 +1638,11 @@ typedef struct edge_connection_t { * an exit)? */ rend_data_t *rend_data; + /* Hidden service connection identifier for edge connections. Used by the HS + * client-side code to identify client SOCKS connections and by the + * service-side code to match HS circuits with their streams. */ + struct hs_ident_edge_conn_t *hs_ident; + uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit * connection. Exit connections only. */ uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell @@ -1783,6 +1793,11 @@ typedef struct dir_connection_t { /** What rendezvous service are we querying for? */ rend_data_t *rend_data; + /* Hidden service connection identifier for dir connections: Used by HS + client-side code to fetch HS descriptors, and by the service-side code to + upload descriptors. */ + struct hs_ident_dir_conn_t *hs_ident; + /** If this is a one-hop connection, tracks the state of the directory guard * for this connection (if any). */ struct circuit_guard_state_t *guard_state; @@ -2058,7 +2073,9 @@ typedef struct download_status_t { * or after each failure? */ download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the * deterministic schedule, or random - * exponential backoffs? */ + * exponential backoffs? + * Increment on failure schedules + * always use exponential backoff. */ uint8_t last_backoff_position; /**< number of attempts/failures, depending * on increment_on, when we last recalculated * the delay. Only updated if backoff @@ -3186,6 +3203,10 @@ typedef struct origin_circuit_t { /** Holds all rendezvous data on either client or service side. */ rend_data_t *rend_data; + /** Holds hidden service identifier on either client or service side. This + * is for both introduction and rendezvous circuit. */ + struct hs_ident_circuit_t *hs_ident; + /** Holds the data that the entry guard system uses to track the * status of the guard this circuit is using, and thereby to determine * whether this circuit can be used. */ diff --git a/src/or/protover.h b/src/or/protover.h index 22667bed79..2066aeec72 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -17,6 +17,11 @@ /* This is a guess. */ #define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha" +/** The protover version number that signifies HSDir support for HSv3 */ +#define PROTOVER_HSDIR_V3 2 +/** The protover version number that signifies HSv3 intro point support */ +#define PROTOVER_HS_INTRO_V3 4 + /** List of recognized subprotocols. */ typedef enum protocol_type_t { PRT_LINK, diff --git a/src/or/relay.c b/src/or/relay.c index 0ff53ed5e9..18ccc65b80 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -184,18 +184,12 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell) /** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b> * (in place). * - * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt. - * - * Returns 0. + * Note that we use the same operation for encrypting and for decrypting. */ -static int -relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in, - int encrypt_mode) +static void +relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in) { - (void)encrypt_mode; crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE); - - return 0; } /** @@ -449,8 +443,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, do { /* Remember: cpath is in forward order, that is, first hop first. */ tor_assert(thishop); - if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0) - return -1; + /* decrypt one layer */ + relay_crypt_one_payload(thishop->b_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -467,19 +461,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, log_fn(LOG_PROTOCOL_WARN, LD_OR, "Incoming cell at client not recognized. Closing."); return -1; - } else { /* we're in the middle. Just one crypt. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, - cell->payload, 1) < 0) - return -1; -// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not " -// "the client."); + } else { + /* We're in the middle. Encrypt one layer. */ + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload); } } else /* cell_direction == CELL_DIRECTION_OUT */ { - /* we're in the middle. Just one crypt. */ + /* We're in the middle. Decrypt one layer. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, - cell->payload, 0) < 0) - return -1; + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -525,11 +514,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, /* moving from farthest to nearest hop */ do { tor_assert(thishop); - /* XXXX RD This is a bug, right? */ - log_debug(LD_OR,"crypting a layer of the relay cell."); - if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) { - return -1; - } + log_debug(LD_OR,"encrypting a layer of the relay cell."); + relay_crypt_one_payload(thishop->f_crypto, cell->payload); thishop = thishop->prev; } while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev); @@ -546,8 +532,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, or_circ = TO_OR_CIRCUIT(circ); chan = or_circ->p_chan; relay_set_digest(or_circ->p_digest, cell); - if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0) - return -1; + /* encrypt one layer */ + relay_crypt_one_payload(or_circ->p_crypto, cell->payload); } ++stats_n_relay_cells_relayed; diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 9bc2d6289d..e47e1ef639 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -17,6 +17,7 @@ #include "connection_edge.h" #include "directory.h" #include "hs_common.h" +#include "hs_circuit.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -285,10 +286,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc, goto perm_err; } - note_crypto_pk_op(REND_CLIENT); - /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg, + /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg, * to avoid buffer overflows? */ - r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, + r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, sizeof(payload)-DIGEST_LEN, tmp, (int)(dh_offset+DH_KEY_LEN), @@ -1150,9 +1150,6 @@ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { - crypt_path_t *hop; - char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; - if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) || !circ->build_state->pending_final_cpath) { @@ -1170,55 +1167,13 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service."); - /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh - * handshake...*/ - tor_assert(circ->build_state); - tor_assert(circ->build_state->pending_final_cpath); - hop = circ->build_state->pending_final_cpath; - tor_assert(hop->rend_dh_handshake_state); - if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, - hop->rend_dh_handshake_state, (char*)request, - DH_KEY_LEN, - keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { - log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); goto err; } - /* ... and set up cpath. */ - if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0) - goto err; - - /* Check whether the digest is right... */ - if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) { - log_warn(LD_PROTOCOL, "Incorrect digest of key material."); - goto err; - } - - crypto_dh_free(hop->rend_dh_handshake_state); - hop->rend_dh_handshake_state = NULL; - - /* All is well. Extend the circuit. */ - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED); - hop->state = CPATH_STATE_OPEN; - /* set the windows to default. these are the windows - * that the client thinks the service has. - */ - hop->package_window = circuit_initial_package_window(); - hop->deliver_window = CIRCWINDOW_START; - - /* Now that this circuit has finished connecting to its destination, - * make sure circuit_get_open_circ_or_launch is willing to return it - * so we can actually use it. */ - circ->hs_circ_has_timed_out = 0; - - onion_append_to_cpath(&circ->cpath, hop); - circ->build_state->pending_final_cpath = NULL; /* prevent double-free */ - - circuit_try_attaching_streams(circ); - - memwipe(keys, 0, sizeof(keys)); return 0; + err: - memwipe(keys, 0, sizeof(keys)); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); return -1; } diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index e1236bdd0f..986bfde75f 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -8,6 +8,8 @@ * introducers, services, clients, and rendezvous points. **/ +#define RENDCOMMON_PRIVATE + #include "or.h" #include "circuitbuild.h" #include "config.h" @@ -395,7 +397,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out, /** Attempt to parse the given <b>desc_str</b> and return true if this * succeeds, false otherwise. */ -static int +STATIC int rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc) { rend_service_descriptor_t *test_parsed = NULL; diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 94c2480d86..292f9277e8 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -63,5 +63,12 @@ int rend_non_anonymous_mode_enabled(const or_options_t *options); void assert_circ_anonymity_ok(origin_circuit_t *circ, const or_options_t *options); +#ifdef RENDCOMMON_PRIVATE + +STATIC int +rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc); + +#endif + #endif diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 23c3deddaa..66d2f93113 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -71,7 +71,6 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, goto err; } /* Rest of body: signature of previous data */ - note_crypto_pk_op(REND_MID); if (crypto_pk_public_checksig_digest(pk, (char*)request, 2+asn1len+DIGEST_LEN, (char*)(request+2+DIGEST_LEN+asn1len), diff --git a/src/or/rendservice.c b/src/or/rendservice.c index f3b78c4663..98ed1100ec 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -18,6 +18,7 @@ #include "control.h" #include "directory.h" #include "hs_common.h" +#include "hs_config.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -231,18 +232,41 @@ rend_service_free(rend_service_t *service) tor_free(service); } -/** Release all the storage held in rend_service_list. - */ +/* Release all the storage held in rend_service_staging_list. */ +void +rend_service_free_staging_list(void) +{ + if (rend_service_staging_list) { + SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_staging_list); + rend_service_staging_list = NULL; + } +} + +/** Release all the storage held in both rend_service_list and + * rend_service_staging_list. */ void rend_service_free_all(void) { - if (!rend_service_list) - return; + if (rend_service_list) { + SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_list); + rend_service_list = NULL; + } + rend_service_free_staging_list(); +} - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_list); - rend_service_list = NULL; +/* Initialize the subsystem. */ +void +rend_service_init(void) +{ + tor_assert(!rend_service_list); + tor_assert(!rend_service_staging_list); + + rend_service_list = smartlist_new(); + rend_service_staging_list = smartlist_new(); } /* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there @@ -252,8 +276,6 @@ static int rend_validate_service(const smartlist_t *service_list, const rend_service_t *service) { - int dupe = 0; - tor_assert(service_list); tor_assert(service); @@ -286,34 +308,6 @@ rend_validate_service(const smartlist_t *service_list, goto invalid; } - /* XXX This duplicate check has two problems: - * - * a) It's O(n^2), but the same comment from the bottom of - * rend_config_services() should apply. - * - * b) We only compare directory paths as strings, so we can't - * detect two distinct paths that specify the same directory - * (which can arise from symlinks, case-insensitivity, bind - * mounts, etc.). - * - * It also can't detect that two separate Tor instances are trying - * to use the same HiddenServiceDir; for that, we would need a - * lock file. But this is enough to detect a simple mistake that - * at least one person has actually made. - */ - if (!rend_service_is_ephemeral(service)) { - /* Skip dupe for ephemeral services. */ - SMARTLIST_FOREACH(service_list, rend_service_t *, ptr, - dupe = dupe || - !strcmp(ptr->directory, service->directory)); - if (dupe) { - log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s.", - rend_service_escaped_dir(service)); - goto invalid; - } - } - /* Valid. */ return 0; invalid: @@ -335,6 +329,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) /* We must have a service list, even if it's a temporary one, so we can * check for duplicate services */ if (BUG(!s_list)) { + rend_service_free(service); return -1; } @@ -496,41 +491,6 @@ rend_service_port_config_free(rend_service_port_config_t *p) tor_free(p); } -/* Check the directory for <b>service</b>, and add the service to - * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL. - * Only add the service to the list if <b>validate_only</b> is false. - * If <b>validate_only</b> is true, free the service. - * If <b>service</b> is NULL, ignore it, and return 0. - * Returns 0 on success, and -1 on failure. - * Takes ownership of <b>service</b>, either freeing it, or adding it to the - * global service list. - */ -STATIC int -rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only) -{ - if (!service) { - /* It is ok for a service to be NULL, this means there are no services */ - return 0; - } - - if (rend_service_check_private_dir(options, service, !validate_only) - < 0) { - rend_service_free(service); - return -1; - } - - smartlist_t *s_list = rend_get_service_list_mutable(service_list); - /* We must have a service list, even if it's a temporary one, so we can - * check for duplicate services */ - if (BUG(!s_list)) { - return -1; - } - return rend_add_service(s_list, service); -} - /* Helper: Actual implementation of the pruning on reload which we've * decoupled in order to make the unit test workeable without ugly hacks. * Furthermore, this function does NOT free any memory but will nullify the @@ -657,19 +617,54 @@ rend_service_prune_list(void) } } -/** Set up rend_service_list, based on the values of HiddenServiceDir and - * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on - * failure. (If <b>validate_only</b> is set, parse, warn and return as - * normal, but don't actually change the configured services.) - */ +/* Copy all the relevant data that the hs_service object contains over to the + * rend_service_t object. The reason to do so is because when configuring a + * service, we go through a generic handler that creates an hs_service_t + * object which so we have to copy the parsed values to a rend service object + * which is version 2 specific. */ +static void +service_config_shadow_copy(rend_service_t *service, + hs_service_config_t *config) +{ + tor_assert(service); + tor_assert(config); + + service->directory = tor_strdup(config->directory_path); + service->dir_group_readable = config->dir_group_readable; + service->allow_unknown_ports = config->allow_unknown_ports; + /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535) + * if the code flow is right so this cast is safe. But just in case, we'll + * check it. */ + service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit; + if (BUG(config->max_streams_per_rdv_circuit > + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) { + service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT; + } + service->max_streams_close_circuit = config->max_streams_close_circuit; + service->n_intro_points_wanted = config->num_intro_points; + /* Switching ownership of the ports to the rend service object. */ + smartlist_add_all(service->ports, config->ports); + smartlist_free(config->ports); + config->ports = NULL; +} + +/* Parse the hidden service configuration starting at <b>line_</b> using the + * already configured generic service configuration in <b>config</b>. This + * function will translate the config object to a rend_service_t and add it to + * the temporary list if valid. If <b>validate_only</b> is set, parse, warn + * and return as normal but don't actually add the service to the list. */ int -rend_config_services(const or_options_t *options, int validate_only) +rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config) { - config_line_t *line; + const config_line_t *line; rend_service_t *service = NULL; - rend_service_port_config_t *portcfg; - int ok = 0; - int rv = -1; + + /* line_ can be NULL which would mean that the service configuration only + * have one line that is the directory directive. */ + tor_assert(options); + tor_assert(config); /* Use the staging service list so that we can check then do the pruning * process using the main list at the end. */ @@ -677,100 +672,23 @@ rend_config_services(const or_options_t *options, int validate_only) rend_service_staging_list = smartlist_new(); } - for (line = options->RendConfigLines; line; line = line->next) { + /* Initialize service. */ + service = tor_malloc_zero(sizeof(rend_service_t)); + service->intro_period_started = time(NULL); + service->ports = smartlist_new(); + /* From the hs_service object which has been used to load the generic + * options, we'll copy over the useful data to the rend_service_t object. */ + service_config_shadow_copy(service, config); + + for (line = line_; line; line = line->next) { if (!strcasecmp(line->key, "HiddenServiceDir")) { - if (service) { - /* Validate and register the service we just finished parsing this - * code registers every service except the last one parsed, which is - * validated and registered below the loop. */ - if (rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* The above frees the service on error so nullify the pointer. */ - service = NULL; - goto free_and_return; - } - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - service->ports = smartlist_new(); - service->intro_period_started = time(NULL); - service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; - continue; - } - if (!service) { - log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", - line->key); - goto free_and_return; + /* We just hit the next hidden service, stop right now. */ + break; } - if (!strcasecmp(line->key, "HiddenServicePort")) { - char *err_msg = NULL; - portcfg = rend_service_parse_port_config(line->value, " ", &err_msg); - if (!portcfg) { - if (err_msg) - log_warn(LD_CONFIG, "%s", err_msg); - tor_free(err_msg); - goto free_and_return; - } - tor_assert(!err_msg); - smartlist_add(service->ports, portcfg); - } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { - service->allow_unknown_ports = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceAllowUnknownPorts=%d for %s", - (int)service->allow_unknown_ports, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, - "HiddenServiceDirGroupReadable")) { - service->dir_group_readable = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceDirGroupReadable should be 0 or 1, not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceDirGroupReadable=%d for %s", - service->dir_group_readable, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { - service->max_streams_per_circuit = (int)tor_parse_long(line->value, - 10, 0, 65535, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreams should be between 0 and %d, not %s", - 65535, line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreams=%d for %s", - service->max_streams_per_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { - service->max_streams_close_circuit = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, " - "not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit=%d for %s", - (int)service->max_streams_close_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + int ok = 0; + /* Those are specific defaults for version 2. */ service->n_intro_points_wanted = (unsigned int) tor_parse_long(line->value, 10, 0, NUM_INTRO_POINTS_MAX, &ok, NULL); @@ -779,12 +697,13 @@ rend_config_services(const or_options_t *options, int validate_only) "HiddenServiceNumIntroductionPoints " "should be between %d and %d, not %s", 0, NUM_INTRO_POINTS_MAX, line->value); - goto free_and_return; + goto err; } log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s", - service->n_intro_points_wanted, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + service->n_intro_points_wanted, escaped(service->directory)); + continue; + } + if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { /* Parse auth type and comma-separated list of client names and add a * rend_authorized_client_t for each client to the service's list * of authorized clients. */ @@ -794,7 +713,7 @@ rend_config_services(const or_options_t *options, int validate_only) if (service->auth_type != REND_NO_AUTH) { log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " "lines for a single service."); - goto free_and_return; + goto err; } type_names_split = smartlist_new(); smartlist_split_string(type_names_split, line->value, " ", 0, 2); @@ -802,7 +721,8 @@ rend_config_services(const or_options_t *options, int validate_only) log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " "should have been prevented when parsing the " "configuration."); - goto free_and_return; + smartlist_free(type_names_split); + goto err; } authname = smartlist_get(type_names_split, 0); if (!strcasecmp(authname, "basic")) { @@ -816,7 +736,7 @@ rend_config_services(const or_options_t *options, int validate_only) (char *) smartlist_get(type_names_split, 0)); SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); smartlist_free(type_names_split); - goto free_and_return; + goto err; } service->clients = smartlist_new(); if (smartlist_len(type_names_split) < 2) { @@ -853,7 +773,7 @@ rend_config_services(const or_options_t *options, int validate_only) client_name, REND_CLIENTNAME_MAX_LEN); SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); smartlist_free(clients); - goto free_and_return; + goto err; } client = tor_malloc_zero(sizeof(rend_authorized_client_t)); client->client_name = tor_strdup(client_name); @@ -875,56 +795,29 @@ rend_config_services(const or_options_t *options, int validate_only) smartlist_len(service->clients), service->auth_type == REND_BASIC_AUTH ? 512 : 16, service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - goto free_and_return; - } - } else { - tor_assert(!strcasecmp(line->key, "HiddenServiceVersion")); - if (strcmp(line->value, "2")) { - log_warn(LD_CONFIG, - "The only supported HiddenServiceVersion is 2."); - goto free_and_return; + goto err; } + continue; } } - /* Validate the last service that we just parsed. */ - if (service && - rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - /* register the final service after we have finished parsing all services - * this code only registers the last service, other services are registered - * within the loop. It is ok for this service to be NULL, it is ignored. */ - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* Service object is freed on error so nullify pointer. */ - service = NULL; - goto free_and_return; + /* Validate the service just parsed. */ + if (rend_validate_service(rend_service_staging_list, service) < 0) { + /* Service is in the staging list so don't try to free it. */ + goto err; } - /* The service is in the staging list so nullify pointer to avoid double - * free of this object in case of error because we lost ownership of it at - * this point. */ - service = NULL; - /* Free the newly added services if validating */ - if (validate_only) { - rv = 0; - goto free_and_return; + /* Add it to the temporary list which we will use to prune our current + * list if any after configuring all services. */ + if (rend_add_service(rend_service_staging_list, service) < 0) { + /* The object has been freed on error already. */ + service = NULL; + goto err; } - /* This could be a reload of configuration so try to prune the main list - * using the staging one. And we know we are not in validate mode here. - * After this, the main and staging list will point to the right place and - * be in a quiescent usable state. */ - rend_service_prune_list(); - return 0; - free_and_return: + err: rend_service_free(service); - SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_staging_list); - rend_service_staging_list = NULL; - return rv; + return -1; } /** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using @@ -1170,15 +1063,8 @@ rend_service_update_descriptor(rend_service_t *service) static char * rend_service_path(const rend_service_t *service, const char *file_name) { - char *file_path = NULL; - tor_assert(service->directory); - - /* Can never fail: asserts rather than leaving file_path NULL. */ - tor_asprintf(&file_path, "%s%s%s", - service->directory, PATH_SEPARATOR, file_name); - - return file_path; + return hs_path_from_filename(service->directory, file_name); } /* Allocate and return a string containing the path to the single onion @@ -1548,9 +1434,9 @@ rend_service_load_keys(rend_service_t *s) char *fname = NULL; char buf[128]; - /* Make sure the directory was created and single onion poisoning was - * checked before calling this function */ - if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0)) + /* Create the directory if needed which will also poison it in case of + * single onion service. */ + if (rend_service_check_private_dir(get_options(), s, 1) < 0) goto err; /* Load key */ @@ -2195,7 +2081,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit, cpath->rend_dh_handshake_state = dh; dh = NULL; - if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0) + if (circuit_init_cpath_crypto(cpath, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 1, 0)<0) goto err; memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN); @@ -2843,10 +2731,8 @@ rend_service_decrypt_intro( } /* Decrypt the encrypted part */ - - note_crypto_pk_op(REND_SERVER); result = - crypto_pk_private_hybrid_decrypt( + crypto_pk_obsolete_private_hybrid_decrypt( key, (char *)buf, sizeof(buf), (const char *)(intro->ciphertext), intro->ciphertext_len, PK_PKCS1_OAEP_PADDING, 1); @@ -3258,7 +3144,6 @@ encode_establish_intro_cell_legacy(char *cell_body_out, if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9)) goto err; len += 20; - note_crypto_pk_op(REND_SERVER); r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len, cell_body_out_len - len, cell_body_out, len); diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 1583a6010b..ffed21d14e 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -13,6 +13,7 @@ #define TOR_RENDSERVICE_H #include "or.h" +#include "hs_service.h" typedef struct rend_intro_cell_s rend_intro_cell_t; typedef struct rend_service_port_config_s rend_service_port_config_t; @@ -119,10 +120,6 @@ typedef struct rend_service_t { STATIC void rend_service_free(rend_service_t *service); STATIC char *rend_service_sos_poison_path(const rend_service_t *service); -STATIC int rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only); STATIC int rend_service_verify_single_onion_poison( const rend_service_t *s, const or_options_t *options); @@ -144,8 +141,11 @@ STATIC void rend_service_prune_list_impl_(void); #endif /* RENDSERVICE_PRIVATE */ int num_rend_services(void); -int rend_config_services(const or_options_t *options, int validate_only); +int rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config); void rend_service_prune_list(void); +void rend_service_free_staging_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); @@ -179,6 +179,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn, origin_circuit_t *circ); void rend_service_dump_stats(int severity); void rend_service_free_all(void); +void rend_service_init(void); rend_service_port_config_t *rend_service_parse_port_config(const char *string, const char *sep, diff --git a/src/or/rephist.c b/src/or/rephist.c index 72a5cc5a9b..e65b93fa76 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -2064,105 +2064,6 @@ rep_hist_circbuilding_dormant(time_t now) return 1; } -/** Structure to track how many times we've done each public key operation. */ -static struct { - /** How many directory objects have we signed? */ - unsigned long n_signed_dir_objs; - /** How many routerdescs have we signed? */ - unsigned long n_signed_routerdescs; - /** How many directory objects have we verified? */ - unsigned long n_verified_dir_objs; - /** How many routerdescs have we verified */ - unsigned long n_verified_routerdescs; - /** How many onionskins have we encrypted to build circuits? */ - unsigned long n_onionskins_encrypted; - /** How many onionskins have we decrypted to do circuit build requests? */ - unsigned long n_onionskins_decrypted; - /** How many times have we done the TLS handshake as a client? */ - unsigned long n_tls_client_handshakes; - /** How many times have we done the TLS handshake as a server? */ - unsigned long n_tls_server_handshakes; - /** How many PK operations have we done as a hidden service client? */ - unsigned long n_rend_client_ops; - /** How many PK operations have we done as a hidden service midpoint? */ - unsigned long n_rend_mid_ops; - /** How many PK operations have we done as a hidden service provider? */ - unsigned long n_rend_server_ops; -} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0}; - -/** Increment the count of the number of times we've done <b>operation</b>. */ -void -note_crypto_pk_op(pk_op_t operation) -{ - switch (operation) - { - case SIGN_DIR: - pk_op_counts.n_signed_dir_objs++; - break; - case SIGN_RTR: - pk_op_counts.n_signed_routerdescs++; - break; - case VERIFY_DIR: - pk_op_counts.n_verified_dir_objs++; - break; - case VERIFY_RTR: - pk_op_counts.n_verified_routerdescs++; - break; - case ENC_ONIONSKIN: - pk_op_counts.n_onionskins_encrypted++; - break; - case DEC_ONIONSKIN: - pk_op_counts.n_onionskins_decrypted++; - break; - case TLS_HANDSHAKE_C: - pk_op_counts.n_tls_client_handshakes++; - break; - case TLS_HANDSHAKE_S: - pk_op_counts.n_tls_server_handshakes++; - break; - case REND_CLIENT: - pk_op_counts.n_rend_client_ops++; - break; - case REND_MID: - pk_op_counts.n_rend_mid_ops++; - break; - case REND_SERVER: - pk_op_counts.n_rend_server_ops++; - break; - default: - log_warn(LD_BUG, "Unknown pk operation %d", operation); - } -} - -/** Log the number of times we've done each public/private-key operation. */ -void -dump_pk_ops(int severity) -{ - tor_log(severity, LD_HIST, - "PK operations: %lu directory objects signed, " - "%lu directory objects verified, " - "%lu routerdescs signed, " - "%lu routerdescs verified, " - "%lu onionskins encrypted, " - "%lu onionskins decrypted, " - "%lu client-side TLS handshakes, " - "%lu server-side TLS handshakes, " - "%lu rendezvous client operations, " - "%lu rendezvous middle operations, " - "%lu rendezvous server operations.", - pk_op_counts.n_signed_dir_objs, - pk_op_counts.n_verified_dir_objs, - pk_op_counts.n_signed_routerdescs, - pk_op_counts.n_verified_routerdescs, - pk_op_counts.n_onionskins_encrypted, - pk_op_counts.n_onionskins_decrypted, - pk_op_counts.n_tls_client_handshakes, - pk_op_counts.n_tls_server_handshakes, - pk_op_counts.n_rend_client_ops, - pk_op_counts.n_rend_mid_ops, - pk_op_counts.n_rend_server_ops); -} - /*** Exit port statistics ***/ /* Some constants */ diff --git a/src/or/rephist.h b/src/or/rephist.h index 2b1c2e7ec7..8f6d46616d 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -62,9 +62,6 @@ int any_predicted_circuits(time_t now); int rep_hist_circbuilding_dormant(time_t now); int predicted_ports_prediction_time_remaining(time_t now); -void note_crypto_pk_op(pk_op_t operation); -void dump_pk_ops(int severity); - void rep_hist_exit_stats_init(time_t now); void rep_hist_reset_exit_stats(time_t now); void rep_hist_exit_stats_term(void); diff --git a/src/or/router.c b/src/or/router.c index 2187a76b48..1b81a228af 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1065,7 +1065,7 @@ init_keys(void) /* 4. Build our router descriptor. */ /* Must be called after keys are initialized. */ mydesc = router_get_my_descriptor(); - if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) { + if (authdir_mode_v3(options)) { const char *m = NULL; routerinfo_t *ri; /* We need to add our own fingerprint so it gets recognized. */ @@ -1596,32 +1596,19 @@ authdir_mode_v3(const or_options_t *options) { return authdir_mode(options) && options->V3AuthoritativeDir != 0; } -/** Return true iff we are a v3 directory authority. */ -int -authdir_mode_any_main(const or_options_t *options) -{ - return options->V3AuthoritativeDir; -} -/** Return true if we believe ourselves to be any kind of - * authoritative directory beyond just a hidserv authority. */ -int -authdir_mode_any_nonhidserv(const or_options_t *options) -{ - return options->BridgeAuthoritativeDir || - authdir_mode_any_main(options); -} /** Return true iff we are an authoritative directory server that is * authoritative about receiving and serving descriptors of type - * <b>purpose</b> on its dirport. Use -1 for "any purpose". */ + * <b>purpose</b> on its dirport. + */ int authdir_mode_handles_descs(const or_options_t *options, int purpose) { - if (purpose < 0) - return authdir_mode_any_nonhidserv(options); + if (BUG(purpose < 0)) /* Deprecated. */ + return authdir_mode(options); else if (purpose == ROUTER_PURPOSE_GENERAL) - return authdir_mode_any_main(options); + return authdir_mode_v3(options); else if (purpose == ROUTER_PURPOSE_BRIDGE) - return (options->BridgeAuthoritativeDir); + return authdir_mode_bridge(options); else return 0; } @@ -1633,7 +1620,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) { if (authdir_mode_bridge(options)) return 0; - return authdir_mode_any_nonhidserv(options); + return authdir_mode(options); } /** Return true iff we are an authoritative directory server that * tests reachability of the descriptors it learns about. @@ -1641,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) int authdir_mode_tests_reachability(const or_options_t *options) { - return authdir_mode_handles_descs(options, -1); + return authdir_mode(options); } /** Return true iff we believe ourselves to be a bridge authoritative * directory server. @@ -3022,7 +3009,6 @@ router_dump_router_to_string(routerinfo_t *router, crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); - note_crypto_pk_op(SIGN_RTR); { char *sig; if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) { diff --git a/src/or/router.h b/src/or/router.h index 9c5def5218..97f331713a 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -54,8 +54,6 @@ int net_is_disabled(void); int authdir_mode(const or_options_t *options); int authdir_mode_v3(const or_options_t *options); -int authdir_mode_any_main(const or_options_t *options); -int authdir_mode_any_nonhidserv(const or_options_t *options); int authdir_mode_handles_descs(const or_options_t *options, int purpose); int authdir_mode_publishes_statuses(const or_options_t *options); int authdir_mode_tests_reachability(const or_options_t *options); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 0e45f63f70..49caa875fe 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -5033,7 +5033,7 @@ launch_descriptor_downloads(int purpose, } } - if (!authdir_mode_any_nonhidserv(options)) { + if (!authdir_mode(options)) { /* If we wind up going to the authorities, we want to only open one * connection to each authority at a time, so that we don't overload * them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH @@ -5055,8 +5055,9 @@ launch_descriptor_downloads(int purpose, if (n_per_request > max_dl_per_req) n_per_request = max_dl_per_req; - if (n_per_request < MIN_DL_PER_REQUEST) - n_per_request = MIN_DL_PER_REQUEST; + if (n_per_request < MIN_DL_PER_REQUEST) { + n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable); + } if (n_downloadable > n_per_request) req_plural = rtr_plural = "s"; @@ -5164,7 +5165,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_add(downloadable, rs->descriptor_digest); } SMARTLIST_FOREACH_END(rsp); - if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) + if (!authdir_mode_v3(options) && smartlist_len(no_longer_old)) { routerlist_t *rl = router_get_routerlist(); log_info(LD_DIR, "%d router descriptors listed in consensus are " diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 0f6113ccfc..8823c0b53e 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -1996,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end, } tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); #ifdef COUNT_DISTINCT_DIGESTS if (!verified_digests) verified_digests = digestmap_new(); @@ -2231,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, } if (key) { - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(digest, DIGEST_LEN, tok, key, 0, "extra-info") < 0) goto err; @@ -2856,7 +2854,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b) int networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) { - int64_t weight_scale; int64_t G=0, M=0, E=0, D=0, T=0; double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed; double Gtotal=0, Mtotal=0, Etotal=0; @@ -2864,7 +2861,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) int valid = 1; (void) consensus_method; - weight_scale = networkstatus_get_weight_scale_param(ns); + const int64_t weight_scale = networkstatus_get_weight_scale_param(ns); + tor_assert(weight_scale >= 1); Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1); Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1); @@ -3360,8 +3358,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) voter_identity = "consensus"; } - /* We extract both and on error, everything is stopped because it means - * the votes is malformed for the shared random value(s). */ + /* We extract both, and on error everything is stopped because it means + * the vote is malformed for the shared random value(s). */ if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", voter_identity); @@ -5288,7 +5286,6 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, } /* Parse and verify signature. */ tok = find_by_keyword(tokens, R_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0, "v2 rendezvous service descriptor") < 0) goto err; diff --git a/src/or/torcert.c b/src/or/torcert.c index 658e620ca5..69b157446a 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -393,7 +393,7 @@ rsa_ed25519_crosscert_check(const uint8_t *crosscert, } const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc); - const uint64_t expiration_time = expiration_date * 3600; + const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600; if (reject_if_expired_before < 0 || expiration_time < (uint64_t)reject_if_expired_before) { |