diff options
author | Nick Mathewson <nickm@torproject.org> | 2017-08-08 20:31:57 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2017-08-08 20:31:57 -0400 |
commit | 34e4122025791d1a607426e5e543d6312872b9dd (patch) | |
tree | 975c1dc949d81ed7a76f50726a49d4aa8e67533e /src/or | |
parent | 649104fdb950057288a5e7b48f402d563546b83b (diff) | |
parent | 2f17743d6f7222cf96250890dd91d6689b2d55c6 (diff) | |
download | tor-34e4122025791d1a607426e5e543d6312872b9dd.tar.gz tor-34e4122025791d1a607426e5e543d6312872b9dd.zip |
Merge branch 'ticket20657_nickm_bugfixes_squashed'
Diffstat (limited to 'src/or')
39 files changed, 5858 insertions, 542 deletions
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index d11e128787..d891c89f38 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -65,6 +65,7 @@ #include "control.h" #include "entrynodes.h" #include "main.h" +#include "hs_circuit.h" #include "hs_circuitmap.h" #include "hs_common.h" #include "hs_ident.h" @@ -1532,6 +1533,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start) return NULL; } +/** Return the first service rendezvous circuit originating from the global + * circuit list after <b>start</b> or at the start of the list if <b>start</b> + * is NULL. Return NULL if no circuit is found. + * + * A service rendezvous point circuit has a purpose of either + * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does + * not return a circuit marked for close and its state must be open. */ +origin_circuit_t * +circuit_get_next_service_rp_circ(origin_circuit_t *start) +{ + int idx = 0; + smartlist_t *lst = circuit_get_global_list(); + + if (start) { + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; + } + + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); + + /* Ignore a marked for close circuit or purpose not matching a service + * intro point or if the state is not open. */ + if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN || + (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND && + circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) { + continue; + } + /* The purposes we are looking for are only for origin circuits so the + * following is valid. */ + return TO_ORIGIN_CIRCUIT(circ); + } + /* Not found. */ + return NULL; +} + /** Return the first circuit originating here in global_circuitlist after * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if * set) matches the private key digest of the rend data associated with the @@ -1913,6 +1949,13 @@ circuit_about_to_free(circuit_t *circ) orig_reason); } + /* Notify the HS subsystem for any intro point circuit closing so it can be + * dealt with cleanly. */ + if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { + hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + } + if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); int timed_out = (reason == END_CIRC_REASON_TIMEOUT); diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 2f76252563..048cd5f763 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_rp_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); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index a3b7066b18..21cc9c540f 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -43,6 +43,7 @@ #include "entrynodes.h" #include "hs_common.h" #include "hs_client.h" +#include "hs_circuit.h" #include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" @@ -782,7 +783,7 @@ circuit_expire_building(void) victim->state, circuit_state_to_string(victim->state), victim->purpose); TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1; - rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim)); + hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim)); continue; } @@ -1113,11 +1114,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) /* Return true if we need any more hidden service server circuits. * HS servers only need an internal circuit. */ STATIC int -needs_hs_server_circuits(int num_uptime_internal) +needs_hs_server_circuits(time_t now, int num_uptime_internal) { - return (num_rend_services() && - num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && - router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); + if (!rend_num_services() && !hs_service_get_num_services()) { + /* No services, we don't need anything. */ + goto no_need; + } + + if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) { + /* We have sufficient amount of internal circuit. */ + goto no_need; + } + + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) { + /* Consensus hasn't been checked or might be invalid so requesting + * internal circuits is not wise. */ + goto no_need; + } + + /* At this point, we need a certain amount of circuits and we will most + * likely use them for rendezvous so we note down the use of internal + * circuit for our prediction for circuit needing uptime and capacity. */ + rep_hist_note_used_internal(now, 1, 1); + + return 1; + no_need: + return 0; } /* We need at least this many internal circuits for hidden service clients */ @@ -1216,7 +1238,7 @@ circuit_predict_and_launch_new(void) return; } - if (needs_hs_server_circuits(num_uptime_internal)) { + if (needs_hs_server_circuits(now, num_uptime_internal)) { flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL); @@ -1280,11 +1302,6 @@ circuit_build_needed_circs(time_t now) if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) connection_ap_rescan_and_attach_pending(); - /* make sure any hidden services have enough intro points - * HS intro point streams only require an internal circuit */ - if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) - rend_consider_services_intro_points(); - circuit_expire_old_circs_as_needed(now); if (!options->DisablePredictedCircuits) @@ -1366,8 +1383,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) * number of streams on the circuit associated with the rend service. */ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ->rend_data); - origin_circ->rend_data->nr_streams--; + hs_dec_rdv_stream_counter(origin_circ); } return; } @@ -1641,11 +1657,11 @@ circuit_has_opened(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at the service, waiting for introductions */ - rend_service_intro_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_S_CONNECT_REND: /* at the service, connecting to rend point */ - rend_service_rendezvous_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_TESTING: circuit_testing_opened(circ); @@ -1795,7 +1811,7 @@ circuit_build_failed(origin_circuit_t *circ) "(%s hop failed).", escaped(build_state_get_exit_nickname(circ->build_state)), failed_at_last_hop?"last":"non-last"); - rend_service_relaunch_rendezvous(circ); + hs_circ_retry_service_rendezvous_point(circ); break; /* default: * This won't happen in normal operation, but might happen if the diff --git a/src/or/circuituse.h b/src/or/circuituse.h index ad4c214a3b..e66679586d 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -68,7 +68,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ); STATIC int needs_exit_circuits(time_t now, int *port_needs_uptime, int *port_needs_capacity); -STATIC int needs_hs_server_circuits(int num_uptime_internal); +STATIC int needs_hs_server_circuits(time_t now, + int num_uptime_internal); STATIC int needs_hs_client_circuits(time_t now, int *needs_uptime, diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index ddcff6aa94..12ddc7e829 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,6 +76,7 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_circuit.h" #include "main.h" #include "nodelist.h" #include "policies.h" @@ -3066,6 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } +/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ +static int +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) +{ + int ret; + origin_circuit_t *origin_circ; + + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); + + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert_nonfatal_unreached(); + return -1; + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); + /* Send back reason DONE because we want to make hidden service port + * scanning harder thus instead of returning that the exit policy + * didn't match, which makes it obvious that the port is closed, + * return DONE and kill the circuit. That way, a user (malicious or + * not) needs one circuit per bad port unless it matches the policy of + * the hidden service. */ + relay_send_end_cell_from_edge(conn->stream_id, circ, + END_STREAM_REASON_DONE, + origin_circ->cpath->prev); + connection_free(TO_CONN(conn)); + + /* Drop the circuit here since it might be someone deliberately + * scanning the hidden service ports. Note that this mitigates port + * scanning by adding more work on the attacker side to successfully + * scan but does not fully solve it. */ + if (ret < -1) { + return END_CIRC_AT_ORIGIN; + } else { + return 0; + } + } + + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; + assert_circuit_ok(circ); + + hs_inc_rdv_stream_counter(origin_circ); + + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); + + /* For path bias: This circuit was used successfully */ + pathbias_mark_use_success(origin_circ); + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3217,58 +3300,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->deliver_window = STREAMWINDOW_START; if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); - assert_circuit_ok(circ); - - const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); - if (r < 0) { - log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->base_.port); - /* Send back reason DONE because we want to make hidden service port - * scanning harder thus instead of returning that the exit policy - * didn't match, which makes it obvious that the port is closed, - * return DONE and kill the circuit. That way, a user (malicious or - * not) needs one circuit per bad port unless it matches the policy of - * the hidden service. */ - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_DONE, - layer_hint); - connection_free(TO_CONN(n_stream)); - tor_free(address); - - /* Drop the circuit here since it might be someone deliberately - * scanning the hidden service ports. Note that this mitigates port - * scanning by adding more work on the attacker side to successfully - * scan but does not fully solve it. */ - if (r < -1) - return END_CIRC_AT_ORIGIN; - else - return 0; - } - assert_circuit_ok(circ); - log_debug(LD_REND,"Finished assigning addr/port"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; - assert_circuit_ok(circ); - - origin_circ->rend_data->nr_streams++; - - connection_exit_connect(n_stream); - - /* For path bias: This circuit was used successfully */ - pathbias_mark_use_success(origin_circ); - tor_free(address); - return 0; + /* We handle this circuit and stream in this function for all supported + * hidden service version. */ + return handle_hs_exit_conn(circ, n_stream); } tor_strlower(address); n_stream->base_.address = address; diff --git a/src/or/directory.c b/src/or/directory.c index 13daea354c..e079a5941f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -186,6 +186,8 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: case DIR_PURPOSE_UPLOAD_RENDDESC_V2: case DIR_PURPOSE_FETCH_RENDDESC_V2: + case DIR_PURPOSE_FETCH_HSDESC: + case DIR_PURPOSE_UPLOAD_HSDESC: return 1; case DIR_PURPOSE_SERVER: default: @@ -244,6 +246,10 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_HSDESC: + return "hidden-service descriptor fetch"; + case DIR_PURPOSE_UPLOAD_HSDESC: + return "hidden-service descriptor upload"; case DIR_PURPOSE_FETCH_MICRODESC: return "microdescriptor fetch"; } @@ -1034,11 +1040,12 @@ struct directory_request_t { size_t payload_len; /** Value to send in an if-modified-since header, or 0 for none. */ time_t if_modified_since; - /** Hidden-service-specific information */ + /** Hidden-service-specific information v2. */ const rend_data_t *rend_query; /** Extra headers to append to the request */ config_line_t *additional_headers; - /** */ + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; /** Used internally to directory.c: gets informed when the attempt to * connect to the directory succeeds or fails, if that attempt bears on the * directory's usability as a directory guard. */ @@ -1268,6 +1275,20 @@ directory_request_set_rend_query(directory_request_t *req, } req->rend_query = query; } +/** + * Set an object containing HS connection identifier to be associated with + * this request. Note that only an alias to <b>ident</b> is stored, so the + * <b>ident</b> object must outlive the request. + */ +void +directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); + } + req->hs_ident = ident; +} /** Set a static circuit_guard_state_t object to affliate with the request in * <b>req</b>. This object will receive notification when the attempt to * connect to the guard either succeeds or fails. */ @@ -1389,6 +1410,7 @@ directory_initiate_request,(directory_request_t *request)) const dir_indirection_t indirection = request->indirection; const char *resource = request->resource; const rend_data_t *rend_query = request->rend_query; + const hs_ident_dir_conn_t *hs_ident = request->hs_ident; circuit_guard_state_t *guard_state = request->guard_state; tor_assert(or_addr_port->port || dir_addr_port->port); @@ -1476,8 +1498,16 @@ directory_initiate_request,(directory_request_t *request)) conn->dirconn_direct = !anonymized_connection; /* copy rendezvous data, if any */ - if (rend_query) + if (rend_query) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!hs_ident); conn->rend_data = rend_data_dup(rend_query); + } + if (hs_ident) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!rend_query); + conn->hs_ident = hs_ident_dir_conn_dup(hs_ident); + } if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ @@ -1835,6 +1865,12 @@ directory_send_command(dir_connection_t *conn, httpcommand = "POST"; url = tor_strdup("/tor/rendezvous2/publish"); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + tor_assert(resource); + tor_assert(payload); + httpcommand = "POST"; + tor_asprintf(&url, "/tor/hs/%s/publish", resource); + break; default: tor_assert(0); return; @@ -2189,6 +2225,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *, const response_handler_args_t *); static int handle_response_upload_renddesc_v2(dir_connection_t *, const response_handler_args_t *); +static int handle_response_upload_hsdesc(dir_connection_t *, + const response_handler_args_t *); static int dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, @@ -2489,6 +2527,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case DIR_PURPOSE_UPLOAD_RENDDESC_V2: rv = handle_response_upload_renddesc_v2(conn, &args); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + rv = handle_response_upload_hsdesc(conn, &args); + break; default: tor_assert_nonfatal_unreached(); rv = -1; @@ -3180,6 +3221,52 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn, return 0; } +/** + * Handler function: processes a response to a POST request to upload an + * hidden service descriptor. + **/ +static int +handle_response_upload_hsdesc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + + tor_assert(conn); + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC); + + log_info(LD_REND, "Uploaded hidden service descriptor (status %d " + "(%s))", + status_code, escaped(reason)); + /* For this directory response, it MUST have an hidden service identifier on + * this connection. */ + tor_assert(conn->hs_ident); + switch (status_code) { + case 200: + log_info(LD_REND, "Uploading hidden service descriptor: " + "finished with status 200 (%s)", escaped(reason)); + /* XXX: Trigger control event. */ + break; + case 400: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + /* XXX: Trigger control event. */ + break; + default: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + /* XXX: Trigger control event. */ + break; + } + + return 0; +} + /** Called when a directory connection reaches EOF. */ int connection_dir_reached_eof(dir_connection_t *conn) diff --git a/src/or/directory.h b/src/or/directory.h index 3e574cc6ac..d3f8a45a82 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -12,6 +12,8 @@ #ifndef TOR_DIRECTORY_H #define TOR_DIRECTORY_H +#include "hs_ident.h" + int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, @@ -71,6 +73,8 @@ void directory_request_set_if_modified_since(directory_request_t *req, time_t if_modified_since); void directory_request_set_rend_query(directory_request_t *req, const rend_data_t *query); +void directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); void directory_request_set_routerstatus(directory_request_t *req, const routerstatus_t *rs); diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 29681b42b5..30215d8681 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -124,8 +124,10 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) if (cache_entry->plaintext_data->revision_counter >= desc->plaintext_data->revision_counter) { log_info(LD_REND, "Descriptor revision counter in our cache is " - "greater or equal than the one we received. " - "Rejecting!"); + "greater or equal than the one we received (%d/%d). " + "Rejecting!", + (int)cache_entry->plaintext_data->revision_counter, + (int)desc->plaintext_data->revision_counter); goto err; } /* We now know that the descriptor we just received is a new one so diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c new file mode 100644 index 0000000000..a0e9074601 --- /dev/null +++ b/src/or/hs_cell.c @@ -0,0 +1,584 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.c + * \brief Hidden service API for cell creation and handling. + **/ + +#include "or.h" +#include "config.h" +#include "rendservice.h" +#include "replaycache.h" + +#include "hs_cell.h" +#include "hs_ntor.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" +#include "hs/cell_introduce1.h" +#include "hs/cell_rendezvous.h" + +/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is + * the cell content up to the ENCRYPTED section of length encoded_cell_len. + * The encrypted param is the start of the ENCRYPTED section of length + * encrypted_len. The mac_key is the key needed for the computation of the MAC + * derived from the ntor handshake of length mac_key_len. + * + * The length mac_out_len must be at least DIGEST256_LEN. */ +static void +compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, + const uint8_t *encrypted, size_t encrypted_len, + const uint8_t *mac_key, size_t mac_key_len, + uint8_t *mac_out, size_t mac_out_len) +{ + size_t offset = 0; + size_t mac_msg_len; + uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(encoded_cell); + tor_assert(encrypted); + tor_assert(mac_key); + tor_assert(mac_out); + tor_assert(mac_out_len >= DIGEST256_LEN); + + /* Compute the size of the message which is basically the entire cell until + * the MAC field of course. */ + mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN); + tor_assert(mac_msg_len <= sizeof(mac_msg)); + + /* First, put the encoded cell in the msg. */ + memcpy(mac_msg, encoded_cell, encoded_cell_len); + offset += encoded_cell_len; + /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which + * is junk at this point). */ + memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN)); + offset += (encrypted_len - DIGEST256_LEN); + tor_assert(offset == mac_msg_len); + + crypto_mac_sha3_256(mac_out, mac_out_len, + mac_key, mac_key_len, + mac_msg, mac_msg_len); + memwipe(mac_msg, 0, sizeof(mac_msg)); +} + +/* From a set of keys, subcredential and the ENCRYPTED section of an + * INTRODUCE2 cell, return a newly allocated intro cell keys structure. + * Finally, the client public key is copied in client_pk. On error, return + * NULL. */ +static hs_ntor_intro_cell_keys_t * +get_introduce2_key_material(const ed25519_public_key_t *auth_key, + const curve25519_keypair_t *enc_key, + const uint8_t *subcredential, + const uint8_t *encrypted_section, + curve25519_public_key_t *client_pk) +{ + hs_ntor_intro_cell_keys_t *keys; + + tor_assert(auth_key); + tor_assert(enc_key); + tor_assert(subcredential); + tor_assert(encrypted_section); + tor_assert(client_pk); + + keys = tor_malloc_zero(sizeof(*keys)); + + /* First bytes of the ENCRYPTED section are the client public key. */ + memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); + + if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, + subcredential, keys) < 0) { + /* Don't rely on the caller to wipe this on error. */ + memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); + tor_free(keys); + keys = NULL; + } + return keys; +} + +/* Using the given encryption key, decrypt the encrypted_section of length + * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated + * buffer containing the decrypted data. On decryption failure, NULL is + * returned. */ +static uint8_t * +decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + uint8_t *decrypted = NULL; + crypto_cipher_t *cipher = NULL; + + tor_assert(enc_key); + tor_assert(encrypted_section); + + /* Decrypt ENCRYPTED section. */ + cipher = crypto_cipher_new_with_bits((char *) enc_key, + CURVE25519_PUBKEY_LEN * 8); + tor_assert(cipher); + + /* This is symmetric encryption so can't be bigger than the encrypted + * section length. */ + decrypted = tor_malloc_zero(encrypted_section_len); + if (crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted_section, + encrypted_section_len) < 0) { + tor_free(decrypted); + decrypted = NULL; + goto done; + } + + done: + crypto_cipher_free(cipher); + return decrypted; +} + +/* Given a pointer to the decrypted data of the ENCRYPTED section of an + * INTRODUCE2 cell of length decrypted_len, parse and validate the cell + * content. Return a newly allocated cell structure or NULL on error. The + * circuit and service object are only used for logging purposes. */ +static trn_cell_introduce_encrypted_t * +parse_introduce2_encrypted(const uint8_t *decrypted_data, + size_t decrypted_len, const origin_circuit_t *circ, + const hs_service_t *service) +{ + trn_cell_introduce_encrypted_t *enc_cell = NULL; + + tor_assert(decrypted_data); + tor_assert(circ); + tor_assert(service); + + if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data, + decrypted_len) < 0) { + log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of " + "the INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) != + HS_CELL_ONION_KEY_TYPE_NTOR) { + log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but " + "expected %u on circuit %u for service %s", + trn_cell_introduce_encrypted_get_onion_key_type(enc_cell), + HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != + CURVE25519_PUBKEY_LEN) { + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but " + "expected %d on circuit %u for service %s", + (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* XXX: Validate NSPEC field as well. */ + + return enc_cell; + err: + trn_cell_introduce_encrypted_free(enc_cell); + return NULL; +} + +/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA + * encryption key. The encoded cell is put in cell_out that MUST at least be + * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on + * success else a negative value and cell_out is untouched. */ +static ssize_t +build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, + uint8_t *cell_out) +{ + ssize_t cell_len; + + tor_assert(circ_nonce); + tor_assert(enc_key); + tor_assert(cell_out); + + memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE); + + cell_len = rend_service_encode_establish_intro_cell((char*)cell_out, + RELAY_PAYLOAD_SIZE, + enc_key, circ_nonce); + return cell_len; +} + +/* Parse an INTRODUCE2 cell from payload of size payload_len for the given + * service and circuit which are used only for logging purposes. The resulting + * parsed cell is put in cell_ptr_out. + * + * This function only parses prop224 INTRODUCE2 cells even when the intro point + * is a legacy intro point. That's because intro points don't actually care + * about the contents of the introduce cell. Legacy INTRODUCE cells are only + * used by the legacy system now. + * + * Return 0 on success else a negative value and cell_ptr_out is untouched. */ +static int +parse_introduce2_cell(const hs_service_t *service, + const origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len, + trn_cell_introduce1_t **cell_ptr_out) +{ + trn_cell_introduce1_t *cell = NULL; + + tor_assert(service); + tor_assert(circ); + tor_assert(payload); + tor_assert(cell_ptr_out); + + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Success. */ + *cell_ptr_out = cell; + return 0; + err: + return -1; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point + * object. The encoded cell is put in cell_out that MUST at least be of the + * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else + * a negative value and cell_out is untouched. This function also supports + * legacy cell creation. */ +ssize_t +hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out) +{ + ssize_t cell_len = -1; + uint16_t sig_len = ED25519_SIG_LEN; + trn_cell_extension_t *ext; + trn_cell_establish_intro_t *cell = NULL; + + tor_assert(circ_nonce); + tor_assert(ip); + + /* Quickly handle the legacy IP. */ + if (ip->base.is_only_legacy) { + tor_assert(ip->legacy_key); + cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key, + cell_out); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + /* Success or not we are done here. */ + goto done; + } + + /* Set extension data. None used here. */ + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + cell = trn_cell_establish_intro_new(); + trn_cell_establish_intro_set_extensions(cell, ext); + /* Set signature size. Array is then allocated in the cell. We need to do + * this early so we can use trunnel API to get the signature length. */ + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); + + /* Set AUTH_KEY_TYPE: 2 means ed25519 */ + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); + + /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of + * AUTH_KEY to match */ + { + uint16_t auth_key_len = ED25519_PUBKEY_LEN; + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + /* We do this call _after_ setting the length because it's reallocated at + * that point only. */ + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); + memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len); + } + + /* Calculate HANDSHAKE_AUTH field (MAC). */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_mac_offset = + sig_len + sizeof(cell->sig_len) + + trn_cell_establish_intro_getlen_handshake_mac(cell); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr; + + /* We first encode the current fields we have in the cell so we can + * compute the MAC using the raw bytes. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + /* Sanity check. */ + tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset); + + /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */ + crypto_mac_sha3_256(mac, sizeof(mac), + (uint8_t *) circ_nonce, DIGEST_LEN, + tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); + handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); + memcpy(handshake_ptr, mac, sizeof(mac)); + + memwipe(mac, 0, sizeof(mac)); + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Calculate the cell signature SIG. */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len)); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr; + ed25519_signature_t sig; + + /* We first encode the current fields we have in the cell so we can + * compute the signature from the raw bytes of the cell. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + + if (ed25519_sign_prefixed(&sig, tmp_cell_enc, + tmp_cell_enc_len - tmp_cell_sig_offset, + ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) { + log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell."); + goto done; + } + /* Copy the signature into the cell. */ + sig_ptr = trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, sig_len); + + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Encode the cell. Can't be bigger than a standard cell. */ + cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + done: + trn_cell_establish_intro_free(cell); + return cell_len; +} + +/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we + * are successful at parsing it, return the length of the parsed cell else a + * negative value on error. */ +ssize_t +hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) +{ + ssize_t ret; + trn_cell_intro_established_t *cell = NULL; + + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. */ + ret = trn_cell_intro_established_parse(&cell, payload, payload_len); + if (ret >= 0) { + /* On success, we do not keep the cell, we just notify the caller that it + * was successfully parsed. */ + trn_cell_intro_established_free(cell); + } + return ret; +} + +/* Parsse the INTRODUCE2 cell using data which contains everything we need to + * do so and contains the destination buffers of information we extract and + * compute from the cell. Return 0 on success else a negative value. The + * service and circ are only used for logging purposes. */ +ssize_t +hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service) +{ + int ret = -1; + time_t elapsed; + uint8_t *decrypted = NULL; + size_t encrypted_section_len; + const uint8_t *encrypted_section; + trn_cell_introduce1_t *cell = NULL; + trn_cell_introduce_encrypted_t *enc_cell = NULL; + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + + tor_assert(data); + tor_assert(circ); + tor_assert(service); + + /* Parse the cell into a decoded data structure pointed by cell_ptr. */ + if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, + &cell) < 0) { + goto done; + } + + log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " + "for service %s. Decoding encrypted section...", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + + /* Encrypted section must at least contain the CLIENT_PK and MAC which is + * defined in section 3.3.2 of the specification. */ + if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length " + "for service %s. Dropping cell.", + safe_str_client(service->onion_address)); + goto done; + } + + /* Check our replay cache for this introduction point. */ + if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, + encrypted_section_len, &elapsed)) { + log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the" + "same ENCRYPTED section was seen %ld seconds ago. " + "Dropping cell.", elapsed); + goto done; + } + + /* Build the key material out of the key material found in the cell. */ + intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, + data->subcredential, + encrypted_section, + &data->client_pk); + if (intro_keys == NULL) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " + "compute key material on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Validate MAC from the cell and our computed key material. The MAC field + * in the cell is at the end of the encrypted section. */ + { + uint8_t mac[DIGEST256_LEN]; + /* The MAC field is at the very end of the ENCRYPTED section. */ + size_t mac_offset = encrypted_section_len - sizeof(mac); + /* Compute the MAC. Use the entire encoded payload with a length up to the + * ENCRYPTED section. */ + compute_introduce_mac(data->payload, + data->payload_len - encrypted_section_len, + encrypted_section, encrypted_section_len, + intro_keys->mac_key, sizeof(intro_keys->mac_key), + mac, sizeof(mac)); + if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) { + log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + } + + { + /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ + const uint8_t *encrypted_data = + encrypted_section + sizeof(data->client_pk); + /* It's symmetric encryption so it's correct to use the ENCRYPTED length + * for decryption. Computes the length of ENCRYPTED_DATA meaning removing + * the CLIENT_PK and MAC length. */ + size_t encrypted_data_len = + encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN); + + /* This decrypts the ENCRYPTED_DATA section of the cell. */ + decrypted = decrypt_introduce2(intro_keys->enc_key, + encrypted_data, encrypted_data_len); + if (decrypted == NULL) { + log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an " + "INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Parse this blob into an encrypted cell structure so we can then extract + * the data we need out of it. */ + enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len, + circ, service); + memwipe(decrypted, 0, encrypted_data_len); + if (enc_cell == NULL) { + goto done; + } + } + + /* XXX: Implement client authorization checks. */ + + /* Extract onion key and rendezvous cookie from the cell used for the + * rendezvous point circuit e2e encryption. */ + memcpy(data->onion_pk.public_key, + trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN); + memcpy(data->rendezvous_cookie, + trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell), + sizeof(data->rendezvous_cookie)); + + /* Extract rendezvous link specifiers. */ + for (size_t idx = 0; + idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { + link_specifier_t *lspec = + trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); + smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + } + + /* Success. */ + ret = 0; + log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); + + done: + if (intro_keys) { + memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); + tor_free(intro_keys); + } + tor_free(decrypted); + trn_cell_introduce_encrypted_free(enc_cell); + trn_cell_introduce1_free(cell); + return ret; +} + +/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake + * info. The encoded cell is put in cell_out and the length of the data is + * returned. This can't fail. */ +ssize_t +hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_rendezvous1_t *cell; + + tor_assert(rendezvous_cookie); + tor_assert(rendezvous_handshake_info); + tor_assert(cell_out); + + cell = trn_cell_rendezvous1_new(); + /* Set the RENDEZVOUS_COOKIE. */ + memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell), + rendezvous_cookie, rendezvous_cookie_len); + /* Set the HANDSHAKE_INFO. */ + trn_cell_rendezvous1_setlen_handshake_info(cell, + rendezvous_handshake_info_len); + memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell), + rendezvous_handshake_info, rendezvous_handshake_info_len); + /* Encoding. */ + cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + tor_assert(cell_len > 0); + + trn_cell_rendezvous1_free(cell); + return cell_len; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h new file mode 100644 index 0000000000..f32f7a4216 --- /dev/null +++ b/src/or/hs_cell.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.h + * \brief Header file containing cell data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CELL_H +#define TOR_HS_CELL_H + +#include "or.h" +#include "hs_service.h" + +/* Onion key type found in the INTRODUCE1 cell. */ +typedef enum { + HS_CELL_ONION_KEY_TYPE_NTOR = 1, +} hs_cell_onion_key_type_t; + +/* This data structure contains data that we need to parse an INTRODUCE2 cell + * which is used by the INTRODUCE2 cell parsing function. On a successful + * parsing, the onion_pk and rendezvous_cookie will be populated with the + * computed key material from the cell data. This structure is only used during + * INTRO2 parsing and discarded after that. */ +typedef struct hs_cell_introduce2_data_t { + /*** Immutable Section: Set on structure init. ***/ + + /* Introduction point authentication public key. Pointer owned by the + introduction point object through which we received the INTRO2 cell. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption keypair for the ntor handshake. Pointer + owned by the introduction point object through which we received the + INTRO2 cell*/ + const curve25519_keypair_t *enc_kp; + /* Subcredentials of the service. Pointer owned by the descriptor that owns + the introduction point through which we received the INTRO2 cell. */ + const uint8_t *subcredential; + /* Payload of the received encoded cell. */ + const uint8_t *payload; + /* Size of the payload of the received encoded cell. */ + size_t payload_len; + + /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ + + /* Onion public key computed using the INTRODUCE2 encrypted section. */ + curve25519_public_key_t onion_pk; + /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + uint8_t rendezvous_cookie[REND_COOKIE_LEN]; + /* Client public key from the INTRODUCE2 encrypted section. */ + curve25519_public_key_t client_pk; + /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + smartlist_t *link_specifiers; + /* Replay cache of the introduction point. */ + replaycache_t *replay_cache; +} hs_cell_introduce2_data_t; + +/* Build cell API. */ +ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out); +ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out); + +/* Parse cell API. */ +ssize_t hs_cell_parse_intro_established(const uint8_t *payload, + size_t payload_len); +ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service); + +#endif /* TOR_HS_CELL_H */ + diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 2f595d72e5..d0265dc548 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -6,14 +6,27 @@ **/ #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "policies.h" +#include "relay.h" +#include "rendservice.h" +#include "rephist.h" +#include "router.h" +#include "hs_cell.h" #include "hs_circuit.h" #include "hs_ident.h" #include "hs_ntor.h" +#include "hs_service.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.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 @@ -167,6 +180,825 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, } } +/* For a given circuit and a service introduction point object, register the + * intro circuit to the circuitmap. This supports legacy intro point. */ +static void +register_intro_circ(const hs_service_intro_point_t *ip, + origin_circuit_t *circ) +{ + tor_assert(ip); + tor_assert(circ); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + return; + } + hs_circuitmap_register_intro_circ_v2_service_side(circ, digest); + } else { + hs_circuitmap_register_intro_circ_v3_service_side(circ, + &ip->auth_key_kp.pubkey); + } +} + +/* Return the number of opened introduction circuit for the given circuit that + * is matching its identity key. */ +static unsigned int +count_opened_desc_intro_point_circuits(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(service); + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + const circuit_t *circ; + const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc == NULL) { + continue; + } + circ = TO_CIRCUIT(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO); + /* Having a circuit not for the requested service is really bad. */ + tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk, + ô->hs_ident->identity_pk)); + /* Only count opened circuit and skip circuit that will be closed. */ + if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) { + count++; + } + } DIGEST256MAP_FOREACH_END; + return count; +} + +/* From a given service, rendezvous cookie and handshake info, create a + * rendezvous point circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys) +{ + hs_ident_circuit_t *ident; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + tor_assert(service); + tor_assert(rendezvous_cookie); + tor_assert(server_pk); + tor_assert(keys); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ + memcpy(ident->rendezvous_cookie, rendezvous_cookie, + sizeof(ident->rendezvous_cookie)); + /* Build the HANDSHAKE_INFO which looks like this: + * SERVER_PK [32 bytes] + * AUTH_INPUT_MAC [32 bytes] + */ + memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN); + memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac, + DIGEST256_LEN); + tor_assert(sizeof(ident->rendezvous_handshake_info) == + sizeof(handshake_info)); + memcpy(ident->rendezvous_handshake_info, handshake_info, + sizeof(ident->rendezvous_handshake_info)); + /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */ + tor_assert(sizeof(ident->rendezvous_ntor_key_seed) == + sizeof(keys->ntor_key_seed)); + memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed, + sizeof(ident->rendezvous_ntor_key_seed)); + return ident; +} + +/* From a given service and service intro point, create an introduction point + * circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_intro_circuit_identifier(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_ident_circuit_t *ident; + + tor_assert(service); + tor_assert(ip); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); + + return ident; +} + +/* For a given introduction point and an introduction circuit, send the + * ESTABLISH_INTRO cell. The service object is used for logging. This can fail + * and if so, the circuit is closed and the intro point object is flagged + * that the circuit is not established anymore which is important for the + * retry mechanism. */ +static void +send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, origin_circuit_t *circ) +{ + ssize_t cell_len; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + + /* Encode establish intro cell. */ + cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, + ip, payload); + if (cell_len < 0) { + log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " + "on circuit %u. Closing circuit.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* Send the cell on the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_INTRO, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " + "on circuit %u.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + /* On error, the circuit has been closed. */ + goto done; + } + + /* Record the attempt to use this circuit. */ + pathbias_count_use_attempt(circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* From a list of link specifier, an onion key and if we are requesting a + * direct connection (ex: single onion service), return a newly allocated + * extend_info_t object. This function checks the firewall policies and if we + * are allowed to extend to the chosen address. + * + * if either IPv4 or legacy ID is missing, error. + * if not direct_conn, IPv4 is prefered. + * if direct_conn, IPv6 is prefered if we have one available. + * if firewall does not allow the chosen address, error. + * + * Return NULL if we can't fulfill the conditions. */ +static extend_info_t * +get_rp_extend_info(const smartlist_t *link_specifiers, + const curve25519_public_key_t *onion_key, int direct_conn) +{ + int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0; + char legacy_id[DIGEST_LEN] = {0}; + uint16_t port_v4 = 0, port_v6 = 0, port = 0; + tor_addr_t addr_v4, addr_v6, *addr = NULL; + ed25519_public_key_t ed25519_pk; + extend_info_t *info = NULL; + + tor_assert(link_specifiers); + tor_assert(onion_key); + + SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) { + switch (link_specifier_get_ls_type(ls)) { + case LS_IPV4: + /* Skip if we already seen a v4. */ + if (have_v4) continue; + tor_addr_from_ipv4h(&addr_v4, + link_specifier_get_un_ipv4_addr(ls)); + port_v4 = link_specifier_get_un_ipv4_port(ls); + have_v4 = 1; + break; + case LS_IPV6: + /* Skip if we already seen a v6. */ + if (have_v6) continue; + tor_addr_from_ipv6_bytes(&addr_v6, + (const char *) link_specifier_getconstarray_un_ipv6_addr(ls)); + port_v6 = link_specifier_get_un_ipv6_port(ls); + have_v6 = 1; + break; + case LS_LEGACY_ID: + /* Make sure we do have enough bytes for the legacy ID. */ + if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { + break; + } + memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls), + sizeof(legacy_id)); + have_legacy_id = 1; + break; + case LS_ED25519_ID: + memcpy(ed25519_pk.pubkey, + link_specifier_getconstarray_un_ed25519_id(ls), + ED25519_PUBKEY_LEN); + have_ed25519_id = 1; + break; + default: + /* Ignore unknown. */ + break; + } + } SMARTLIST_FOREACH_END(ls); + + /* IPv4, legacy ID are mandatory for rend points. + * ed25519 keys and ipv6 are optional for rend points */ + if (!have_v4 || !have_legacy_id) { + goto done; + } + /* By default, we pick IPv4 but this might change to v6 if certain + * conditions are met. */ + addr = &addr_v4; port = port_v4; + + /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop + * circuit so we can't extend in IPv6. And at this point, we do have an IPv4 + * address available so go to validation. */ + if (!direct_conn) { + goto validate; + } + + /* From this point on, we have a request for a direct connection to the + * rendezvous point so make sure we can actually connect through our + * firewall. We'll prefer IPv6. */ + + /* IPv6 test. */ + if (have_v6 && + fascist_firewall_allows_address_addr(&addr_v6, port_v6, + FIREWALL_OR_CONNECTION, 1, 1)) { + /* Direct connection and we can reach it in IPv6 so go for it. */ + addr = &addr_v6; port = port_v6; + goto validate; + } + /* IPv4 test and we are sure we have a v4 because of the check above. */ + if (fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, 0, 0)) { + /* Direct connection and we can reach it in IPv4 so go for it. */ + addr = &addr_v4; port = port_v4; + goto validate; + } + + validate: + /* We'll validate now that the address we've picked isn't a private one. If + * it is, are we allowing to extend to private address? */ + if (!extend_info_addr_is_allowed(addr)) { + log_warn(LD_REND, "Rendezvous point address is private and it is not " + "allowed to extend to it: %s:%u", + fmt_addr(&addr_v4), port_v4); + goto done; + } + + /* We do have everything for which we think we can connect successfully. */ + info = extend_info_new(NULL, legacy_id, + have_ed25519_id ? &ed25519_pk : NULL, + NULL, onion_key, + addr, port); + done: + return info; +} + +/* For a given service, the ntor onion key and a rendezvous cookie, launch a + * circuit to the rendezvous point specified by the link specifiers. On + * success, a circuit identifier is attached to the circuit with the needed + * data. This function will try to open a circuit for a maximum value of + * MAX_REND_FAILURES then it will give up. */ +static void +launch_rendezvous_point_circuit(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data) +{ + int circ_needs_uptime; + time_t now = time(NULL); + extend_info_t *info = NULL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); + + /* Get the extend info data structure for the chosen rendezvous point + * specified by the given link specifiers. */ + info = get_rp_extend_info(data->link_specifiers, &data->onion_pk, + service->config.is_single_onion); + if (info == NULL) { + /* We are done here, we can't extend to the rendezvous point. */ + goto end; + } + + for (int i = 0; i < MAX_REND_FAILURES; i++) { + int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; + if (circ_needs_uptime) { + circ_flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* Firewall and policies are checked when getting the extend info. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, + circ_flags); + if (circ != NULL) { + /* Stop retrying, we have a circuit! */ + break; + } + } + if (circ == NULL) { + log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s " + "for service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(service->onion_address)); + goto end; + } + log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " + "for service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(hex_str((const char *) data->rendezvous_cookie, + REND_COOKIE_LEN)), + safe_str_client(service->onion_address)); + tor_assert(circ->build_state); + /* Rendezvous circuit have a specific timeout for the time spent on trying + * to connect to the rendezvous point. */ + circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; + + /* Create circuit identifier and key material. */ + { + hs_ntor_rend_cell_keys_t keys; + curve25519_keypair_t ephemeral_kp; + /* No need for extra strong, this is only for this circuit life time. This + * key will be used for the RENDEZVOUS1 cell that will be sent on the + * circuit once opened. */ + curve25519_keypair_generate(&ephemeral_kp, 0); + if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, + &ip->enc_key_kp, + &ephemeral_kp, &data->client_pk, + &keys) < 0) { + /* This should not really happened but just in case, don't make tor + * freak out, close the circuit and move on. */ + log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " + "service %s", + safe_str_client(service->onion_address)); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + circ->hs_ident = create_rp_circuit_identifier(service, + data->rendezvous_cookie, + &ephemeral_kp.pubkey, &keys); + memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); + memwipe(&keys, 0, sizeof(keys)); + tor_assert(circ->hs_ident); + } + + end: + extend_info_free(info); +} + +/* Return true iff the given service rendezvous circuit circ is allowed for a + * relaunch to the rendezvous point. */ +static int +can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* XXX: Retrying under certain condition. This is related to #22455. */ + + /* Avoid to relaunch twice a circuit to the same rendezvous point at the + * same time. */ + if (circ->hs_service_side_rend_circ_has_been_relaunched) { + log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " + "Skipping retry.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + goto disallow; + } + + /* A failure count that has reached maximum allowed or circuit that expired, + * we skip relaunching. */ + if (circ->build_state->failure_count > MAX_REND_FAILURES || + circ->build_state->expiry_time <= time(NULL)) { + log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " + "failed with %d attempts and expiry time %ld. " + "Giving up building.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit)), + circ->build_state->failure_count, + circ->build_state->expiry_time); + goto disallow; + } + + /* Allowed to relaunch. */ + return 1; + disallow: + return 0; +} + +/* Retry the rendezvous point of circ by launching a new circuit to it. */ +static void +retry_service_rendezvous_point(const origin_circuit_t *circ) +{ + int flags = 0; + origin_circuit_t *new_circ; + cpath_build_state_t *bstate; + + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Ease our life. */ + bstate = circ->build_state; + + log_info(LD_REND, "Retrying rendezvous point circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + + /* Get the current build state flags for the next circuit. */ + flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; + flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; + flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; + + /* We do NOT add the onehop tunnel flag even though it might be a single + * onion service. The reason is that if we failed once to connect to the RP + * with a direct connection, we consider that chances are that we will fail + * again so try a 3-hop circuit and hope for the best. Because the service + * has no anonymity (single onion), this change of behavior won't affect + * security directly. */ + + new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, + bstate->chosen_exit, flags); + if (new_circ == NULL) { + log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + goto done; + } + + /* Transfer build state information to the new circuit state in part to + * catch any other failures. */ + new_circ->build_state->failure_count = bstate->failure_count++; + new_circ->build_state->expiry_time = bstate->expiry_time; + new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); + + done: + return; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return an introduction point circuit matching the given intro point object. + * NULL is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) +{ + origin_circuit_t *circ = NULL; + + tor_assert(ip); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + goto end; + } + circ = hs_circuitmap_get_intro_circ_v2_service_side(digest); + } else { + circ = hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + } + end: + return circ; +} + +/* Called when we fail building a rendezvous circuit at some point other than + * the last hop: launches a new circuit to the same rendezvous point. This + * supports legacy service. + * + * We currently relaunch connections to rendezvous points if: + * - A rendezvous circuit timed out before connecting to RP. + * - The redenzvous circuit failed to connect to the RP. + * + * We avoid relaunching a connection to this rendezvous point if: + * - We have already tried MAX_REND_FAILURES times to connect to this RP. + * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT + * seconds + * - We've already retried this specific rendezvous circuit. + */ +void +hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Check if we are allowed to relaunch to the rendezvous point of circ. */ + if (!can_relaunch_service_rendezvous_point(circ)) { + goto done; + } + + /* Flag the circuit that we are relaunching so to avoid to relaunch twice a + * circuit to the same rendezvous point at the same time. */ + circ->hs_service_side_rend_circ_has_been_relaunched = 1; + + /* Legacy service don't have an hidden service ident. */ + if (circ->hs_ident) { + retry_service_rendezvous_point(circ); + } else { + rend_service_relaunch_rendezvous(circ); + } + + done: + return; +} + +/* For a given service and a service intro point, launch a circuit to the + * extend info ei. If the service is a single onion, a one-hop circuit will be + * requested. Return 0 if the circuit was successfully launched and tagged + * with the correct identifier. On error, a negative value is returned. */ +int +hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei) +{ + /* Standard flags for introduction circuit. */ + int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(ei); + + /* Update circuit flags in case of a single onion service that requires a + * direct connection. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + log_info(LD_REND, "Launching a circuit to intro point %s for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + + /* Note down the launch for the retry period. Even if the circuit fails to + * be launched, we still want to respect the retry period to avoid stress on + * the circuit subsystem. */ + service->state.num_intro_circ_launched++; + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + ei, circ_flags); + if (circ == NULL) { + goto end; + } + + /* Setup the circuit identifier and attach it to it. */ + circ->hs_ident = create_intro_circuit_identifier(service, ip); + tor_assert(circ->hs_ident); + /* Register circuit in the global circuitmap. */ + register_intro_circ(ip, circ); + + /* Success. */ + ret = 0; + end: + return ret; +} + +/* Called when a service introduction point circuit is done building. Given + * the service and intro point object, this function will send the + * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the + * circuit has been repurposed to General because we already have too many + * opened. */ +int +hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ) +{ + int ret = 0; + unsigned int num_intro_circ, num_needed_circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(desc); + tor_assert(circ); + + /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already + * established introduction circuits */ + num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); + num_needed_circ = service->config.num_intro_points; + if (num_intro_circ > num_needed_circ) { + /* There are too many opened valid intro circuit for what the service + * needs so repurpose this one. */ + + /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just + * closes the circuit. I have NO idea why it does that so it hasn't been + * added here. I can only assume in case our ExcludeNodes list changes but + * in that case, all circuit are flagged unusable (config.c). --dgoulet */ + + log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " + "have enough for service %s. Repurposing " + "it to general and leaving internal.", + safe_str_client(service->onion_address)); + tor_assert(circ->build_state->is_internal); + /* Remove it from the circuitmap. */ + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + /* Cleaning up the hidden service identifier and repurpose. */ + hs_ident_circuit_free(circ->hs_ident); + circ->hs_ident = NULL; + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); + /* Inform that this circuit just opened for this new purpose. */ + circuit_has_opened(circ); + /* This return value indicate to the caller that the IP object should be + * removed from the service because it's corresponding circuit has just + * been repurposed. */ + ret = 1; + goto done; + } + + log_info(LD_REND, "Introduction circuit %u established for service %s.", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call + * makes sure the circuit gets closed. */ + send_establish_intro(service, ip, circ); + + done: + return ret; +} + +/* Called when a service rendezvous point circuit is done building. Given the + * service and the circuit, this function will send a RENDEZVOUS1 cell on the + * circuit using the information in the circuit identifier. If the cell can't + * be sent, the circuit is closed. */ +void +hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ) +{ + size_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(service); + tor_assert(circ); + tor_assert(circ->hs_ident); + + /* Some useful logging. */ + log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN), + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* This can't fail. */ + payload_len = hs_cell_build_rendezvous1( + circ->hs_ident->rendezvous_cookie, + sizeof(circ->hs_ident->rendezvous_cookie), + circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info), + payload); + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_RENDEZVOUS1, + (const char *) payload, payload_len, + circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Setup end-to-end rendezvous circuit between the client and us. */ + if (hs_circuit_setup_e2e_rend_circ(circ, + circ->hs_ident->rendezvous_ntor_key_seed, + sizeof(circ->hs_ident->rendezvous_ntor_key_seed), + 1) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); + goto done; + } + + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle + * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the + * given introduction circuit circ. The service is only used for logging + * purposes. Return 0 on success else a negative value. */ +int +hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + tor_assert(payload); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + goto done; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. For a legacy node, it's an empty payload so as long as we + * have the cell, we are good. */ + if (!ip->base.is_only_legacy && + hs_cell_parse_intro_established(payload, payload_len) < 0) { + log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Switch the purpose to a fully working intro point. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the + * circuit so update our pathbias subsystem. */ + pathbias_mark_use_success(circ); + /* Success. */ + ret = 0; + + done: + return ret; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the INTRODUCE2 payload of size payload_len for the given + * circuit and service. This cell is associated with the intro point object ip + * and the subcredential. Return 0 on success else a negative value. */ +int +hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + time_t elapsed; + hs_cell_introduce2_data_t data; + + tor_assert(service); + tor_assert(circ); + tor_assert(ip); + tor_assert(subcredential); + tor_assert(payload); + + /* Populate the data structure with everything we need for the cell to be + * parsed, decrypted and key material computed correctly. */ + data.auth_pk = &ip->auth_key_kp.pubkey; + data.enc_kp = &ip->enc_key_kp; + data.subcredential = subcredential; + data.payload = payload; + data.payload_len = payload_len; + data.link_specifiers = smartlist_new(); + data.replay_cache = ip->replay_cache; + + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { + goto done; + } + + /* Check whether we've seen this REND_COOKIE before to detect repeats. */ + if (replaycache_add_test_and_elapsed( + service->state.replay_cache_rend_cookie, + data.rendezvous_cookie, sizeof(data.rendezvous_cookie), + &elapsed)) { + /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE + * as its previous one if its intro circ times out while in state + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first + * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2 + * cell), we are already trying to connect to that rend point (and may + * have already succeeded); drop this cell. */ + log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE " + "field %ld seconds ago. Dropping cell.", elapsed); + goto done; + } + + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid + * so increment our counter that we've seen one on this intro point. */ + ip->introduce2_count++; + + /* Launch rendezvous circuit with the onion key and rend cookie. */ + launch_rendezvous_point_circuit(service, ip, &data); + /* Success. */ + ret = 0; + + done: + SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(data.link_specifiers); + memwipe(&data, 0, sizeof(data)); + return ret; +} + /* 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 diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 71ce5c3331..9e359394e8 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -10,6 +10,40 @@ #define TOR_HS_CIRCUIT_H #include "or.h" +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_service.h" + +/* Circuit API. */ +int hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ); +void hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ); +int hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei); +int hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie); +void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); + +origin_circuit_t *hs_circ_service_get_intro_circ( + const hs_service_intro_point_t *ip); + +/* Cell API. */ +int hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len); /* e2e circuit API. */ diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 27330bfcdb..0d3d41b7cd 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -15,10 +15,119 @@ #include "config.h" #include "networkstatus.h" +#include "nodelist.h" #include "hs_cache.h" #include "hs_common.h" #include "hs_service.h" #include "rendcommon.h" +#include "rendservice.h" +#include "router.h" +#include "shared_random.h" +#include "shared_random_state.h" + +/* Ed25519 Basepoint value. Taken from section 5 of + * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ +static const char *str_ed25519_basepoint = + "(15112221349535400772501151409588531511" + "454012693041857206046113283949847762202, " + "463168356949264781694283940034751631413" + "07993866256225615783033603165251855960)"; + +#ifdef HAVE_SYS_UN_H + +/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, + * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* defined(HAVE_SYS_UN_H) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* HAVE_SYS_UN_H */ + +/* Helper function: The key is a digest that we compare to a node_t object + * current hsdir_index. */ +static int +compare_digest_to_current_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_next_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects current hsdir_index. */ +static int +compare_node_current_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->current, + node2->hsdir_index->current, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_next_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->next, + node2->hsdir_index->next, + DIGEST256_LEN); +} /* Allocate and return a string containing the path to filename in directory. * This function will never return NULL. The caller must free this path. */ @@ -72,6 +181,17 @@ hs_check_service_private_dir(const char *username, const char *path, STATIC uint64_t get_time_period_length(void) { + /* If we are on a test network, make the time period smaller than normal so + that we actually see it rotate. Specifically, make it the same length as + an SRV protocol run. */ + if (get_options()->TestingTorNetwork) { + unsigned run_duration = sr_state_get_protocol_run_duration(); + /* An SRV run should take more than a minute (it's 24 rounds) */ + tor_assert_nonfatal(run_duration > 60); + /* Turn it from seconds to minutes before returning: */ + return sr_state_get_protocol_run_duration() / 60; + } + int32_t time_period_length = networkstatus_get_param(NULL, "hsdir-interval", HS_TIME_PERIOD_LENGTH_DEFAULT, HS_TIME_PERIOD_LENGTH_MIN, @@ -83,17 +203,22 @@ get_time_period_length(void) } /** Get the HS time period number at time <b>now</b> */ -STATIC uint64_t -get_time_period_num(time_t now) +uint64_t +hs_get_time_period_num(time_t now) { uint64_t time_period_num; + + /* Start by calculating minutes since the epoch */ uint64_t time_period_length = get_time_period_length(); uint64_t minutes_since_epoch = now / 60; - /* Now subtract half a day to fit the prop224 time period schedule (see - * section [TIME-PERIODS]). */ - tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET); - minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET; + /* Apply the rotation offset as specified by prop224 (section + * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV + * publication */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + time_period_rotation_offset /= 60; /* go from seconds to minutes */ + tor_assert(minutes_since_epoch > time_period_rotation_offset); + minutes_since_epoch -= time_period_rotation_offset; /* Calculate the time period */ time_period_num = minutes_since_epoch / time_period_length; @@ -105,7 +230,22 @@ get_time_period_num(time_t now) uint64_t hs_get_next_time_period_num(time_t now) { - return get_time_period_num(now) + 1; + return hs_get_time_period_num(now) + 1; +} + +/* Return the start time of the upcoming time period based on <b>now</b>. */ +time_t +hs_get_start_time_of_next_time_period(time_t now) +{ + uint64_t time_period_length = get_time_period_length(); + + /* Get start time of next time period */ + uint64_t next_time_period_num = hs_get_next_time_period_num(now); + uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length; + + /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + return start_of_next_tp_in_mins * 60 + time_period_rotation_offset; } /* Create a new rend_data_t for a specific given <b>version</b>. @@ -360,6 +500,148 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* Using the given time period number, compute the disaster shared random + * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ +static void +compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + crypto_digest_t *digest; + + tor_assert(srv_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + + /* Start setting up payload: + * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */ + crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX, + HS_SRV_DISASTER_PREFIX_LEN); + + /* Setup INT_8(period_length) | INT_8(period_num) */ + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/** Due to the high cost of computing the disaster SRV and that potentially we + * would have to do it thousands of times in a row, we always cache the + * computer disaster SRV (and its corresponding time period num) in case we + * want to reuse it soon after. We need to cache two SRVs, one for each active + * time period (in case of overlap mode). + */ +static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; +static uint64_t cached_time_period_nums[2] = {0}; + +/** Compute the disaster SRV value for this <b>time_period_num</b> and put it + * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches + * to see if we have already computed it. */ +STATIC void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + if (time_period_num == cached_time_period_nums[0]) { + memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN); + return; + } else if (time_period_num == cached_time_period_nums[1]) { + memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN); + return; + } else { + int replace_idx; + // Replace the lower period number. + if (cached_time_period_nums[0] <= cached_time_period_nums[1]) { + replace_idx = 0; + } else { + replace_idx = 1; + } + cached_time_period_nums[replace_idx] = time_period_num; + compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]); + memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN); + return; + } +} + +#ifdef TOR_UNIT_TESTS + +/** Get the first cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_first_cached_disaster_srv(void) +{ + return cached_disaster_srv[0]; +} + +/** Get the second cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_second_cached_disaster_srv(void) +{ + return cached_disaster_srv[1]; +} + +#endif + +/* When creating a blinded key, we need a parameter which construction is as + * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). + * + * The nonce has a pre-defined format which uses the time period number + * period_num and the start of the period in second start_time_period. + * + * The secret of size secret_len is optional meaning that it can be NULL and + * thus will be ignored for the param construction. + * + * The result is put in param_out. */ +static void +build_blinded_key_param(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t period_num, uint64_t period_length, + uint8_t *param_out) +{ + size_t offset = 0; + const char blind_str[] = "Derive temporary signing key"; + uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; + crypto_digest_t *digest; + + tor_assert(pubkey); + tor_assert(param_out); + + /* Create the nonce N. The construction is as follow: + * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */ + memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); + offset += HS_KEYBLIND_NONCE_PREFIX_LEN; + set_uint64(nonce + offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(nonce + offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == HS_KEYBLIND_NONCE_LEN); + + /* Generate the parameter h and the construction is as follow: + * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str)); + crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); + /* Optional secret. */ + if (secret) { + crypto_digest_add_bytes(digest, (char *) secret, secret_len); + } + crypto_digest_add_bytes(digest, str_ed25519_basepoint, + strlen(str_ed25519_basepoint)); + crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce)); + + /* Extract digest and put it in the param. */ + crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(nonce, 0, sizeof(nonce)); +} + /* 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) @@ -442,6 +724,98 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, tor_assert(offset == HS_SERVICE_ADDR_LEN); } +/* Using the given identity public key and a blinded public key, compute the + * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * This can't fail. */ +void +hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out) +{ + uint8_t credential[DIGEST256_LEN]; + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(subcred_out); + + /* First, build the credential. Construction is as follow: + * credential = H("credential" | public-identity-key) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX, + HS_CREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN); + crypto_digest_free(digest); + + /* Now, compute the subcredential. Construction is as follow: + * subcredential = H("subcredential" | credential | blinded-public-key). */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX, + HS_SUBCREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) credential, + sizeof(credential)); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(credential, 0, sizeof(credential)); +} + +/* From the given list of hidden service ports, find the ones that much the + * given edge connection conn, pick one at random and use it to set the + * connection address. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { + smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } + } + return (chosen_port) ? 0 : -1; +} + /* 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 @@ -541,6 +915,404 @@ hs_build_address(const ed25519_public_key_t *key, uint8_t version, tor_assert(hs_address_is_valid(addr_out)); } +/* Return a newly allocated copy of lspec. */ +link_specifier_t * +hs_link_specifier_dup(const link_specifier_t *lspec) +{ + link_specifier_t *dup = link_specifier_new(); + memcpy(dup, lspec, sizeof(*dup)); + /* The unrecognized field is a dynamic array so make sure to copy its + * content and not the pointer. */ + link_specifier_setlen_un_unrecognized( + dup, link_specifier_getlen_un_unrecognized(lspec)); + if (link_specifier_getlen_un_unrecognized(dup)) { + memcpy(link_specifier_getarray_un_unrecognized(dup), + link_specifier_getconstarray_un_unrecognized(lspec), + link_specifier_getlen_un_unrecognized(dup)); + } + return dup; +} + +/* From a given ed25519 public key pk and an optional secret, compute a + * blinded public key and put it in blinded_pk_out. This is only useful to + * the client side because the client only has access to the identity public + * key of the service. */ +void +hs_build_blinded_pubkey(const ed25519_public_key_t *pk, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *blinded_pk_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(pk); + tor_assert(blinded_pk_out); + tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + + build_blinded_key_param(pk, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_public_blind(blinded_pk_out, pk, param); + + memwipe(param, 0, sizeof(param)); +} + +/* From a given ed25519 keypair kp and an optional secret, compute a blinded + * keypair for the current time period and put it in blinded_kp_out. This is + * only useful by the service side because the client doesn't have access to + * the identity secret key. */ +void +hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *blinded_kp_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(kp); + tor_assert(blinded_kp_out); + /* Extra safety. A zeroed key is bad. */ + tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + + build_blinded_key_param(&kp->pubkey, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_keypair_blind(blinded_kp_out, kp, param); + + memwipe(param, 0, sizeof(param)); +} + +/* Return true if overlap mode is active given the date in consensus. If + * consensus is NULL, then we use the latest live consensus we can find. */ +MOCK_IMPL(int, +hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) +{ + time_t valid_after; + time_t srv_start_time, tp_start_time; + + if (!consensus) { + consensus = networkstatus_get_live_consensus(now); + if (!consensus) { + return 0; + } + } + + /* We consider to be in overlap mode when we are in the period of time + * between a fresh SRV and the beginning of the new time period (in the + * normal network this is between 00:00 (inclusive) and 12:00 UTC + * (exclusive)) */ + valid_after = consensus->valid_after; + srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after); + tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); + + if (valid_after >= srv_start_time && valid_after < tp_start_time) { + return 1; + } + + return 0; +} + +/* Return 1 if any virtual port in ports needs a circuit with good uptime. + * Else return 0. */ +int +hs_service_requires_uptime_circ(const smartlist_t *ports) +{ + tor_assert(ports); + + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, + p->virtual_port)) { + return 1; + } + } SMARTLIST_FOREACH_END(p); + return 0; +} + +/* Build hs_index which is used to find the responsible hsdirs. This index + * value is used to select the responsible HSDir where their hsdir_index is + * closest to this value. + * SHA3-256("store-at-idx" | blinded_public_key | + * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) ) + * + * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out) +{ + crypto_digest_t *digest; + + tor_assert(blinded_pk); + tor_assert(hs_index_out); + + /* Build hs_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + + /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */ + { + uint64_t period_length = get_time_period_length(); + char buf[sizeof(uint64_t)*3]; + size_t offset = 0; + set_uint64(buf, tor_htonll(replica)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(buf)); + + crypto_digest_add_bytes(digest, buf, sizeof(buf)); + } + + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Build hsdir_index which is used to find the responsible hsdirs. This is the + * index value that is compare to the hs_index when selecting an HSDir. + * SHA3-256("node-idx" | node_identity | + * shared_random_value | INT_8(period_length) | INT_8(period_num) ) + * + * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv_value, uint64_t period_num, + uint8_t *hsdir_index_out) +{ + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(srv_value); + tor_assert(hsdir_index_out); + + /* Build hsdir_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); + + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Return a newly allocated buffer containing the current shared random value + * or if not present, a disaster value is computed using the given time period + * number. If a consensus is provided in <b>ns</b>, use it to get the SRV + * value. This function can't fail. */ +uint8_t * +hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *current_srv = sr_get_current(ns); + + if (current_srv) { + memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return a newly allocated buffer containing the previous shared random + * value or if not present, a disaster value is computed using the given time + * period number. This function can't fail. */ +uint8_t * +hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *previous_srv = sr_get_previous(ns); + + if (previous_srv) { + memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return the number of replicas defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_n_replicas(void) +{ + /* The [1,16] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_n_replicas", + HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); +} + +/* Return the spread fetch value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_fetch(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_fetch", + HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); +} + +/* Return the spread store value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_store(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_store", + HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); +} + +/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index. + * Return 0 if everything is as expected, else return -1. */ +static int +node_has_hsdir_index(const node_t *node) +{ + tor_assert(node_supports_v3_hsdir(node)); + + /* A node can't have an HSDir index without a descriptor since we need desc + * to get its ed25519 key */ + if (!node_has_descriptor(node)) { + return 0; + } + + /* At this point, since the node has a desc, this node must also have an + * hsdir index. If not, something went wrong, so BUG out. */ + if (BUG(node->hsdir_index == NULL) || + BUG(tor_mem_is_zero((const char*)node->hsdir_index->current, + DIGEST256_LEN))) { + return 0; + } + + return 1; +} + +/* For a given blinded key and time period number, get the responsible HSDir + * and put their routerstatus_t object in the responsible_dirs list. If + * is_next_period is true, the next hsdir_index of the node_t is used. If + * is_client is true, the spread fetch consensus parameter is used else the + * spread store is used which is only for upload. This function can't fail but + * it is possible that the responsible_dirs list contains fewer nodes than + * expected. + * + * This function goes over the latest consensus routerstatus list and sorts it + * by their node_t hsdir_index then does a binary search to find the closest + * node. All of this makes it a bit CPU intensive so use it wisely. */ +void +hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int is_next_period, + int is_client, smartlist_t *responsible_dirs) +{ + smartlist_t *sorted_nodes; + /* The compare function used for the smartlist bsearch. We have two + * different depending on is_next_period. */ + int (*cmp_fct)(const void *, const void **); + + tor_assert(blinded_pk); + tor_assert(responsible_dirs); + + sorted_nodes = smartlist_new(); + + /* Add every node_t that support HSDir v3 for which we do have a valid + * hsdir_index already computed for them for this consensus. */ + { + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (!c || smartlist_len(c->routerstatus_list) == 0) { + log_warn(LD_REND, "No valid consensus so we can't get the responsible " + "hidden service directories."); + goto done; + } + SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) { + /* Even though this node_t object won't be modified and should be const, + * we can't add const object in a smartlist_t. */ + node_t *n = node_get_mutable_by_id(rs->identity_digest); + tor_assert(n); + if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { + if (!node_has_hsdir_index(n)) { + log_info(LD_GENERAL, "Node %s was found without hsdir index.", + node_describe(n)); + continue; + } + smartlist_add(sorted_nodes, n); + } + } SMARTLIST_FOREACH_END(rs); + } + if (smartlist_len(sorted_nodes) == 0) { + log_warn(LD_REND, "No nodes found to be HSDir or supporting v3."); + goto done; + } + + /* First thing we have to do is sort all node_t by hsdir_index. The + * is_next_period tells us if we want the current or the next one. Set the + * bsearch compare function also while we are at it. */ + if (is_next_period) { + smartlist_sort(sorted_nodes, compare_node_next_hsdir_index); + cmp_fct = compare_digest_to_next_hsdir_index; + } else { + smartlist_sort(sorted_nodes, compare_node_current_hsdir_index); + cmp_fct = compare_digest_to_current_hsdir_index; + } + + /* For all replicas, we'll select a set of HSDirs using the consensus + * parameters and the sorted list. The replica starting at value 1 is + * defined by the specification. */ + for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) { + int idx, start, found, n_added = 0; + uint8_t hs_index[DIGEST256_LEN] = {0}; + /* Number of node to add to the responsible dirs list depends on if we are + * trying to fetch or store. A client always fetches. */ + int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); + + /* Get the index that we should use to select the node. */ + hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); + /* The compare function pointer has been set correctly earlier. */ + start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct, + &found); + /* Getting the length of the list if no member is greater than the key we + * are looking for so start at the first element. */ + if (idx == smartlist_len(sorted_nodes)) { + start = idx = 0; + } + while (n_added < n_to_add) { + const node_t *node = smartlist_get(sorted_nodes, idx); + /* If the node has already been selected which is possible between + * replicas, the specification says to skip over. */ + if (!smartlist_contains(responsible_dirs, node->rs)) { + smartlist_add(responsible_dirs, node->rs); + ++n_added; + } + if (++idx == smartlist_len(sorted_nodes)) { + /* Wrap if we've reached the end of the list. */ + idx = 0; + } + if (idx == start) { + /* We've gone over the whole list, stop and avoid infinite loop. */ + break; + } + } + } + + done: + smartlist_free(sorted_nodes); +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void @@ -561,3 +1333,37 @@ hs_free_all(void) hs_cache_free_all(); } +/* For the given origin circuit circ, decrement the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_dec_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams--; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams--; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + +/* For the given origin circuit circ, increment the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_inc_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams++; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams++; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 203a5d0818..fd2a1f4e32 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -11,6 +11,9 @@ #include "or.h" +/* Trunnel */ +#include "ed25519_cert.h" + /* Protocol version 2. Use this instead of hardcoding "2" in the code base, * this adds a clearer semantic to the value when used. */ #define HS_VERSION_TWO 2 @@ -49,8 +52,6 @@ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ /* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ -/* 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" @@ -76,12 +77,77 @@ #define HS_SERVICE_ADDR_LEN_BASE32 \ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) +/* The default HS time period length */ +#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ +/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ + +/* Keyblinding parameter construction is as follow: + * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ +#define HS_KEYBLIND_NONCE_PREFIX "key-blind" +#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1) +#define HS_KEYBLIND_NONCE_LEN \ + (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) + +/* Credential and subcredential prefix value. */ +#define HS_CREDENTIAL_PREFIX "credential" +#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1) +#define HS_SUBCREDENTIAL_PREFIX "subcredential" +#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) + +/* Node hidden service stored at index prefix value. */ +#define HS_INDEX_PREFIX "store-at-idx" +#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1) + +/* Node hidden service directory index prefix value. */ +#define HSDIR_INDEX_PREFIX "node-idx" +#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1) + +/* Prefix of the shared random value disaster mode. */ +#define HS_SRV_DISASTER_PREFIX "shared-random-disaster" +#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) + +/* Default value of number of hsdir replicas (hsdir_n_replicas). */ +#define HS_DEFAULT_HSDIR_N_REPLICAS 2 +/* Default value of hsdir spread store (hsdir_spread_store). */ +#define HS_DEFAULT_HSDIR_SPREAD_STORE 3 +/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 + /* 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; +/* Represents the mapping from a virtual port of a rendezvous service to a + * real port on some IP. */ +typedef struct rend_service_port_config_t { + /* The incoming HS virtual port we're mapping */ + uint16_t virtual_port; + /* Is this an AF_UNIX port? */ + unsigned int is_unix_addr:1; + /* The outgoing TCP port to use, if !is_unix_addr */ + uint16_t real_port; + /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ + tor_addr_t real_addr; + /* The socket path to connect to, if is_unix_addr */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; +} rend_service_port_config_t; + +/* Hidden service directory index used in a node_t which is set once we set + * the consensus. */ +typedef struct hsdir_index_t { + /* The hsdir index for the current time period. */ + uint8_t current[DIGEST256_LEN]; + /* The hsdir index for the next time period. */ + uint8_t next[DIGEST256_LEN]; +} hsdir_index_t; + void hs_init(void); void hs_free_all(void); @@ -95,6 +161,16 @@ 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 hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *pubkey_out); +void hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *kp_out); +int hs_service_requires_uptime_circ(const smartlist_t *ports); + void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); rend_data_t *rend_data_client_create(const char *onion_address, @@ -111,14 +187,54 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +void hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out); + +uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); +time_t hs_get_start_time_of_next_time_period(time_t now); + +link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); + +MOCK_DECL(int, hs_overlap_mode_is_active, + (const networkstatus_t *consensus, time_t now)); + +uint8_t *hs_get_current_srv(uint64_t time_period_num, + const networkstatus_t *ns); +uint8_t *hs_get_previous_srv(uint64_t time_period_num, + const networkstatus_t *ns); + +void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv, uint64_t period_num, + uint8_t *hsdir_index_out); +void hs_build_hs_index(uint64_t replica, + const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out); + +int32_t hs_get_hsdir_n_replicas(void); +int32_t hs_get_hsdir_spread_fetch(void); +int32_t hs_get_hsdir_spread_store(void); + +void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int is_next_period, + int is_client, smartlist_t *responsible_dirs); + +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + +void hs_inc_rdv_stream_counter(origin_circuit_t *circ); +void hs_dec_rdv_stream_counter(origin_circuit_t *circ); #ifdef HS_COMMON_PRIVATE +STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); + #ifdef TOR_UNIT_TESTS STATIC uint64_t get_time_period_length(void); -STATIC uint64_t get_time_period_num(time_t now); + +STATIC uint8_t *get_first_cached_disaster_srv(void); +STATIC uint8_t *get_second_cached_disaster_srv(void); #endif /* TOR_UNIT_TESTS */ diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 2393eac252..9a1e377155 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -58,6 +58,7 @@ #include "hs_descriptor.h" #include "or.h" +#include "circuitbuild.h" #include "ed25519_cert.h" /* Trunnel interface. */ #include "parsecommon.h" #include "rendcache.h" @@ -78,6 +79,7 @@ #define str_intro_auth_required "intro-auth-required" #define str_single_onion "single-onion-service" #define str_intro_point "introduction-point" +#define str_ip_onion_key "onion-key" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" #define str_ip_enc_key_cert "enc-key-cert" @@ -136,6 +138,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { /* Descriptor ruleset for the introduction points section. */ static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), + T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), @@ -144,29 +147,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Free a descriptor intro point object. */ -STATIC void -desc_intro_point_free(hs_desc_intro_point_t *ip) -{ - if (!ip) { - return; - } - if (ip->link_specifiers) { - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, tor_free(ls)); - smartlist_free(ip->link_specifiers); - } - tor_cert_free(ip->auth_key_cert); - tor_cert_free(ip->enc_key_cert); - if (ip->legacy.key) { - crypto_pk_free(ip->legacy.key); - } - if (ip->legacy.cert.encoded) { - tor_free(ip->legacy.cert.encoded); - } - tor_free(ip); -} - /* Free the content of the plaintext section of a descriptor. */ static void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) @@ -197,7 +177,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) } if (desc->intro_points) { SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, - desc_intro_point_free(ip)); + hs_desc_intro_point_free(ip)); smartlist_free(desc->intro_points); } memwipe(desc, 0, sizeof(*desc)); @@ -256,7 +236,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); offset += sizeof(desc->subcredential); /* Copy revision counter value. */ - set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter)); offset += sizeof(uint64_t); tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); } @@ -383,6 +363,14 @@ encode_link_specifiers(const smartlist_t *specs) link_specifier_set_ls_len(ls, legacy_id_len); break; } + case LS_ED25519_ID: + { + size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); + uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); + memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); + link_specifier_set_ls_len(ls, ed25519_id_len); + break; + } default: tor_assert(0); } @@ -479,6 +467,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip) return encoded; } +/* Encode an introduction point onion key. Return a newly allocated string + * with it. On failure, return NULL. */ +static char * +encode_onion_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "onion-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { + goto done; + } + tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); + + done: + return encoded; +} + /* Encode an introduction point object and return a newly allocated string * with it. On failure, return NULL. */ static char * @@ -498,6 +506,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(ls_str); } + /* Onion key encoding. */ + { + char *encoded_onion_key = encode_onion_key(ip); + if (encoded_onion_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_onion_key); + tor_free(encoded_onion_key); + } + /* Authentication key encoding. */ { char *encoded_cert; @@ -988,6 +1006,10 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(encoded_out); tor_assert(desc->plaintext_data.version == 3); + if (BUG(desc->subcredential == NULL)) { + goto err; + } + /* Build the non-encrypted values. */ { char *encoded_cert; @@ -1134,6 +1156,15 @@ decode_link_specifiers(const char *encoded) memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), sizeof(hs_spec->u.legacy_id)); break; + case LS_ED25519_ID: + /* Both are known at compile time so let's make sure they are the same + * else we can copy memory out of bound. */ + tor_assert(link_specifier_getlen_un_ed25519_id(ls) == + sizeof(hs_spec->u.ed25519_id)); + memcpy(hs_spec->u.ed25519_id, + link_specifier_getconstarray_un_ed25519_id(ls), + sizeof(hs_spec->u.ed25519_id)); + break; default: goto err; } @@ -1626,6 +1657,50 @@ decode_intro_legacy_key(const directory_token_t *tok, return -1; } +/* Dig into the descriptor <b>tokens</b> to find the onion key we should use + * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it + * was found and well-formed, otherwise return -1 in case of errors. */ +static int +set_intro_point_onion_key(curve25519_public_key_t *onion_key_out, + const smartlist_t *tokens) +{ + int retval = -1; + smartlist_t *onion_keys = NULL; + + tor_assert(onion_key_out); + + onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY); + if (!onion_keys) { + log_warn(LD_REND, "Descriptor did not contain intro onion keys"); + goto err; + } + + SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) { + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); + + /* Try to find an ntor key, it's the only recognized type right now */ + if (!strcmp(tok->args[0], "ntor")) { + if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); + goto err; + } + /* Got the onion key! Set the appropriate retval */ + retval = 0; + } + } SMARTLIST_FOREACH_END(tok); + + /* Log an error if we didn't find it :( */ + if (retval < 0) { + log_warn(LD_REND, "Descriptor did not contain ntor onion keys"); + } + + err: + smartlist_free(onion_keys); + return retval; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1651,17 +1726,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Ok we seem to have a well formed section containing enough tokens to * parse. Allocate our IP object and try to populate it. */ - ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t)); + ip = hs_desc_intro_point_new(); /* "introduction-point" SP link-specifiers NL */ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT); tor_assert(tok->n_args == 1); + /* Our constructor creates this list by default so free it. */ + smartlist_free(ip->link_specifiers); ip->link_specifiers = decode_link_specifiers(tok->args[0]); if (!ip->link_specifiers) { log_warn(LD_REND, "Introduction point has invalid link specifiers"); goto err; } + /* "onion-key" SP ntor SP key NL */ + if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) { + goto err; + } + /* "auth-key" NL certificate NL */ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY); tor_assert(tok->object_body); @@ -1733,7 +1815,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) goto done; err: - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; done: @@ -2215,7 +2297,7 @@ hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, hs_descriptor_t **desc_out) { - int ret; + int ret = -1; hs_descriptor_t *desc; tor_assert(encoded); @@ -2223,10 +2305,13 @@ hs_desc_decode_descriptor(const char *encoded, desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Subcredentials are optional. */ - if (subcredential) { - memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + if (BUG(!subcredential)) { + log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); + goto err; } + memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); if (ret < 0) { goto err; @@ -2352,3 +2437,110 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } +/* Return a newly allocated descriptor intro point. */ +hs_desc_intro_point_t * +hs_desc_intro_point_new(void) +{ + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + return ip; +} + +/* Free a descriptor intro point object. */ +void +hs_desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (ip == NULL) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + tor_cert_free(ip->enc_key_cert); + crypto_pk_free(ip->legacy.key); + tor_free(ip->legacy.cert.encoded); + tor_free(ip); +} + +/* Free the given descriptor link specifier. */ +void +hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls) +{ + if (ls == NULL) { + return; + } + tor_free(ls); +} + +/* Return a newly allocated descriptor link specifier using the given extend + * info and requested type. Return NULL on error. */ +hs_desc_link_specifier_t * +hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) +{ + hs_desc_link_specifier_t *ls = NULL; + + tor_assert(info); + + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = type; + switch (ls->type) { + case LS_IPV4: + if (info->addr.family != AF_INET) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_IPV6: + if (info->addr.family != AF_INET6) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_LEGACY_ID: + /* Bug out if the identity digest is not set */ + if (BUG(tor_mem_is_zero(info->identity_digest, + sizeof(info->identity_digest)))) { + goto err; + } + memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); + break; + case LS_ED25519_ID: + /* ed25519 keys are optional for intro points */ + if (ed25519_public_key_is_zero(&info->ed_identity)) { + goto err; + } + memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, + sizeof(ls->u.ed25519_id)); + break; + default: + /* Unknown type is code flow error. */ + tor_assert(0); + } + + return ls; + err: + tor_free(ls); + return NULL; +} + +/* From the given descriptor, remove and free every introduction point. */ +void +hs_descriptor_clear_intro_points(hs_descriptor_t *desc) +{ + smartlist_t *ips; + + tor_assert(desc); + + ips = desc->encrypted_data.intro_points; + if (ips) { + SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *, + ip, hs_desc_intro_point_free(ip)); + smartlist_clear(ips); + } +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 58c4089795..fa211d3917 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -23,12 +23,15 @@ /* The latest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 +/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours + * which is 180 minutes or 10800 seconds. */ +#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) /* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) /* Lifetime of certificate in the descriptor. This defines the lifetime of the * descriptor signing key and the cross certification cert of that key. */ -#define HS_DESC_CERT_LIFETIME (24 * 60 * 60) +#define HS_DESC_CERT_LIFETIME (36 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 /* Length of the secret input needed for the KDF construction which derives @@ -65,12 +68,14 @@ typedef struct hs_desc_link_specifier_t { * specification. */ uint8_t type; - /* It's either an address/port or a legacy identity fingerprint. */ + /* It must be one of these types, can't be more than one. */ union { /* IP address and port of the relay use to extend. */ tor_addr_port_t ap; /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ uint8_t legacy_id[DIGEST_LEN]; + /* ed25519 identity. A 32-byte key. */ + uint8_t ed25519_id[ED25519_PUBKEY_LEN]; } u; } hs_desc_link_specifier_t; @@ -80,6 +85,10 @@ typedef struct hs_desc_intro_point_t { * contains hs_desc_link_specifier_t object. It MUST have at least one. */ smartlist_t *link_specifiers; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* Authentication key used to establish the introduction point circuit and * cross-certifies the blinded public key for the replica thus signed by * the blinded key and in turn signs it. */ @@ -197,6 +206,11 @@ void hs_descriptor_free(hs_descriptor_t *desc); void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc); void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); +void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls); +hs_desc_link_specifier_t *hs_desc_link_specifier_new( + const extend_info_t *info, uint8_t type); +void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); + int hs_desc_encode_descriptor(const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, char **encoded_out); @@ -211,6 +225,9 @@ int hs_desc_decode_encrypted(const hs_descriptor_t *desc, size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); +hs_desc_intro_point_t *hs_desc_intro_point_new(void); +void hs_desc_intro_point_free(hs_desc_intro_point_t *ip); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ @@ -229,7 +246,6 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); -STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); STATIC size_t decode_superencrypted(const char *message, size_t message_len, uint8_t **encrypted_out); #endif /* HS_DESCRIPTOR_PRIVATE */ diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c index 5b5dc9aaff..e69350d82e 100644 --- a/src/or/hs_ident.c +++ b/src/or/hs_ident.c @@ -30,13 +30,20 @@ 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 circuit identifier src, return a newly allocated copy of it. + * This can't fail. */ +hs_ident_circuit_t * +hs_ident_circuit_dup(const hs_ident_circuit_t *src) +{ + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + /* For a given directory connection identifier src, return a newly allocated * copy of it. This can't fail. */ hs_ident_dir_conn_t * diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h index 8a7c3598cf..e259fde54d 100644 --- a/src/or/hs_ident.h +++ b/src/or/hs_ident.h @@ -52,27 +52,32 @@ typedef struct hs_ident_circuit_t { * 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; + /* (All circuit) Introduction point authentication key. It's also needed on + * the rendezvous circuit for the ntor handshake. It's used as the unique key + * of the introduction point so it should not be shared between multiple + * intro points. */ + ed25519_public_key_t intro_auth_pk; - /* (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 client rendezvous circuit) Introduction point encryption public + * key. We keep it in the rendezvous identifier for the ntor handshake. */ + curve25519_public_key_t intro_enc_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: + /* (Only service 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 client rendezvous circuit) Client ephemeral keypair needed for the + * e2e encryption with the service. */ + curve25519_keypair_t rendezvous_client_kp; + /* (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]; @@ -110,6 +115,7 @@ 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); +hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src); /* Directory connection identifier API. */ hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index 636b345b5b..cb4d6c02e5 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -24,6 +24,7 @@ #include "hs/cell_introduce1.h" #include "hs_circuitmap.h" +#include "hs_descriptor.h" #include "hs_intropoint.h" #include "hs_common.h" @@ -591,3 +592,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } +/* Clear memory allocated by the given intropoint object ip (but don't free the + * object itself). */ +void +hs_intropoint_clear(hs_intropoint_t *ip) +{ + if (ip == NULL) { + return; + } + tor_cert_free(ip->auth_key_cert); + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, + hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + memset(ip, 0, sizeof(hs_intropoint_t)); +} + diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index bfb1331ba0..5c77f07ec3 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -13,11 +13,11 @@ #include "torcert.h" /* Authentication key type in an ESTABLISH_INTRO cell. */ -enum hs_intro_auth_key_type { +typedef enum { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01, HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02, -}; +} hs_intro_auth_key_type_t; /* INTRODUCE_ACK status code. */ typedef enum { @@ -30,6 +30,9 @@ typedef enum { /* Object containing introduction point common data between the service and * the client side. */ typedef struct hs_intropoint_t { + /* Does this intro point only supports legacy ID ?. */ + unsigned int is_only_legacy : 1; + /* Authentication key certificate from the descriptor. */ tor_cert_t *auth_key_cert; /* A list of link specifier. */ @@ -47,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)); /* also used by rendservice.c */ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); +hs_intropoint_t *hs_intro_new(void); +void hs_intropoint_clear(hs_intropoint_t *ip); + #ifdef HS_INTROPOINT_PRIVATE #include "hs/cell_establish_intro.h" diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 5fde42ddbb..5f36964547 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -9,25 +9,62 @@ #define HS_SERVICE_PRIVATE #include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" #include "circuitlist.h" +#include "circuituse.h" #include "config.h" +#include "directory.h" +#include "main.h" +#include "networkstatus.h" +#include "nodelist.h" #include "relay.h" #include "rendservice.h" #include "router.h" #include "routerkeys.h" +#include "routerlist.h" +#include "statefile.h" +#include "hs_circuit.h" #include "hs_common.h" #include "hs_config.h" +#include "hs_circuit.h" +#include "hs_descriptor.h" +#include "hs_ident.h" #include "hs_intropoint.h" #include "hs_service.h" -#include "hs/cell_establish_intro.h" +/* Trunnel */ +#include "ed25519_cert.h" #include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* Helper macro. Iterate over every service in the global map. The var is the + * name of the service pointer. */ +#define FOR_EACH_SERVICE_BEGIN(var) \ + STMT_BEGIN \ + hs_service_t **var##_iter, *var; \ + HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \ + var = *var##_iter; +#define FOR_EACH_SERVICE_END } STMT_END ; + +/* Helper macro. Iterate over both current and previous descriptor of a + * service. The var is the name of the descriptor pointer. This macro skips + * any descriptor object of the service that is NULL. */ +#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \ + STMT_BEGIN \ + hs_service_descriptor_t *var; \ + for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \ + ++var ## _loop_idx) { \ + (var ## _loop_idx == 0) ? (var = service->desc_current) : \ + (var = service->desc_next); \ + if (var == NULL) continue; +#define FOR_EACH_DESCRIPTOR_END } STMT_END ; /* 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"; +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 @@ -35,6 +72,8 @@ static const char *address_tld = "onion"; * loading keys requires that we are an actual running tor process. */ static smartlist_t *hs_service_staging_list; +static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); + /* 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 @@ -137,10 +176,10 @@ static void set_service_default_config(hs_service_config_t *c, const or_options_t *options) { + (void) 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; @@ -167,6 +206,77 @@ service_clear_config(hs_service_config_t *config) memset(config, 0, sizeof(*config)); } +/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_min_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_introduce2", + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} + +/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_max_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_introduce2", + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} + +/* Return the minimum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_min_lifetime(void) +{ +#define MIN_INTRO_POINT_LIFETIME_TESTING 10 + if (get_options()->TestingTorNetwork) { + return MIN_INTRO_POINT_LIFETIME_TESTING; + } + + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_lifetime", + INTRO_POINT_LIFETIME_MIN_SECONDS, + 0, INT32_MAX); +} + +/* Return the maximum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_max_lifetime(void) +{ +#define MAX_INTRO_POINT_LIFETIME_TESTING 30 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_LIFETIME_TESTING; + } + + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_lifetime", + INTRO_POINT_LIFETIME_MAX_SECONDS, + 0, INT32_MAX); +} + +/* Return the number of extra introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_num_extra(void) +{ + /* The [0, 128] range bounds the number of extra introduction point allowed. + * Above 128 intro points, it's getting a bit crazy. */ + return networkstatus_get_param(NULL, "hs_intro_num_extra", + NUM_INTRO_POINTS_EXTRA, 0, 128); +} + /* Helper: Function that needs to return 1 for the HT for each loop which * frees every service in an hash map. */ static int @@ -201,13 +311,357 @@ service_free_all(void) } } +/* Free a given service intro point object. */ +STATIC void +service_intro_point_free(hs_service_intro_point_t *ip) +{ + if (!ip) { + return; + } + memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp)); + memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp)); + crypto_pk_free(ip->legacy_key); + replaycache_free(ip->replay_cache); + hs_intropoint_clear(&ip->base); + tor_free(ip); +} + +/* Helper: free an hs_service_intro_point_t object. This function is used by + * digest256map_free() which requires a void * pointer. */ +static void +service_intro_point_free_(void *obj) +{ + service_intro_point_free(obj); +} + +/* Return a newly allocated service intro point and fully initialized from the + * given extend_info_t ei if non NULL. If is_legacy is true, we also generate + * the legacy key. On error, NULL is returned. */ +STATIC hs_service_intro_point_t * +service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip; + + ip = tor_malloc_zero(sizeof(*ip)); + /* We'll create the key material. No need for extra strong, those are short + * term keys. */ + ed25519_keypair_generate(&ip->auth_key_kp, 0); + + { /* Set introduce2 max cells limit */ + int32_t min_introduce2_cells = get_intro_point_min_introduce2(); + int32_t max_introduce2_cells = get_intro_point_max_introduce2(); + if (BUG(max_introduce2_cells < min_introduce2_cells)) { + goto err; + } + ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells, + max_introduce2_cells); + } + { /* Set intro point lifetime */ + int32_t intro_point_min_lifetime = get_intro_point_min_lifetime(); + int32_t intro_point_max_lifetime = get_intro_point_max_lifetime(); + if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { + goto err; + } + ip->time_to_expire = time(NULL) + + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); + } + + ip->replay_cache = replaycache_new(0, 0); + + /* Initialize the base object. We don't need the certificate object. */ + ip->base.link_specifiers = smartlist_new(); + + /* Generate the encryption key for this intro point. */ + curve25519_keypair_generate(&ip->enc_key_kp, 0); + /* Figure out if this chosen node supports v3 or is legacy only. */ + if (is_legacy) { + ip->base.is_only_legacy = 1; + /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */ + ip->legacy_key = crypto_pk_new(); + if (crypto_pk_generate_key(ip->legacy_key) < 0) { + goto err; + } + } + + if (ei == NULL) { + goto done; + } + + /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are + * mandatory. */ + ls = hs_desc_link_specifier_new(ei, LS_IPV4); + /* It is impossible to have an extend info object without a v4. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID); + /* It is impossible to have an extend info object without an identity + * digest. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + /* ed25519 identity key is optional for intro points */ + ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + + /* IPv6 is optional. */ + ls = hs_desc_link_specifier_new(ei, LS_IPV6); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + + /* Finally, copy onion key from the extend_info_t object. */ + memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); + + done: + return ip; + err: + service_intro_point_free(ip); + return NULL; +} + +/* Add the given intro point object to the given intro point map. The intro + * point MUST have its RSA encryption key set if this is a legacy type or the + * authentication key set otherwise. */ +STATIC void +service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) +{ + hs_service_intro_point_t *old_ip_entry; + + tor_assert(map); + tor_assert(ip); + + old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + /* Make sure we didn't just try to double-add an intro point */ + tor_assert_nonfatal(!old_ip_entry); +} + +/* For a given service, remove the intro point from that service's descriptors + * (check both current and next descriptor) */ +STATIC void +service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + tor_assert(service); + tor_assert(ip); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We'll try to remove the descriptor on both descriptors which is not + * very expensive to do instead of doing loopup + remove. */ + digest256map_remove(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey); + } FOR_EACH_DESCRIPTOR_END; +} + +/* For a given service and authentication key, return the intro point or NULL + * if not found. This will check both descriptors in the service. */ +STATIC hs_service_intro_point_t * +service_intro_point_find(const hs_service_t *service, + const ed25519_public_key_t *auth_key) +{ + hs_service_intro_point_t *ip = NULL; + + tor_assert(service); + tor_assert(auth_key); + + /* Trying all descriptors to find the right intro point. + * + * Even if we use the same node as intro point in both descriptors, the node + * will have a different intro auth key for each descriptor since we generate + * a new one everytime we pick an intro point. + * + * After #22893 gets implemented, intro points will be moved to be + * per-service instead of per-descriptor so this function will need to + * change. + */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if ((ip = digest256map_get(desc->intro_points.map, + auth_key->pubkey)) != NULL) { + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return ip; +} + +/* For a given service and intro point, return the descriptor for which the + * intro point is assigned to. NULL is returned if not found. */ +STATIC hs_service_descriptor_t * +service_desc_find_by_intro(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_service_descriptor_t *descp = NULL; + + tor_assert(service); + tor_assert(ip); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (digest256map_get(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey)) { + descp = desc; + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return descp; +} + +/* From a circuit identifier, get all the possible objects associated with the + * ident. If not NULL, service, ip or desc are set if the object can be found. + * They are untouched if they can't be found. + * + * This is an helper function because we do those lookups often so it's more + * convenient to simply call this functions to get all the things at once. */ +STATIC void +get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc) +{ + hs_service_t *s; + + tor_assert(ident); + + /* Get service object from the circuit identifier. */ + s = find_service(hs_service_map, &ident->identity_pk); + if (s && service) { + *service = s; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + if (s && ip) { + *ip = service_intro_point_find(s, &ident->intro_auth_pk); + } + + /* Get the descriptor for this introduction point and service. */ + if (s && ip && *ip && desc) { + *desc = service_desc_find_by_intro(s, *ip); + } +} + +/* From a given intro point, return the first link specifier of type + * encountered in the link specifier list. Return NULL if it can't be found. + * + * The caller does NOT have ownership of the object, the intro point does. */ +static hs_desc_link_specifier_t * +get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) +{ + hs_desc_link_specifier_t *lnk_spec = NULL; + + tor_assert(ip); + + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == type) { + lnk_spec = ls; + goto end; + } + } SMARTLIST_FOREACH_END(ls); + + end: + return lnk_spec; +} + +/* Given a service intro point, return the node_t associated to it. This can + * return NULL if the given intro point has no legacy ID or if the node can't + * be found in the consensus. */ +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip) +{ + const hs_desc_link_specifier_t *ls; + + tor_assert(ip); + + ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + if (BUG(!ls)) { + return NULL; + } + /* XXX In the future, we want to only use the ed25519 ID (#22173). */ + return node_get_by_id((const char *) ls->u.legacy_id); +} + +/* Given a service intro point, return the extend_info_t for it. This can + * return NULL if the node can't be found for the intro point or the extend + * info can't be created for the found node. If direct_conn is set, the extend + * info is validated on if we can connect directly. */ +static extend_info_t * +get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, + unsigned int direct_conn) +{ + extend_info_t *info = NULL; + const node_t *node; + + tor_assert(ip); + + node = get_node_from_intro_point(ip); + if (node == NULL) { + /* This can happen if the relay serving as intro point has been removed + * from the consensus. In that case, the intro point will be removed from + * the descriptor during the scheduled events. */ + goto end; + } + + /* In the case of a direct connection (single onion service), it is possible + * our firewall policy won't allow it so this can return a NULL value. */ + info = extend_info_from_node(node, direct_conn); + + end: + return info; +} + +/* Return the number of introduction points that are established for the + * given descriptor. */ +static unsigned int +count_desc_circuit_established(const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + count += ip->circuit_established; + } DIGEST256MAP_FOREACH_END; + + return count; +} + /* Close all rendezvous circuits for the given service. */ static void close_service_rp_circuits(hs_service_t *service) { + origin_circuit_t *ocirc = NULL; + tor_assert(service); - /* XXX: To implement. */ - return; + + /* The reason we go over all circuit instead of using the circuitmap API is + * because most hidden service circuits are rendezvous circuits so there is + * no real improvement at getting all rendezvous circuits from the + * circuitmap and then going over them all to find the right ones. + * Furthermore, another option would have been to keep a list of RP cookies + * for a service but it creates an engineering complexity since we don't + * have a "RP circuit closed" event to clean it up properly so we avoid a + * memory DoS possibility. */ + + while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) { + /* Only close circuits that are v3 and for this service. */ + if (ocirc->hs_ident != NULL && + ed25519_pubkey_eq(ô->hs_ident->identity_pk, + &service->keys.identity_pk)) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, it is removed from the + * hs circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } } /* Close the circuit(s) for the given map of introduction points. */ @@ -218,13 +672,11 @@ close_intro_circuits(hs_service_intropoints_t *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); + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); 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 is considered old/uneeded. When freed, the circuit is removed + * from the HS circuitmap. */ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); } } DIGEST256MAP_FOREACH_END; @@ -236,12 +688,9 @@ 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); - } + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + close_intro_circuits(&desc->intro_points); + } FOR_EACH_DESCRIPTOR_END; } /* Close any circuits related to the given service. */ @@ -269,7 +718,7 @@ move_descriptor_intro_points(hs_service_descriptor_t *src, tor_assert(src); tor_assert(dst); - /* XXX: Free dst introduction points. */ + digest256map_free(dst->intro_points.map, service_intro_point_free_); dst->intro_points.map = src->intro_points.map; /* Nullify the source. */ src->intro_points.map = NULL; @@ -283,7 +732,6 @@ 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); } @@ -339,7 +787,6 @@ static void register_all_services(void) { struct hs_service_ht *new_service_map; - hs_service_t *s, **iter; tor_assert(hs_service_staging_list); @@ -359,6 +806,8 @@ register_all_services(void) move_ephemeral_services(hs_service_map, new_service_map); SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) { + hs_service_t *s; + /* 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); @@ -386,9 +835,9 @@ register_all_services(void) /* 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); - } + FOR_EACH_SERVICE_BEGIN(service) { + close_service_circuits(service); + } FOR_EACH_SERVICE_END; /* Time to make the switch. We'll clear the staging list because its content * has now changed ownership to the map. */ @@ -399,25 +848,22 @@ register_all_services(void) /* 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 +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]; + char *addr_buf = NULL; 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); + tor_asprintf(&addr_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) { + if (write_str_to_file(fname, addr_buf, 0) < 0) { log_warn(LD_REND, "Could not write onion address to hostname file %s", escaped(fname)); goto end; @@ -437,6 +883,7 @@ write_address_to_file(const hs_service_t *service, const char *fname_) ret = 0; end: tor_free(fname); + tor_free(addr_buf); return ret; } @@ -510,13 +957,1960 @@ load_service_keys(hs_service_t *service) return ret; } +/* Free a given service descriptor object and all key material is wiped. */ +STATIC void +service_descriptor_free(hs_service_descriptor_t *desc) +{ + if (!desc) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); + memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); + SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); + smartlist_free(desc->hsdir_missing_info); + /* Cleanup all intro points. */ + digest256map_free(desc->intro_points.map, service_intro_point_free_); + digestmap_free(desc->intro_points.failed_id, tor_free_); + tor_free(desc); +} + +/* Return a newly allocated service descriptor object. */ +STATIC hs_service_descriptor_t * +service_descriptor_new(void) +{ + hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc)); + sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); + /* Initialize the intro points map. */ + sdesc->intro_points.map = digest256map_new(); + sdesc->intro_points.failed_id = digestmap_new(); + sdesc->hsdir_missing_info = smartlist_new(); + return sdesc; +} + +/* From the given service, remove all expired failing intro points for each + * descriptor. */ +static void +remove_expired_failing_intro(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the failing intro points list. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) { + time_t failure_time = *t; + if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) { + MAP_DEL_CURRENT(key); + tor_free(t); + } + } DIGESTMAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* For the given descriptor desc, put all node_t object found from its failing + * intro point list and put them in the given node_list. */ +static void +setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, + smartlist_t *node_list) +{ + tor_assert(desc); + tor_assert(node_list); + + DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) { + (void) t; /* Make gcc happy. */ + const node_t *node = node_get_by_id(key); + if (node) { + smartlist_add(node_list, (void *) node); + } + } DIGESTMAP_FOREACH_END; +} + +/* For the given failing intro point ip, we add its time of failure to the + * failed map and index it by identity digest (legacy ID) in the descriptor + * desc failed id map. */ +static void +remember_failing_intro_point(const hs_service_intro_point_t *ip, + hs_service_descriptor_t *desc, time_t now) +{ + time_t *time_of_failure, *prev_ptr; + const hs_desc_link_specifier_t *legacy_ls; + + tor_assert(ip); + tor_assert(desc); + + time_of_failure = tor_malloc_zero(sizeof(time_t)); + *time_of_failure = now; + legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + tor_assert(legacy_ls); + prev_ptr = digestmap_set(desc->intro_points.failed_id, + (const char *) legacy_ls->u.legacy_id, + time_of_failure); + tor_free(prev_ptr); +} + +/* Copy the descriptor link specifier object from src to dst. */ +static void +link_specifier_copy(hs_desc_link_specifier_t *dst, + const hs_desc_link_specifier_t *src) +{ + tor_assert(dst); + tor_assert(src); + memcpy(dst, src, sizeof(hs_desc_link_specifier_t)); +} + +/* Using a given descriptor signing keypair signing_kp, a service intro point + * object ip and the time now, setup the content of an already allocated + * descriptor intro desc_ip. + * + * Return 0 on success else a negative value. */ +static int +setup_desc_intro_point(const ed25519_keypair_t *signing_kp, + const hs_service_intro_point_t *ip, + time_t now, hs_desc_intro_point_t *desc_ip) +{ + int ret = -1; + time_t nearest_hour = now - (now % 3600); + + tor_assert(signing_kp); + tor_assert(ip); + tor_assert(desc_ip); + + /* Copy the onion key. */ + memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key)); + + /* Key and certificate material. */ + desc_ip->auth_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_AUTH_HS_IP_KEY, + &ip->auth_key_kp.pubkey, + nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->auth_key_cert == NULL) { + log_warn(LD_REND, "Unable to create intro point auth-key certificate"); + goto done; + } + + /* Copy link specifier(s). */ + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, ls) { + hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup)); + link_specifier_copy(dup, ls); + smartlist_add(desc_ip->link_specifiers, dup); + } SMARTLIST_FOREACH_END(ls); + + /* For a legacy intro point, we'll use an RSA/ed cross certificate. */ + if (ip->base.is_only_legacy) { + desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key); + /* Create cross certification cert. */ + ssize_t cert_len = tor_make_rsa_ed25519_crosscert( + &signing_kp->pubkey, + desc_ip->legacy.key, + nearest_hour + HS_DESC_CERT_LIFETIME, + &desc_ip->legacy.cert.encoded); + if (cert_len < 0) { + log_warn(LD_REND, "Unable to create enc key legacy cross cert."); + goto done; + } + desc_ip->legacy.cert.len = cert_len; + } + + /* Encryption key and its cross certificate. */ + { + ed25519_public_key_t ed25519_pubkey; + + /* Use the public curve25519 key. */ + memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey, + sizeof(desc_ip->enc_key)); + /* The following can't fail. */ + ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey, + &ip->enc_key_kp.pubkey, + 0); + desc_ip->enc_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_CROSS_HS_IP_KEYS, + &ed25519_pubkey, nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->enc_key_cert == NULL) { + log_warn(LD_REND, "Unable to create enc key curve25519 cross cert."); + goto done; + } + } + /* Success. */ + ret = 0; + + done: + return ret; +} + +/* Using the given descriptor from the given service, build the descriptor + * intro point list so we can then encode the descriptor for publication. This + * function does not pick intro points, they have to be in the descriptor + * current map. Cryptographic material (keys) must be initialized in the + * descriptor for this function to make sense. */ +static void +build_desc_intro_points(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + hs_desc_encrypted_data_t *encrypted; + + tor_assert(service); + tor_assert(desc); + + /* Ease our life. */ + encrypted = &desc->desc->encrypted_data; + /* Cleanup intro points, we are about to set them from scratch. */ + hs_descriptor_clear_intro_points(desc->desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new(); + if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) { + hs_desc_intro_point_free(desc_ip); + continue; + } + /* We have a valid descriptor intro point. Add it to the list. */ + smartlist_add(encrypted->intro_points, desc_ip); + } DIGEST256MAP_FOREACH_END; +} + +/* Populate the descriptor encrypted section fomr the given service object. + * This will generate a valid list of introduction points that can be used + * after for circuit creation. Return 0 on success else -1 on error. */ +static int +build_service_desc_encrypted(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + hs_desc_encrypted_data_t *encrypted; + + tor_assert(service); + tor_assert(desc); + + encrypted = &desc->desc->encrypted_data; + + encrypted->create2_ntor = 1; + encrypted->single_onion_service = service->config.is_single_onion; + + /* Setup introduction points from what we have in the service. */ + if (encrypted->intro_points == NULL) { + encrypted->intro_points = smartlist_new(); + } + /* We do NOT build introduction point yet, we only do that once the circuit + * have been opened. Until we have the right number of introduction points, + * we do not encode anything in the descriptor. */ + + /* XXX: Support client authorization (#20700). */ + encrypted->intro_auth_types = NULL; + return 0; +} + +/* Populare the descriptor plaintext section from the given service object. + * The caller must make sure that the keys in the descriptors are valid that + * is are non-zero. Return 0 on success else -1 on error. */ +static int +build_service_desc_plaintext(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + int ret = -1; + hs_desc_plaintext_data_t *plaintext; + + tor_assert(service); + tor_assert(desc); + /* XXX: Use a "assert_desc_ok()" ? */ + tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp, + sizeof(desc->blinded_kp))); + tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp, + sizeof(desc->signing_kp))); + + /* Set the subcredential. */ + hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + desc->desc->subcredential); + + plaintext = &desc->desc->plaintext_data; + + plaintext->version = service->config.version; + plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME; + plaintext->signing_key_cert = + tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC, + &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (plaintext->signing_key_cert == NULL) { + log_warn(LD_REND, "Unable to create descriptor signing certificate for " + "service %s", + safe_str_client(service->onion_address)); + goto end; + } + /* Copy public key material to go in the descriptor. */ + ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey); + ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey); + /* Success. */ + ret = 0; + + end: + return ret; +} + +/* For the given service and descriptor object, create the key material which + * is the blinded keypair and the descriptor signing keypair. Return 0 on + * success else -1 on error where the generated keys MUST be ignored. */ +static int +build_service_desc_keys(const hs_service_t *service, + hs_service_descriptor_t *desc, + uint64_t time_period_num) +{ + int ret = 0; + ed25519_keypair_t kp; + + tor_assert(desc); + tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk, + ED25519_PUBKEY_LEN)); + + /* XXX: Support offline key feature (#18098). */ + + /* Copy the identity keys to the keypair so we can use it to create the + * blinded key. */ + memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey)); + memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey)); + /* Build blinded keypair for this time period. */ + hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp); + /* Let's not keep too much traces of our keys in memory. */ + memwipe(&kp, 0, sizeof(kp)); + + /* No need for extra strong, this is a temporary key only for this + * descriptor. Nothing long term. */ + if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) { + log_warn(LD_REND, "Can't generate descriptor signing keypair for " + "service %s", + safe_str_client(service->onion_address)); + ret = -1; + } + + return ret; +} + +/* Given a service and the current time, build a descriptor for the service. + * This function does not pick introduction point, this needs to be done by + * the update function. On success, desc_out will point to the newly allocated + * descriptor object. + * + * This can error if we are unable to create keys or certificate. */ +static void +build_service_descriptor(hs_service_t *service, time_t now, + uint64_t time_period_num, + hs_service_descriptor_t **desc_out) +{ + char *encoded_desc; + hs_service_descriptor_t *desc; + + tor_assert(service); + tor_assert(desc_out); + + desc = service_descriptor_new(); + desc->time_period_num = time_period_num; + + /* Create the needed keys so we can setup the descriptor content. */ + if (build_service_desc_keys(service, desc, time_period_num) < 0) { + goto err; + } + /* Setup plaintext descriptor content. */ + if (build_service_desc_plaintext(service, desc, now) < 0) { + goto err; + } + /* Setup encrypted descriptor content. */ + if (build_service_desc_encrypted(service, desc) < 0) { + goto err; + } + + /* Set the revision counter for this descriptor */ + set_descriptor_revision_counter(desc->desc); + + /* Let's make sure that we've created a descriptor that can actually be + * encoded properly. This function also checks if the encoded output is + * decodable after. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto err; + } + tor_free(encoded_desc); + + /* Assign newly built descriptor to the next slot. */ + *desc_out = desc; + return; + + err: + service_descriptor_free(desc); +} + +/* Build descriptors for each service if needed. There are conditions to build + * a descriptor which are details in the function. */ +STATIC void +build_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + if (service->desc_current == NULL) { + /* This means we just booted up because else this descriptor will never + * be NULL as it should always point to the descriptor that was in + * desc_next after rotation. */ + build_service_descriptor(service, now, hs_get_time_period_num(now), + &service->desc_current); + + log_info(LD_REND, "Hidden service %s current descriptor successfully " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); + } + /* A next descriptor to NULL indicate that we need to build a fresh one if + * we are in the overlap period for the _next_ time period since it means + * we either just booted or we just rotated our descriptors. */ + if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) { + build_service_descriptor(service, now, hs_get_next_time_period_num(now), + &service->desc_next); + log_info(LD_REND, "Hidden service %s next descriptor successfully " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); + } + } FOR_EACH_DESCRIPTOR_END; +} + +/* Randomly pick a node to become an introduction point but not present in the + * given exclude_nodes list. The chosen node is put in the exclude list + * regardless of success or not because in case of failure, the node is simply + * unsusable from that point on. If direct_conn is set, try to pick a node + * that our local firewall/policy allows to directly connect to and if not, + * fallback to a normal 3-hop node. Return a newly allocated service intro + * point ready to be used for encoding. NULL on error. */ +static hs_service_intro_point_t * +pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) +{ + const node_t *node; + extend_info_t *info = NULL; + hs_service_intro_point_t *ip = NULL; + /* Normal 3-hop introduction point flags. */ + router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC; + /* Single onion flags. */ + router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN; + + node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes, + direct_conn ? direct_flags : flags); + if (node == NULL && direct_conn) { + /* Unable to find a node for direct connection, let's fall back to a + * normal 3-hop node. */ + node = router_choose_random_node(exclude_nodes, + get_options()->ExcludeNodes, flags); + } + if (!node) { + goto err; + } + + /* We have a suitable node, add it to the exclude list. We do this *before* + * we can validate the extend information because even in case of failure, + * we don't want to use that node anymore. */ + smartlist_add(exclude_nodes, (void *) node); + + /* We do this to ease our life but also this call makes appropriate checks + * of the node object such as validating ntor support for instance. */ + info = extend_info_from_node(node, direct_conn); + if (BUG(info == NULL)) { + goto err; + } + + /* Let's do a basic sanity check here so that we don't end up advertising the + * ed25519 identity key of relays that don't actually support the link + * protocol */ + if (!node_supports_ed25519_link_authentication(node)) { + tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); + } + + /* Create our objects and populate them with the node information. */ + ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node)); + if (ip == NULL) { + goto err; + } + extend_info_free(info); + return ip; + err: + service_intro_point_free(ip); + extend_info_free(info); + return NULL; +} + +/* For a given descriptor from the given service, pick any needed intro points + * and update the current map with those newly picked intro points. Return the + * number node that might have been added to the descriptor current map. */ +static unsigned int +pick_needed_intro_points(hs_service_t *service, + hs_service_descriptor_t *desc) +{ + int i = 0, num_needed_ip; + smartlist_t *exclude_nodes = smartlist_new(); + + tor_assert(service); + tor_assert(desc); + + /* Compute how many intro points we actually need to open. */ + num_needed_ip = service->config.num_intro_points - + digest256map_size(desc->intro_points.map); + if (BUG(num_needed_ip < 0)) { + /* Let's not make tor freak out here and just skip this. */ + goto done; + } + + /* We want to end up with config.num_intro_points intro points, but if we + * have no intro points at all (chances are they all cycled or we are + * starting up), we launch get_intro_point_num_extra() extra circuits and + * use the first config.num_intro_points that complete. See proposal #155, + * section 4 for the rationale of this which is purely for performance. + * + * The ones after the first config.num_intro_points will be converted to + * 'General' internal circuits and then we'll drop them from the list of + * intro points. */ + if (digest256map_size(desc->intro_points.map) == 0) { + num_needed_ip += get_intro_point_num_extra(); + } + + /* Build an exclude list of nodes of our intro point(s). The expiring intro + * points are OK to pick again because this is afterall a concept of round + * robin so they are considered valid nodes to pick again. */ + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *intro_node = get_node_from_intro_point(ip); + if (intro_node) { + smartlist_add(exclude_nodes, (void*)intro_node); + } + } DIGEST256MAP_FOREACH_END; + /* Also, add the failing intro points that our descriptor encounteered in + * the exclude node list. */ + setup_intro_point_exclude_list(desc, exclude_nodes); + + for (i = 0; i < num_needed_ip; i++) { + hs_service_intro_point_t *ip; + + /* This function will add the picked intro point node to the exclude nodes + * list so we don't pick the same one at the next iteration. */ + ip = pick_intro_point(service->config.is_single_onion, exclude_nodes); + if (ip == NULL) { + /* If we end up unable to pick an introduction point it is because we + * can't find suitable node and calling this again is highly unlikely to + * give us a valid node all of the sudden. */ + log_info(LD_REND, "Unable to find a suitable node to be an " + "introduction point for service %s.", + safe_str_client(service->onion_address)); + goto done; + } + /* Valid intro point object, add it to the descriptor current map. */ + service_intro_point_add(desc->intro_points.map, ip); + } + /* We've successfully picked all our needed intro points thus none are + * missing which will tell our upload process to expect the number of + * circuits to be the number of configured intro points circuits and not the + * number of intro points object that we have. */ + desc->missing_intro_points = 0; + + /* Success. */ + done: + /* We don't have ownership of the node_t object in this list. */ + smartlist_free(exclude_nodes); + return i; +} + +/* Update the given descriptor from the given service. The possible update + * actions includes: + * - Picking missing intro points if needed. + * - Incrementing the revision counter if needed. + */ +static void +update_service_descriptor(hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + tor_assert(desc->desc); + + num_intro_points = digest256map_size(desc->intro_points.map); + + /* Pick any missing introduction point(s). */ + if (num_intro_points < service->config.num_intro_points) { + unsigned int num_new_intro_points = pick_needed_intro_points(service, + desc); + if (num_new_intro_points != 0) { + log_info(LD_REND, "Service %s just picked %u intro points and wanted " + "%u. It currently has %d intro points. " + "Launching ESTABLISH_INTRO circuit shortly.", + safe_str_client(service->onion_address), + num_new_intro_points, + service->config.num_intro_points - num_intro_points, + num_intro_points); + /* We'll build those introduction point into the descriptor once we have + * confirmation that the circuits are opened and ready. However, + * indicate that this descriptor should be uploaded from now on. */ + desc->next_upload_time = now; + } + /* Were we able to pick all the intro points we needed? If not, we'll + * flag the descriptor that it's missing intro points because it + * couldn't pick enough which will trigger a descriptor upload. */ + if ((num_new_intro_points + num_intro_points) < + service->config.num_intro_points) { + desc->missing_intro_points = 1; + } + } +} + +/* Update descriptors for each service if needed. */ +STATIC void +update_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + /* We'll try to update each descriptor that is if certain conditions apply + * in order for the descriptor to be updated. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + update_service_descriptor(service, desc, now); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Return true iff the given intro point has expired that is it has been used + * for too long or we've reached our max seen INTRODUCE2 cell. */ +STATIC int +intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now) +{ + tor_assert(ip); + + if (ip->introduce2_count >= ip->introduce2_max) { + goto expired; + } + + if (ip->time_to_expire <= now) { + goto expired; + } + + /* Not expiring. */ + return 0; + expired: + return 1; +} + +/* Go over the given set of intro points for each service and remove any + * invalid ones. The conditions for removal are: + * + * - The node doesn't exists anymore (not in consensus) + * OR + * - The intro point maximum circuit retry count has been reached and no + * circuit can be found associated with it. + * OR + * - The intro point has expired and we should pick a new one. + * + * If an intro point is removed, the circuit (if any) is immediately close. + * If a circuit can't be found, the intro point is kept if it hasn't reached + * its maximum circuit retry value and thus should be retried. */ +static void +cleanup_intro_points(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the intro points. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Go over the current intro points we have, make sure they are still + * valid and remove any of them that aren't. */ + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *node = get_node_from_intro_point(ip); + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + int has_expired = intro_point_should_expire(ip, now); + + /* We cleanup an intro point if it has expired or if we do not know the + * node_t anymore (removed from our latest consensus) or if we've + * reached the maximum number of retry with a non existing circuit. */ + if (has_expired || node == NULL || + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + /* Remove intro point from descriptor map. We'll add it to the failed + * map if we retried it too many times. */ + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + + /* XXX: Legacy code does NOT do that, it keeps the circuit open until + * a new descriptor is uploaded and then closed all expiring intro + * point circuit. Here, we close immediately and because we just + * discarded the intro point, a new one will be selected, a new + * descriptor created and uploaded. There is no difference to an + * attacker between the timing of a new consensus and intro point + * rotation (possibly?). */ + if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) { + /* After this, no new cells will be handled on the circuit. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + continue; + } + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/** We just entered overlap period and we need to rotate our <b>service</b> + * descriptors */ +static void +rotate_service_descriptors(hs_service_t *service) +{ + if (service->desc_current) { + /* Close all IP circuits for the descriptor. */ + close_intro_circuits(&service->desc_current->intro_points); + /* We don't need this one anymore, we won't serve any clients coming with + * this service descriptor. */ + service_descriptor_free(service->desc_current); + } + /* The next one become the current one and emptying the next will trigger + * a descriptor creation for it. */ + service->desc_current = service->desc_next; + service->desc_next = NULL; +} + +/* Rotate descriptors for each service if needed. If we are just entering + * the overlap period, rotate them that is point the previous descriptor to + * the current and cleanup the previous one. A non existing current + * descriptor will trigger a descriptor build for the next time period. */ +STATIC void +rotate_all_descriptors(time_t now) +{ + /* XXX We rotate all our service descriptors at once. In the future it might + * be wise, to rotate service descriptors independently to hide that all + * those descriptors are on the same tor instance */ + + FOR_EACH_SERVICE_BEGIN(service) { + /* We are _not_ in the overlap period so skip rotation. */ + if (!hs_overlap_mode_is_active(NULL, now)) { + service->state.in_overlap_period = 0; + continue; + } + /* We've entered the overlap period already so skip rotation. */ + if (service->state.in_overlap_period) { + continue; + } + /* It's the first time the service encounters the overlap period so flag + * it in order to make sure we don't rotate at next check. */ + service->state.in_overlap_period = 1; + + /* If we have a next descriptor lined up, rotate the descriptors so that it + * becomes current. */ + if (service->desc_next) { + rotate_service_descriptors(service); + } + log_info(LD_REND, "We've just entered the overlap period. Service %s " + "descriptors have been rotated!", + safe_str_client(service->onion_address)); + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all our services are up + * to date and ready for the other scheduled events. This includes looking at + * the introduction points status and descriptor rotation time. */ +STATIC void +run_housekeeping_event(time_t now) +{ + /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are + * simply moving things around or removing uneeded elements. */ + + FOR_EACH_SERVICE_BEGIN(service) { + /* Cleanup invalid intro points from the service descriptor. */ + cleanup_intro_points(service, now); + + /* Remove expired failing intro point from the descriptor failed list. We + * reset them at each INTRO_CIRC_RETRY_PERIOD. */ + remove_expired_failing_intro(service, now); + + /* At this point, the service is now ready to go through the scheduled + * events guaranteeing a valid state. Intro points might be missing from + * the descriptors after the cleanup but the update/build process will + * make sure we pick those missing ones. */ + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all descriptors are up to + * date. Once this returns, each service descriptor needs to be considered for + * new introduction circuits and then for upload. */ +static void +run_build_descriptor_event(time_t now) +{ + /* For v2 services, this step happens in the upload event. */ + + /* Run v3+ events. */ + /* We start by rotating the descriptors only if needed. */ + rotate_all_descriptors(now); + + /* Then, we'll try to build new descriptors that we might need. The + * condition is that the next descriptor is non existing because it has + * been rotated or we just started up. */ + build_all_descriptors(now); + + /* Finally, we'll check if we should update the descriptors. Missing + * introduction points will be picked in this function which is useful for + * newly built descriptors. */ + update_all_descriptors(now); +} + +/* For the given service, launch any intro point circuits that could be + * needed. This considers every descriptor of the service. */ +static void +launch_intro_point_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* For both descriptors, try to launch any missing introduction point + * circuits using the current map. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Keep a ref on if we need a direct connection. We use this often. */ + unsigned int direct_conn = service->config.is_single_onion; + + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + extend_info_t *ei; + + /* Skip the intro point that already has an existing circuit + * (established or not). */ + if (hs_circ_service_get_intro_circ(ip)) { + continue; + } + + ei = get_extend_info_from_intro_point(ip, direct_conn); + if (ei == NULL) { + if (!direct_conn) { + /* In case of a multi-hop connection, it should never happen that we + * can't get the extend info from the node. Avoid connection and + * remove intro point from descriptor in order to recover from this + * potential bug. */ + tor_assert_nonfatal(ei); + } + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + continue; + } + + /* Launch a circuit to the intro point. */ + ip->circuit_retries++; + if (hs_circ_launch_intro_point(service, ip, ei) < 0) { + log_warn(LD_REND, "Unable to launch intro circuit to node %s " + "for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + /* Intro point will be retried if possible after this. */ + } + extend_info_free(ei); + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* Don't try to build more than this many circuits before giving up for a + * while. Dynamically calculated based on the configured number of intro + * points for the given service and how many descriptor exists. The default + * use case of 3 introduction points and two descriptors will allow 28 + * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */ +static unsigned int +get_max_intro_circ_per_period(const hs_service_t *service) +{ + unsigned int count = 0; + unsigned int multiplier = 0; + unsigned int num_wanted_ip; + + tor_assert(service); + tor_assert(service->config.num_intro_points <= + HS_CONFIG_V3_MAX_INTRO_POINTS); + +/* For a testing network, allow to do it for the maximum amount so circuit + * creation and rotation and so on can actually be tested without limit. */ +#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING; + } + + num_wanted_ip = service->config.num_intro_points; + + /* The calculation is as follow. We have a number of intro points that we + * want configured as a torrc option (num_intro_points). We then add an + * extra value so we can launch multiple circuits at once and pick the + * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll + * pick 5 intros and launch 5 circuits. */ + count += (num_wanted_ip + get_intro_point_num_extra()); + + /* Then we add the number of retries that is possible to do for each intro + * point. If we want 3 intros, we'll allow 3 times the number of possible + * retry. */ + count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES); + + /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we + * have none. */ + multiplier += (service->desc_current) ? 1 : 0; + multiplier += (service->desc_next) ? 1 : 0; + + return (count * multiplier); +} + +/* For the given service, return 1 if the service is allowed to launch more + * introduction circuits else 0 if the maximum has been reached for the retry + * period of INTRO_CIRC_RETRY_PERIOD. */ +STATIC int +can_service_launch_intro_circuit(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* Consider the intro circuit retry period of the service. */ + if (now > (service->state.intro_circ_retry_started_time + + INTRO_CIRC_RETRY_PERIOD)) { + service->state.intro_circ_retry_started_time = now; + service->state.num_intro_circ_launched = 0; + goto allow; + } + /* Check if we can still launch more circuits in this period. */ + if (service->state.num_intro_circ_launched <= + get_max_intro_circ_per_period(service)) { + goto allow; + } + + /* Rate limit log that we've reached our circuit creation limit. */ + { + char *msg; + time_t elapsed_time = now - service->state.intro_circ_retry_started_time; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, now))) { + log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit " + "of %u per %d seconds. It launched %u circuits in " + "the last %ld seconds. Will retry in %ld seconds.", + safe_str_client(service->onion_address), + get_max_intro_circ_per_period(service), + INTRO_CIRC_RETRY_PERIOD, + service->state.num_intro_circ_launched, elapsed_time, + INTRO_CIRC_RETRY_PERIOD - elapsed_time); + tor_free(msg); + } + } + + /* Not allow. */ + return 0; + allow: + return 1; +} + +/* Scheduled event run from the main loop. Make sure we have all the circuits + * we need for each service. */ +static void +run_build_circuit_event(time_t now) +{ + /* Make sure we can actually have enough information or able to build + * internal circuits as required by services. */ + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN || + !have_completed_a_circuit()) { + return; + } + + /* Run v2 check. */ + if (rend_num_services() > 0) { + rend_consider_services_intro_points(now); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* For introduction circuit, we need to make sure we don't stress too much + * circuit creation so make sure this service is respecting that limit. */ + if (can_service_launch_intro_circuit(service, now)) { + /* Launch intro point circuits if needed. */ + launch_intro_point_circuits(service); + /* Once the circuits have opened, we'll make sure to update the + * descriptor intro point list and cleanup any extraneous. */ + } + } FOR_EACH_SERVICE_END; +} + +/* Encode and sign the service descriptor desc and upload it to the given + * hidden service directory. This does nothing if PublishHidServDescriptors + * is false. */ +static void +upload_descriptor_to_hsdir(const hs_service_t *service, + hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char version_str[4] = {0}, *encoded_desc = NULL; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(service); + tor_assert(desc); + tor_assert(hsdir); + + memset(&ident, 0, sizeof(ident)); + + /* Let's avoid doing that if tor is configured to not publish. */ + if (!get_options()->PublishHidServDescriptors) { + log_info(LD_REND, "Service %s not publishing descriptor. " + "PublishHidServDescriptors is set to 1.", + safe_str_client(service->onion_address)); + goto end; + } + + /* First of all, we'll encode the descriptor. This should NEVER fail but + * just in case, let's make sure we have an actual usable descriptor. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto end; + } + + /* Setup the connection identifier. */ + ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk); + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs/<version>/publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", + service->config.version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir->rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_upload_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); + + /* Logging so we know where it was sent. */ + { + int is_next_desc = (service->desc_next == desc); + const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next : + hsdir->hsdir_index->current; + log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 + " initiated upload request to %s with index %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", + desc->desc->plaintext_data.revision_counter, + safe_str_client(node_describe(hsdir)), + safe_str_client(hex_str((const char *) index, 32))); + } + + /* XXX: Inform control port of the upload event (#20699). */ + end: + tor_free(encoded_desc); + return; +} + +/** Return a newly-allocated string for our state file which contains revision + * counter information for <b>desc</b>. The format is: + * + * HidServRevCounter <blinded_pubkey> <rev_counter> + */ +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) +{ + char *state_str = NULL; + char blinded_pubkey_b64[ED25519_BASE64_LEN+1]; + uint64_t rev_counter = desc->desc->plaintext_data.revision_counter; + const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey; + + /* Turn the blinded key into b64 so that we save it on state */ + tor_assert(blinded_pubkey); + if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) { + goto done; + } + + /* Format is: <blinded key> <rev counter> */ + tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter); + + log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!", + rev_counter, blinded_pubkey_b64); + + done: + return state_str; +} + +/** Update HS descriptor revision counters in our state by removing the old + * ones and writing down the ones that are currently active. */ +static void +update_revision_counters_in_state(void) +{ + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + or_state_t *state = get_or_state(); + + /* Prepare our state structure with the rev counters */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We don't want to save zero counters */ + if (desc->desc->plaintext_data.revision_counter == 0) { + continue; + } + + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("HidServRevCounter"); + (*nextline)->value = encode_desc_rev_counter_for_state(desc); + nextline = &(*nextline)->next; + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* Remove the old rev counters, and replace them with the new ones */ + config_free_lines(state->HidServRevCounter); + state->HidServRevCounter = lines; + + /* Set the state as dirty since we just edited it */ + if (!get_options()->AvoidDiskWrites) { + or_state_mark_dirty(state, 0); + } +} + +/** Scan the string <b>state_line</b> for the revision counter of the service + * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the + * line is relevant to this service, and return the cached revision + * counter. Else set <b>service_found_out</b> to False. */ +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out) +{ + smartlist_t *items = NULL; + int ok; + ed25519_public_key_t pubkey_in_state; + uint64_t rev_counter = 0; + + tor_assert(service_found_out); + tor_assert(state_line); + tor_assert(blinded_pubkey); + + /* Assume that the line is not for this service */ + *service_found_out = 0; + + /* Start parsing the state line */ + items = smartlist_new(); + smartlist_split_string(items, state_line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 2) { + log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring."); + goto done; + } + + char *b64_key_str = smartlist_get(items, 0); + char *saved_rev_counter_str = smartlist_get(items, 1); + + /* Parse blinded key to check if it's for this hidden service */ + if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) { + log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring."); + goto done; + } + /* State line not for this hidden service */ + if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) { + goto done; + } + + rev_counter = tor_parse_uint64(saved_rev_counter_str, + 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring."); + goto done; + } + + /* Since we got this far, the line was for this service */ + *service_found_out = 1; + + log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64, + b64_key_str, rev_counter); + + done: + if (items) { + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + } + + return rev_counter; +} + +/** Dig into our state file and find the current revision counter for the + * service with blinded key <b>blinded_pubkey</b>. If no revision counter is + * found, return 0. */ +static uint64_t +get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey) +{ + or_state_t *state = get_or_state(); + config_line_t *line; + + /* Set default value for rev counters (if not found) to 0 */ + uint64_t final_rev_counter = 0; + + for (line = state->HidServRevCounter ; line ; line = line->next) { + int service_found = 0; + uint64_t rev_counter = 0; + + tor_assert(!strcmp(line->key, "HidServRevCounter")); + + /* Scan all the HidServRevCounter lines till we find the line for this + service: */ + rev_counter = check_state_line_for_service_rev_counter(line->value, + blinded_pubkey, + &service_found); + if (service_found) { + final_rev_counter = rev_counter; + goto done; + } + } + + done: + return final_rev_counter; +} + +/** Update the value of the revision counter for <b>hs_desc</b> and save it on + our state file. */ +static void +increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + /* Increment the revision counter of <b>hs_desc</b> so the next update (which + * will trigger an upload) will have the right value. We do this at this + * stage to only do it once because a descriptor can have many updates before + * being uploaded. By doing it at upload, we are sure to only increment by 1 + * and thus avoid leaking how many operations we made on the descriptor from + * the previous one before uploading. */ + rev_counter++; + hs_desc->plaintext_data.revision_counter = rev_counter; + + update_revision_counters_in_state(); +} + +/** Set the revision counter in <b>hs_desc</b>, using the state file to find + * the current counter value if it exists. */ +static void +set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + hs_desc->plaintext_data.revision_counter = rev_counter; +} + +/* Encode and sign the service descriptor desc and upload it to the + * responsible hidden service directories. If for_next_period is true, the set + * of directories are selected using the next hsdir_index. This does nothing + * if PublishHidServDescriptors is false. */ +static void +upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc, int for_next_period) +{ + smartlist_t *responsible_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + /* Get our list of responsible HSDir. */ + responsible_dirs = smartlist_new(); + /* The parameter 0 means that we aren't a client so tell the function to use + * the spread store consensus paremeter. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + for_next_period, 0, responsible_dirs); + + /* For each responsible HSDir we have, initiate an upload command. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, + hsdir_rs) { + const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + /* Getting responsible hsdir implies that the node_t object exists for the + * routerstatus_t found in the consensus else we have a problem. */ + tor_assert(hsdir_node); + /* Do not upload to an HSDir we don't have a descriptor for. */ + if (!node_has_descriptor(hsdir_node)) { + log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading " + "descriptor. We'll try later once we have it.", + safe_str_client(node_describe(hsdir_node))); + /* Once we get new directory information, this HSDir will be retried if + * we ever get the descriptor. */ + smartlist_add(desc->hsdir_missing_info, + tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN)); + continue; + } + + /* Upload this descriptor to the chosen directory. */ + upload_descriptor_to_hsdir(service, desc, hsdir_node); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Set the next upload time for this descriptor. Even if we are configured + * to not upload, we still want to follow the right cycle of life for this + * descriptor. */ + desc->next_upload_time = + (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN, + HS_SERVICE_NEXT_UPLOAD_TIME_MAX)); + { + char fmt_next_time[ISO_TIME_LEN+1]; + format_local_iso_time(fmt_next_time, desc->next_upload_time); + log_debug(LD_REND, "Service %s set to upload a descriptor at %s", + safe_str_client(service->onion_address), fmt_next_time); + } + + /* Update the revision counter of this descriptor */ + increment_descriptor_revision_counter(desc->desc); + + smartlist_free(responsible_dirs); + return; +} + +/* Return 1 if the given descriptor from the given service can be uploaded + * else return 0 if it can not. */ +static int +should_service_upload_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + + /* If this descriptors has missing intro points that is that it couldn't get + * them all when it was time to pick them, it means that we should upload + * instead of waiting an arbitrary amount of time breaking the service. + * Else, if we have no missing intro points, we use the value taken from the + * service configuration. */ + if (desc->missing_intro_points) { + num_intro_points = digest256map_size(desc->intro_points.map); + } else { + num_intro_points = service->config.num_intro_points; + } + + /* This means we tried to pick intro points but couldn't get any so do not + * upload descriptor in this case. We need at least one for the service to + * be reachable. */ + if (desc->missing_intro_points && num_intro_points == 0) { + goto cannot; + } + + /* Check if all our introduction circuit have been established for all the + * intro points we have selected. */ + if (count_desc_circuit_established(desc) != num_intro_points) { + goto cannot; + } + + /* Is it the right time to upload? */ + if (desc->next_upload_time > now) { + goto cannot; + } + + /* Can upload! */ + return 1; + cannot: + return 0; +} + +/* Scheduled event run from the main loop. Try to upload the descriptor for + * each service. */ +STATIC void +run_upload_descriptor_event(time_t now) +{ + /* v2 services use the same function for descriptor creation and upload so + * we do everything here because the intro circuits were checked before. */ + if (rend_num_services() > 0) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + int for_next_period = 0; + + /* Can this descriptor be uploaed? */ + if (!should_service_upload_descriptor(service, desc, now)) { + continue; + } + + log_info(LD_REND, "Initiating upload for hidden service %s descriptor " + "for service %s with %u/%u introduction points%s.", + (desc == service->desc_current) ? "current" : "next", + safe_str_client(service->onion_address), + digest256map_size(desc->intro_points.map), + service->config.num_intro_points, + (desc->missing_intro_points) ? " (couldn't pick more)" : ""); + + /* At this point, we have to upload the descriptor so start by building + * the intro points descriptor section which we are now sure to be + * accurate because all circuits have been established. */ + build_desc_intro_points(service, desc, now); + + /* If the service is in the overlap period and this descriptor is the + * next one, it has to be uploaded for the next time period meaning + * we'll use the next node_t hsdir_index to pick the HSDirs. */ + if (desc == service->desc_next) { + for_next_period = 1; + } + upload_descriptor_to_all(service, desc, for_next_period); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Called when the introduction point circuit is done building and ready to be + * used. */ +static void +service_intro_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + /* Let's do some basic sanity checking of the circ state */ + if (BUG(!circ->cpath)) { + return; + } + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + return; + } + if (BUG(!circ->hs_ident)) { + return; + } + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + log_warn(LD_REND, "Unknown introduction point auth key on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* We can't have an IP object without a descriptor. */ + tor_assert(desc); + + if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { + /* Getting here means that the circuit has been re-purposed because we + * have enough intro circuit opened. Remove the IP from the service. */ + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + goto done; + + err: + /* Close circuit, we can't use it. */ + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* Called when a rendezvous circuit is done building and ready to be used. */ +static void +service_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(circ->cpath); + /* Getting here means this is a v3 rendezvous circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Declare the circuit dirty to avoid reuse, and for path-bias */ + if (!TO_CIRCUIT(circ)->timestamp_dirty) + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the rendezvous " + "circuit %u with cookie %s. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN)); + goto err; + } + + /* If the cell can't be sent, the circuit will be closed within this + * function. */ + hs_circ_service_rp_has_opened(service, circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just + * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given + * introduction circuit. Return 0 on success else a negative value. */ +static int +service_handle_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* We need the service and intro point for this cell. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, NULL); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_REND, "Introduction circuit established without an intro " + "point object on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. On success, the ip object and circuit purpose is updated to + * reflect the fact that the introduction circuit is established. */ + if (hs_circ_handle_intro_established(service, ip, circ, payload, + payload_len) < 0) { + goto err; + } + + /* Flag that we have an established circuit for this intro point. This value + * is what indicates the upload scheduled event if we are ready to build the + * intro point into the descriptor and upload. */ + ip->circuit_established = 1; + + log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " + "on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + return 0; + + err: + return -1; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the cell and return 0 on success else a negative value. */ +static int +service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); + + /* We'll need every object associated with this circuit. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_BUG, "Unknown service identity key %s when handling " + "an INTRODUCE2 cell on circuit %u", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_BUG, "Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* If we have an IP object, we MUST have a descriptor object. */ + tor_assert(desc); + + /* The following will parse, decode and launch the rendezvous point circuit. + * Both current and legacy cells are handled. */ + if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + payload, payload_len) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + +/* For a given service and a descriptor of that service, consider retrying to + * upload the descriptor to any directories from which we had missing + * information when originally tried to be uploaded. This is called when our + * directory information has changed. */ +static void +consider_hsdir_upload_retry(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + smartlist_t *responsible_dirs = NULL; + smartlist_t *still_missing_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + responsible_dirs = smartlist_new(); + still_missing_dirs = smartlist_new(); + + /* We first need to get responsible directories from the latest consensus so + * we can then make sure that the node that we were missing information for + * is still responsible for this descriptor. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) { + const node_t *node; + const char *id = rs->identity_digest; + if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) { + continue; + } + /* We do need a node_t object and descriptor to perform an upload. If + * found, we remove the id from the missing dir list else we add it to the + * still missing dir list to keep track of id that are still missing. */ + node = node_get_by_id(id); + if (node && node_has_descriptor(node)) { + upload_descriptor_to_hsdir(service, desc, node); + smartlist_remove(desc->hsdir_missing_info, id); + } else { + smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN)); + } + } SMARTLIST_FOREACH_END(rs); + + /* Switch the still missing dir list with the current missing dir list in + * the descriptor. It is possible that the list ends up empty which is what + * we want if we have no more missing dir. */ + SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); + smartlist_free(desc->hsdir_missing_info); + desc->hsdir_missing_info = still_missing_dirs; + + /* No ownership of the routerstatus_t object in this list. */ + smartlist_free(responsible_dirs); +} + +/* Add to list every filename used by service. This is used by the sandbox + * subsystem. */ +static void +service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) +{ + const char *s_dir; + char fname[128] = {0}; + + tor_assert(service); + tor_assert(list); + + /* Ease our life. */ + s_dir = service->config.directory_path; + /* The hostname file. */ + smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname)); + /* The key files splitted in two. */ + tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); + tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return the number of service we have configured and usable. */ +unsigned int +hs_service_get_num_services(void) +{ + if (hs_service_map == NULL) { + return 0; + } + return HT_SIZE(hs_service_map); +} + +/* Called once an introduction circuit is closed. If the circuit doesn't have + * a v3 identifier, it is ignored. */ +void +hs_service_intro_circ_has_closed(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + if (circ->hs_ident == NULL) { + /* This is not a v3 circuit, ignore. */ + goto end; + } + + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on intro circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + goto end; + } + if (ip == NULL) { + /* The introduction point object has already been removed probably by our + * cleanup process so ignore. */ + goto end; + } + /* Can't have an intro point object without a descriptor. */ + tor_assert(desc); + + /* Circuit disappeared so make sure the intro point is updated. By + * keeping the object in the descriptor, we'll be able to retry. */ + ip->circuit_established = 0; + + /* We've retried too many times, remember it as a failed intro point so we + * don't pick it up again. It will be retried in INTRO_CIRC_RETRY_PERIOD + * seconds. */ + if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, approx_time()); + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + end: + return; +} + +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + +/* Add to file_list every filename used by a configured hidden service, and to + * dir_list every directory path used by a configured hidden service. This is + * used by the sandbox subsystem to whitelist those. */ +void +hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list) +{ + tor_assert(file_list); + tor_assert(dir_list); + + /* Add files and dirs for legacy services. */ + rend_services_add_filenames_to_lists(file_list, dir_list); + + /* Add files and dirs for v3+. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* Skip ephemeral service, they don't touch the disk. */ + if (service->config.is_ephemeral) { + continue; + } + service_add_fnames_to_list(service, file_list); + smartlist_add_strdup(dir_list, service->config.directory_path); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Called when our internal view of the directory has changed. We might have + * new descriptors for hidden service directories that we didn't have before + * so try them if it's the case. */ +void +hs_service_dir_info_changed(void) +{ + /* For each service we have, check every descriptor and consider retrying to + * upload it to directories that we might have had missing information + * previously that is missing a router descriptor. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* This cleans up the descriptor missing hsdir information list if a + * successful upload is made or if any of the directory aren't + * responsible anymore for the service descriptor. */ + consider_hsdir_upload_retry(service, desc); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and + * launch a circuit to the rendezvous point. */ +int +hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Do some initial validation and logging before we parse the cell */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto done; + } + + if (circ->hs_ident) { + ret = service_handle_introduce2(circ, payload, payload_len); + } else { + ret = rend_service_receive_introduction(circ, payload, payload_len); + } + + done: + return ret; +} + +/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an + * established introduction point. Return 0 on success else a negative value + * and the circuit is closed. */ +int +hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto err; + } + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + if (circ->hs_ident) { + ret = service_handle_intro_established(circ, payload, payload_len); + } else { + ret = rend_service_intro_established(circ, payload, payload_len); + } + + if (ret < 0) { + goto err; + } + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* Called when any kind of hidden service circuit is done building thus + * opened. This is the entry point from the circuit subsystem. */ +void +hs_service_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + if (circ->hs_ident) { + service_intro_circ_has_opened(circ); + } else { + rend_service_intro_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + if (circ->hs_ident) { + service_rendezvous_circ_has_opened(circ); + } else { + rend_service_rendezvous_has_opened(circ); + } + break; + default: + tor_assert(0); + } +} + /* 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_num_services() != 0) { if (rend_service_load_all_keys(NULL) < 0) { goto err; } @@ -575,6 +2969,9 @@ hs_service_new(const or_options_t *options) set_service_default_config(&service->config, options); /* Set the default service version. */ service->config.version = HS_SERVICE_DEFAULT_VERSION; + /* Allocate the CLIENT_PK replay cache in service state. */ + service->state.replay_cache_rend_cookie = + replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); return service; } @@ -588,37 +2985,48 @@ hs_service_free(hs_service_t *service) 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 descriptors. Go over both descriptor with this loop. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + service_descriptor_free(desc); + } FOR_EACH_DESCRIPTOR_END; /* Free service configuration. */ service_clear_config(&service->config); + /* Free replay cache from state. */ + if (service->state.replay_cache_rend_cookie) { + replaycache_free(service->state.replay_cache_rend_cookie); + } + /* Wipe service keys. */ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); tor_free(service); } +/* Periodic callback. Entry point from the main loop to the HS service + * subsystem. This is call every second. This is skipped if tor can't build a + * circuit or the network is disabled. */ +void +hs_service_run_scheduled_events(time_t now) +{ + /* First thing we'll do here is to make sure our services are in a + * quiescent state for the scheduled events. */ + run_housekeeping_event(now); + + /* Order matters here. We first make sure the descriptor object for each + * service contains the latest data. Once done, we check if we need to open + * new introduction circuit. Finally, we try to upload the descriptor for + * each service. */ + + /* Make sure descriptors are up to date. */ + run_build_descriptor_event(now); + /* Make sure services have enough circuits. */ + run_build_circuit_event(now); + /* Upload the descriptors if needed/possible. */ + run_upload_descriptor_event(now); +} + /* Initialize the service HS subsystem. */ void hs_service_init(void) @@ -644,159 +3052,6 @@ hs_service_free_all(void) 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. */ - -/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in - * <b>buf_out</b> which has size <b>buf_out_len</b>. Return the number of - * bytes written, or a negative integer if there was an error. */ -ssize_t -get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, - const trn_cell_establish_intro_t *cell) -{ - ssize_t bytes_used = 0; - - if (buf_out_len < RELAY_PAYLOAD_SIZE) { - return -1; - } - - bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len, - cell); - return bytes_used; -} - -/* Set the cell extensions of <b>cell</b>. */ -static void -set_trn_cell_extensions(trn_cell_establish_intro_t *cell) -{ - trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new(); - - /* For now, we don't use extensions at all. */ - trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ - trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions); -} - -/** Given the circuit handshake info in <b>circuit_key_material</b>, create and - * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The - * returned cell is allocated on the heap and it's the responsibility of the - * caller to free it. */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len) -{ - trn_cell_establish_intro_t *cell = NULL; - ssize_t encoded_len; - - log_warn(LD_GENERAL, - "Generating ESTABLISH_INTRO cell (key_material_len: %u)", - (unsigned) circuit_key_material_len); - - /* Generate short-term keypair for use in ESTABLISH_INTRO */ - ed25519_keypair_t key_struct; - if (ed25519_keypair_generate(&key_struct, 0) < 0) { - goto err; - } - - cell = trn_cell_establish_intro_new(); - - /* Set AUTH_KEY_TYPE: 2 means ed25519 */ - trn_cell_establish_intro_set_auth_key_type(cell, - HS_INTRO_AUTH_KEY_TYPE_ED25519); - - /* Set AUTH_KEY_LEN field */ - /* Must also set byte-length of AUTH_KEY to match */ - int auth_key_len = ED25519_PUBKEY_LEN; - trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); - trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); - - /* Set AUTH_KEY field */ - uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); - memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len); - - /* No cell extensions needed */ - set_trn_cell_extensions(cell); - - /* Set signature size. - We need to do this up here, because _encode() needs it and we need to call - _encode() to calculate the MAC and signature. - */ - int sig_len = ED25519_SIG_LEN; - trn_cell_establish_intro_set_sig_len(cell, sig_len); - trn_cell_establish_intro_setlen_sig(cell, sig_len); - - /* XXX How to make this process easier and nicer? */ - - /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */ - { - /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive - the MAC from it. */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - uint8_t mac[TRUNNEL_SHA3_256_LEN]; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell."); - goto err; - } - - /* sanity check */ - tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN); - - /* Calculate MAC of all fields before HANDSHAKE_AUTH */ - crypto_mac_sha3_256(mac, sizeof(mac), - circuit_key_material, circuit_key_material_len, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN)); - /* Write the MAC to the cell */ - uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(cell); - memcpy(handshake_ptr, mac, sizeof(mac)); - } - - /* Calculate the cell signature */ - { - /* To calculate the sig we follow the same procedure as above. We first - dump the cell up to the sig, and then calculate the sig */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - ed25519_signature_t sig; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2)."); - goto err; - } - - tor_assert(encoded_len > ED25519_SIG_LEN); - - if (ed25519_sign_prefixed(&sig, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + sizeof(cell->sig_len)), - ESTABLISH_INTRO_SIG_PREFIX, - &key_struct)) { - log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell."); - goto err; - } - - /* And write the signature to the cell */ - uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell); - memcpy(sig_ptr, sig.sig, sig_len); - } - - /* We are done! Return the cell! */ - return cell; - - err: - trn_cell_establish_intro_free(cell); - return NULL; -} - #ifdef TOR_UNIT_TESTS /* Return the global service map size. Only used by unit test. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 54b9e69724..8d613d23ed 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -15,6 +15,7 @@ #include "hs_common.h" #include "hs_descriptor.h" +#include "hs_ident.h" #include "hs_intropoint.h" /* Trunnel */ @@ -25,17 +26,30 @@ * present. */ #define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO +/* As described in the specification, service publishes their next descriptor + * at a random time between those two values (in seconds). */ +#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60) +#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60) + /* Service side introduction point. */ typedef struct hs_service_intro_point_t { /* Top level intropoint "shared" data between client/service. */ hs_intropoint_t base; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* 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; + /* Encryption keypair for the "ntor" type. */ + curve25519_keypair_t enc_key_kp; + + /* Legacy key if that intro point doesn't support v3. This should be used if + * the base object legacy flag is set. */ + crypto_pk_t *legacy_key; /* Amount of INTRODUCE2 cell accepted from this intro point. */ uint64_t introduce2_count; @@ -74,6 +88,12 @@ typedef struct hs_service_intropoints_t { /* Contains the current hs_service_intro_point_t objects indexed by * authentication public key. */ digest256map_t *map; + + /* Contains node's identity key digest that were introduction point for this + * descriptor but were retried to many times. We keep those so we avoid + * re-picking them over and over for a circuit retry period. + * XXX: Once we have #22173, change this to only use ed25519 identity. */ + digestmap_t *failed_id; } hs_service_intropoints_t; /* Representation of a service descriptor. */ @@ -95,6 +115,20 @@ typedef struct hs_service_descriptor_t { * hs_service_intropoints_t object indexed by authentication key (the RSA * key if the node is legacy). */ hs_service_intropoints_t intro_points; + + /* The time period number this descriptor has been created for. */ + uint64_t time_period_num; + + /* True iff we have missing intro points for this descriptor because we + * couldn't pick any nodes. */ + unsigned int missing_intro_points : 1; + + /* List of identity digests for hidden service directories to which we + * couldn't upload this descriptor because we didn't have its router + * descriptor at the time. If this list is non-empty, only the relays in this + * list are re-tried to upload this descriptor when our directory information + * have been updated. */ + smartlist_t *hsdir_missing_info; } hs_service_descriptor_t; /* Service key material. */ @@ -123,10 +157,6 @@ typedef struct hs_service_config_t { * 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. */ @@ -170,6 +200,13 @@ typedef struct hs_service_state_t { /* Indicate that the service has entered the overlap period. We use this * flag to check for descriptor rotation. */ unsigned int in_overlap_period : 1; + + /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point + * through two or more different introduction points; when they do, this + * keeps us from launching multiple simultaneous attempts to connect to the + * same rend point. */ + replaycache_t *replay_cache_rend_cookie; } hs_service_state_t; /* Representation of a service running on this tor instance. */ @@ -216,19 +253,25 @@ void hs_service_free_all(void); hs_service_t *hs_service_new(const or_options_t *options); void hs_service_free(hs_service_t *service); +unsigned int hs_service_get_num_services(void); 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. */ - -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len); -ssize_t -get_establish_intro_payload(uint8_t *buf, size_t buf_len, - const trn_cell_establish_intro_t *cell); +void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); + +void hs_service_dir_info_changed(void); +void hs_service_run_scheduled_events(time_t now); +void hs_service_circuit_has_opened(origin_circuit_t *circ); +int hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_service_receive_introduce2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_service_intro_circ_has_closed(origin_circuit_t *circ); #ifdef HS_SERVICE_PRIVATE @@ -245,6 +288,55 @@ 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); +/* Service introduction point functions. */ +STATIC hs_service_intro_point_t *service_intro_point_new( + const extend_info_t *ei, + unsigned int is_legacy); +STATIC void service_intro_point_free(hs_service_intro_point_t *ip); +STATIC void service_intro_point_add(digest256map_t *map, + hs_service_intro_point_t *ip); +STATIC void service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip); +STATIC hs_service_intro_point_t *service_intro_point_find( + const hs_service_t *service, + const ed25519_public_key_t *auth_key); +STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( + const hs_service_t *service, + const hs_ident_circuit_t *ident); +/* Service descriptor functions. */ +STATIC hs_service_descriptor_t *service_descriptor_new(void); +STATIC hs_service_descriptor_t *service_desc_find_by_intro( + const hs_service_t *service, + const hs_service_intro_point_t *ip); +/* Helper functions. */ +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, + hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc); +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip); +STATIC int can_service_launch_intro_circuit(hs_service_t *service, + time_t now); +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now); +STATIC void run_housekeeping_event(time_t now); +STATIC void rotate_all_descriptors(time_t now); +STATIC void build_all_descriptors(time_t now); +STATIC void update_all_descriptors(time_t now); +STATIC void run_upload_descriptor_event(time_t now); + +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc); + +STATIC void service_descriptor_free(hs_service_descriptor_t *desc); + +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out); + +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_); #endif /* TOR_UNIT_TESTS */ diff --git a/src/or/include.am b/src/or/include.am index 15b86ef50b..8db5be095a 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -54,6 +54,7 @@ LIBTOR_A_SOURCES = \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/hs_cache.c \ + src/or/hs_cell.c \ src/or/hs_circuit.c \ src/or/hs_circuitmap.c \ src/or/hs_client.c \ @@ -184,11 +185,12 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_cell.h \ + src/or/hs_config.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_ident.h \ src/or/hs_intropoint.h \ diff --git a/src/or/main.c b/src/or/main.c index 0267f4daec..42d984acfb 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1194,6 +1194,7 @@ CALLBACK(heartbeat); CALLBACK(clean_consdiffmgr); CALLBACK(reset_padding_counts); CALLBACK(check_canonical_channels); +CALLBACK(hs_service); #undef CALLBACK @@ -1229,6 +1230,7 @@ static periodic_event_item_t periodic_events[] = { CALLBACK(clean_consdiffmgr), CALLBACK(reset_padding_counts), CALLBACK(check_canonical_channels), + CALLBACK(hs_service), END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1461,12 +1463,6 @@ run_scheduled_events(time_t now) /* 6. And remove any marked circuits... */ circuit_close_all_marked(); - /* 7. And upload service descriptors if necessary. */ - if (have_completed_a_circuit() && !net_is_disabled()) { - rend_consider_services_upload(now); - rend_consider_descriptor_republication(); - } - /* 8. and blow away any connections that need to die. have to do this now, * because if we marked a conn for close and left its socket -1, then * we'll pass it to poll/select and bad things will happen. @@ -2101,6 +2097,28 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options) return CDM_CLEAN_CALLBACK_INTERVAL; } +/* + * Periodic callback: Run scheduled events for HS service. This is called + * every second. + */ +static int +hs_service_callback(time_t now, const or_options_t *options) +{ + (void) options; + + /* We need to at least be able to build circuits and that we actually have + * a working network. */ + if (!have_completed_a_circuit() || net_is_disabled()) { + goto end; + } + + hs_service_run_scheduled_events(now); + + end: + /* Every 1 second. */ + return 1; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; /** Number of libevent errors in the last second: we die if we get too many. */ @@ -3554,7 +3572,7 @@ sandbox_init_filter(void) { smartlist_t *files = smartlist_new(); smartlist_t *dirs = smartlist_new(); - rend_services_add_filenames_to_lists(files, dirs); + hs_service_lists_fnames_for_sandbox(files, dirs); SMARTLIST_FOREACH(files, char *, file_name, { char *tmp_name = NULL; tor_asprintf(&tmp_name, "%s.tmp", file_name); @@ -3563,6 +3581,7 @@ sandbox_init_filter(void) /* steals references */ sandbox_cfg_allow_open_filename(&cfg, file_name); sandbox_cfg_allow_open_filename(&cfg, tmp_name); + tor_free(file_name); }); SMARTLIST_FOREACH(dirs, char *, dir, { /* steals reference */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index aff36b4c0b..69bff55cff 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1393,14 +1393,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) MOCK_IMPL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)) { - if (networkstatus_get_latest_consensus() && - networkstatus_get_latest_consensus()->valid_after <= now && - now <= networkstatus_get_latest_consensus()->valid_until) - return networkstatus_get_latest_consensus(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && networkstatus_is_live(ns, now)) + return ns; else return NULL; } +/** Given a consensus in <b>ns</b>, return true iff currently live and + * unexpired. */ +int +networkstatus_is_live(const networkstatus_t *ns, time_t now) +{ + return (ns->valid_after <= now && now <= ns->valid_until); +} + /** Determine if <b>consensus</b> is valid or expired recently enough that * we can still use it. * diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index e774c4d266..f9320747d2 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -81,6 +81,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); +int networkstatus_is_live(const networkstatus_t *ns, time_t now); int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); int networkstatus_valid_until_is_reasonably_live(time_t valid_until, diff --git a/src/or/nodelist.c b/src/or/nodelist.c index dafeb9f12d..0fcaea626d 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -45,6 +45,7 @@ #include "dirserv.h" #include "entrynodes.h" #include "geoip.h" +#include "hs_common.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -54,6 +55,7 @@ #include "rendservice.h" #include "router.h" #include "routerlist.h" +#include "routerparse.h" #include "routerset.h" #include "torcert.h" @@ -164,12 +166,78 @@ node_get_or_create(const char *identity_digest) smartlist_add(the_nodelist->nodes, node); node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); node->country = -1; return node; } +/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index + * for the node, both current and next if possible. This can only fails if the + * node_t ed25519 identity key can't be found which would be a bug. */ +static void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns) +{ + time_t now = approx_time(); + const ed25519_public_key_t *node_identity_pk; + uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL; + uint64_t next_time_period_num, current_time_period_num; + + tor_assert(node); + tor_assert(ns); + + if (!networkstatus_is_live(ns, now)) { + log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus."); + goto done; + } + + node_identity_pk = node_get_ed25519_id(node); + if (node_identity_pk == NULL) { + log_debug(LD_GENERAL, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); + goto done; + } + + /* Get the current and next time period number, we might use them both. */ + current_time_period_num = hs_get_time_period_num(now); + next_time_period_num = hs_get_next_time_period_num(now); + + if (hs_overlap_mode_is_active(ns, now)) { + /* We are in overlap mode, this means that our consensus has just cycled + * from current SRV to previous SRV so for the _next_ upcoming time + * period, we have to use the current SRV and use the previous SRV for the + * current time period. If the current or previous SRV can't be found, the + * disaster one is returned. */ + next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns); + /* The following can be confusing so again, in overlap mode, we use our + * previous SRV for our _current_ hsdir index. */ + current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns); + } else { + /* If NOT in overlap mode, we only need to compute the current hsdir index + * for the ongoing time period and thus the current SRV. If it can't be + * found, the disaster one is returned. */ + current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns); + } + + /* Build the current hsdir index. */ + hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv, + current_time_period_num, node->hsdir_index->current); + if (next_hsdir_index_srv) { + /* Build the next hsdir index if we have a next SRV that we can use. */ + hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv, + next_time_period_num, node->hsdir_index->next); + } else { + memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next)); + } + + done: + tor_free(current_hsdir_index_srv); + tor_free(next_hsdir_index_srv); + return; +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -216,6 +284,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) dirserv_set_node_flags_from_authoritative_status(node, status); } + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (node->rs && node->rs->supports_v3_hsdir) { + node_set_hsdir_index(node, + networkstatus_get_latest_consensus()); + } + return node; } @@ -246,6 +322,12 @@ nodelist_add_microdesc(microdesc_t *md) node->md->held_by_nodes--; node->md = md; md->held_by_nodes++; + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } } return node; } @@ -283,6 +365,9 @@ nodelist_set_consensus(networkstatus_t *ns) } } + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } node_set_country(node); /* If we're not an authdir, believe others. */ @@ -410,6 +495,7 @@ node_free(node_t *node) if (node->md) node->md->held_by_nodes--; tor_assert(node->nodelist_idx == -1); + tor_free(node->hsdir_index); tor_free(node); } @@ -719,6 +805,15 @@ node_supports_v3_hsdir(const node_t *node) if (node->ri->protocol_list == NULL) { return 0; } + /* Bug #22447 forces us to filter on tor version: + * If platform is a Tor version, and older than 0.3.0.8, return False. + * Else, obey the protocol list. */ + if (node->ri->platform) { + if (!strcmpstart(node->ri->platform, "Tor ") && + !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) { + return 0; + } + } return protocol_list_supports_protocol(node->ri->protocol_list, PRT_HSDIR, PROTOVER_HSDIR_V3); } diff --git a/src/or/or.h b/src/or/or.h index 32b4cd1b7e..ff11c72790 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -421,15 +421,20 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define DIR_PURPOSE_MAX_ 19 +/** A connection to a hidden service directory: upload a v3 descriptor. */ +#define DIR_PURPOSE_UPLOAD_HSDESC 20 +/** A connection to a hidden service directory: fetch a v3 descriptor. */ +#define DIR_PURPOSE_FETCH_HSDESC 21 +#define DIR_PURPOSE_MAX_ 21 /** True iff <b>p</b> is a purpose corresponding to uploading * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ - (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2) + (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ + (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \ + (p)==DIR_PURPOSE_UPLOAD_HSDESC) #define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ @@ -850,6 +855,8 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) struct hs_ident_edge_conn_t; struct hs_ident_dir_conn_t; struct hs_ident_circuit_t; +/* Stub because we can't include hs_common.h. */ +struct hsdir_index_t; /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple @@ -2490,6 +2497,10 @@ typedef struct node_t { time_t last_reachable; /* IPv4. */ time_t last_reachable6; /* IPv6. */ + /* Hidden service directory index data. This is used by a service or client + * in order to know what's the hs directory index for this node at the time + * the consensus is set. */ + struct hsdir_index_t *hsdir_index; } node_t; /** Linked list of microdesc hash lines for a single router in a directory @@ -4616,6 +4627,9 @@ typedef struct { config_line_t *TransportProxies; + /** Cached revision counters for active hidden services on this host */ + config_line_t *HidServRevCounter; + /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the * bandwidth usage. The "Interval" fields hold the granularity, in seconds, diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c index 7959867875..6b5359303a 100644 --- a/src/or/parsecommon.c +++ b/src/or/parsecommon.c @@ -436,7 +436,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) * in the same order in which they occur in <b>s</b>. Otherwise return * NULL. */ smartlist_t * -find_all_by_keyword(smartlist_t *s, directory_keyword k) +find_all_by_keyword(const smartlist_t *s, directory_keyword k) { smartlist_t *out = NULL; SMARTLIST_FOREACH(s, directory_token_t *, t, diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index b9f1613457..5e5f9f4db6 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -160,6 +160,7 @@ typedef enum { R3_INTRO_AUTH_REQUIRED, R3_SINGLE_ONION_SERVICE, R3_INTRODUCTION_POINT, + R3_INTRO_ONION_KEY, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, R3_INTRO_ENC_KEY_CERT, @@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s, directory_token_t *find_opt_by_keyword(smartlist_t *s, directory_keyword keyword); -smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k); +smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k); #endif /* TOR_PARSECOMMON_H */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 986bfde75f..8b555a3164 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -777,7 +777,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = rend_service_receive_introduction(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) @@ -793,7 +793,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = rend_service_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 98ed1100ec..a205b00c6b 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -83,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable( smartlist_t* substitute_service_list); static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted); -/** Represents the mapping from a virtual port of a rendezvous service to - * a real port on some IP. - */ -struct rend_service_port_config_s { - /* The incoming HS virtual port we're mapping */ - uint16_t virtual_port; - /* Is this an AF_UNIX port? */ - unsigned int is_unix_addr:1; - /* The outgoing TCP port to use, if !is_unix_addr */ - uint16_t real_port; - /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ - tor_addr_t real_addr; - /* The socket path to connect to, if is_unix_addr */ - char unix_addr[FLEXIBLE_ARRAY_MEMBER]; -}; - /* Hidden service directory file names: * new file names should be added to rend_service_add_filenames_to_list() * for sandboxing purposes. */ @@ -164,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s) /** Return the number of rendezvous services we have configured. */ int -num_rend_services(void) +rend_num_services(void) { if (!rend_service_list) return 0; @@ -1694,24 +1678,6 @@ rend_service_get_by_service_id(const char *id) return NULL; } -/** Return 1 if any virtual port in <b>service</b> wants a circuit - * to have good uptime. Else return 0. - */ -static int -rend_service_requires_uptime(rend_service_t *service) -{ - int i; - rend_service_port_config_t *p; - - for (i=0; i < smartlist_len(service->ports); ++i) { - p = smartlist_get(service->ports, i); - if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, - p->virtual_port)) - return 1; - } - return 0; -} - /** Check client authorization of a given <b>descriptor_cookie</b> of * length <b>cookie_len</b> for <b>service</b>. Return 1 for success * and 0 for failure. */ @@ -2029,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, goto err; } - circ_needs_uptime = rend_service_requires_uptime(service); + circ_needs_uptime = hs_service_requires_uptime_circ(service->ports); /* help predict this next time */ rep_hist_note_used_internal(now, circ_needs_uptime, 1); @@ -2926,29 +2892,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); - /* Don't relaunch the same rend circ twice. */ - if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) { - log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; " - "not relaunching it again.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1; - - if (!oldcirc->build_state || - oldcirc->build_state->failure_count > MAX_REND_FAILURES || - oldcirc->build_state->expiry_time < time(NULL)) { - log_info(LD_REND, - "Attempt to build circuit to %s for rendezvous has failed " - "too many times or expired; giving up.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldstate = oldcirc->build_state; tor_assert(oldstate); @@ -3116,10 +3059,11 @@ count_intro_point_circuits(const rend_service_t *service) crypto material. On success, fill <b>cell_body_out</b> and return the number of bytes written. On fail, return -1. */ -STATIC ssize_t -encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, char *rend_circ_nonce) +ssize_t +rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce) { int retval = -1; int r; @@ -3256,7 +3200,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /* Send the ESTABLISH_INTRO cell */ { ssize_t len; - len = encode_establish_intro_cell_legacy(buf, sizeof(buf), + len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), circuit->intro_key, circuit->cpath->prev->rend_circ_nonce); if (len < 0) { @@ -3983,10 +3927,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) * This is called once a second by the main loop. */ void -rend_consider_services_intro_points(void) +rend_consider_services_intro_points(time_t now) { int i; - time_t now; const or_options_t *options = get_options(); /* Are we in single onion mode? */ const int allow_direct = rend_service_allow_non_anonymous_connection( @@ -4003,7 +3946,6 @@ rend_consider_services_intro_points(void) exclude_nodes = smartlist_new(); retry_nodes = smartlist_new(); - now = time(NULL); SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) { int r; @@ -4281,60 +4223,6 @@ rend_service_dump_stats(int severity) } } -#ifdef HAVE_SYS_UN_H - -/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, - * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success - * else return -ENOSYS if AF_UNIX is not supported (see function in the - * #else statement below). */ -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - tor_assert(ports); - tor_assert(p); - tor_assert(p->is_unix_addr); - - smartlist_add(ports, p); - return 0; -} - -/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 - * on success else return -ENOSYS if AF_UNIX is not supported (see function - * in the #else statement below). */ -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - tor_assert(conn); - tor_assert(p); - tor_assert(p->is_unix_addr); - - conn->base_.socket_family = AF_UNIX; - tor_addr_make_unspec(&conn->base_.addr); - conn->base_.port = 1; - conn->base_.address = tor_strdup(p->unix_addr); - return 0; -} - -#else /* defined(HAVE_SYS_UN_H) */ - -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - (void) conn; - (void) p; - return -ENOSYS; -} - -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - (void) ports; - (void) p; - return -ENOSYS; -} - -#endif /* HAVE_SYS_UN_H */ - /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for * 'circ', and look up the port and address based on conn-\>port. * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure @@ -4347,9 +4235,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, { rend_service_t *service; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - smartlist_t *matching_ports; - rend_service_port_config_t *chosen_port; - unsigned int warn_once = 0; const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); @@ -4385,41 +4270,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return service->max_streams_close_circuit ? -2 : -1; } } - matching_ports = smartlist_new(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, - { - if (conn->base_.port != p->virtual_port) { - continue; - } - if (!(p->is_unix_addr)) { - smartlist_add(matching_ports, p); - } else { - if (add_unix_port(matching_ports, p)) { - if (!warn_once) { - /* Unix port not supported so warn only once. */ - log_warn(LD_REND, - "Saw AF_UNIX virtual port mapping for port %d on service " - "%s, which is unsupported on this platform. Ignoring it.", - conn->base_.port, serviceid); - } - warn_once++; - } - } - }); - chosen_port = smartlist_choose(matching_ports); - smartlist_free(matching_ports); - if (chosen_port) { - if (!(chosen_port->is_unix_addr)) { - /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ - tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); - conn->base_.port = chosen_port->real_port; - } else { - if (set_unix_port(conn, chosen_port)) { - /* Simply impossible to end up here else we were able to add a Unix - * port without AF_UNIX support... ? */ - tor_assert(0); - } - } + + if (hs_set_conn_addr_port(service->ports, conn) == 0) { + /* Successfully set the port to the connection. We are done. */ return 0; } diff --git a/src/or/rendservice.h b/src/or/rendservice.h index ffed21d14e..ed1044f04a 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -16,9 +16,6 @@ #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; - -#ifdef RENDSERVICE_PRIVATE /* This can be used for both INTRODUCE1 and INTRODUCE2 */ @@ -64,6 +61,8 @@ struct rend_intro_cell_s { uint8_t dh[DH_KEY_LEN]; }; +#ifdef RENDSERVICE_PRIVATE + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ @@ -126,10 +125,6 @@ STATIC int rend_service_verify_single_onion_poison( STATIC int rend_service_poison_new_single_onion_dir( const rend_service_t *s, const or_options_t* options); -STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, - char *rend_circ_nonce); #ifdef TOR_UNIT_TESTS STATIC void set_rend_service_list(smartlist_t *new_list); @@ -140,7 +135,7 @@ STATIC void rend_service_prune_list_impl_(void); #endif /* RENDSERVICE_PRIVATE */ -int num_rend_services(void); +int rend_num_services(void); int rend_config_service(const config_line_t *line_, const or_options_t *options, hs_service_config_t *config); @@ -149,7 +144,7 @@ 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); -void rend_consider_services_intro_points(void); +void rend_consider_services_intro_points(time_t now); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); void rend_consider_descriptor_republication(void); @@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request, char **err_msg_out); int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro, char **err_msg_out); +ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce); int rend_service_validate_intro_late(const rend_intro_cell_t *intro, char **err_msg_out); void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc); diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 8823c0b53e..3449e6f6b5 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -2706,7 +2706,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->supports_ed25519_hs_intro = protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); rs->supports_v3_hsdir = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2); + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HSDIR_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -2718,6 +2719,12 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); rs->protocols_known = 1; } + if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { + /* Bug #22447 forces us to filter on this version. */ + if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { + rs->supports_v3_hsdir = 0; + } + } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); } diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 25ca0611cd..e4ee64139a 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1390,6 +1390,52 @@ sr_get_previous_for_control(void) return srv_str; } +/* Return current shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_current(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.current_srv; + } + return NULL; +} + +/* Return previous shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_previous(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.previous_srv; + } + return NULL; +} + #ifdef TOR_UNIT_TESTS /* Set the global value of number of SRV agreements so the test can play diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 1f027c70e0..76d5b95422 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp, char *sr_get_current_for_control(void); char *sr_get_previous_for_control(void); +const sr_srv_t *sr_get_current(const networkstatus_t *ns); +const sr_srv_t *sr_get_previous(const networkstatus_t *ns); + #ifdef SHARED_RANDOM_PRIVATE /* Encode */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index ef026e7f23..d7ae05e895 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -133,7 +133,7 @@ get_voting_interval(void) /* Given the time <b>now</b>, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. */ -static time_t +STATIC time_t get_start_time_of_current_round(time_t now) { const or_options_t *options = get_options(); @@ -156,6 +156,42 @@ get_start_time_of_current_round(time_t now) return curr_start; } +/** Return the start time of the current SR protocol run. For example, if the + * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this + * function should return 23/06/2017 00:00:00. */ +time_t +sr_state_get_start_time_of_current_protocol_run(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + time_t beginning_of_current_round = get_start_time_of_current_round(now); + + /* Get current SR protocol round */ + int current_round = (now / voting_interval) % total_rounds; + + /* Get start time by subtracting the time elapsed from the beginning of the + protocol run */ + time_t time_elapsed_since_start_of_run = current_round * voting_interval; + return beginning_of_current_round - time_elapsed_since_start_of_run; +} + +/** Return the time (in seconds) it takes to complete a full SR protocol phase + * (e.g. the commit phase). */ +unsigned int +sr_state_get_phase_duration(void) +{ + return SHARED_RANDOM_N_ROUNDS * get_voting_interval(); +} + +/** Return the time (in seconds) it takes to complete a full SR protocol run */ +unsigned int +sr_state_get_protocol_run_duration(void) +{ + int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + return total_protocol_rounds * get_voting_interval(); +} + /* Return the time we should expire the state file created at <b>now</b>. * We expire the state file in the beginning of the next protocol run. */ STATIC time_t diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 3526ad47d3..837fa75392 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -121,11 +121,16 @@ int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); +time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +unsigned int sr_state_get_phase_duration(void); +unsigned int sr_state_get_protocol_run_duration(void); + #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_start_time_of_current_round(time_t now); STATIC time_t get_state_valid_until_time(time_t now); STATIC const char *get_phase_str(sr_phase_t phase); diff --git a/src/or/statefile.c b/src/or/statefile.c index aaed104095..18111771da 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -85,6 +85,8 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), + V(HidServRevCounter, LINELIST, NULL), + V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), V(BWHistoryReadValues, CSV, ""), |