From 5750f18bc7efacc2405918d08bc3f47e4c6a7ae9 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 9 Aug 2017 11:25:53 -0400 Subject: [PATCH 01/62] sr: Log voting schedule at debug level Because the HS subsystem calls it every second, change the log level to debug so it doesn't spam the info log. Signed-off-by: David Goulet --- src/or/shared_random_state.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index d7ae05e895..5fcf88cc4a 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -139,7 +139,7 @@ get_start_time_of_current_round(time_t now) const or_options_t *options = get_options(); int voting_interval = get_voting_interval(); voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); + get_voting_schedule(options, now, LOG_DEBUG); tor_assert(new_voting_schedule); /* First, get the start time of the next round */ From 43343ec019e24cb3b1d5802d1369ab75ec0e8776 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 12 Aug 2017 13:05:25 +0300 Subject: [PATCH 02/62] prop224: Improve our checks for unset HSDir index. We used to not check next hsdir index. --- src/or/hs_common.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 6c860b0cf0..0529c0cdb0 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1180,9 +1180,10 @@ hs_get_hsdir_spread_store(void) } /** node is an HSDir so make sure that we have assigned an hsdir index. + * If is_for_next_period is set, also check the next HSDir index field. * Return 0 if everything is as expected, else return -1. */ static int -node_has_hsdir_index(const node_t *node) +node_has_hsdir_index(const node_t *node, int is_for_next_period) { tor_assert(node_supports_v3_hsdir(node)); @@ -1200,6 +1201,12 @@ node_has_hsdir_index(const node_t *node) return 0; } + if (is_for_next_period && + BUG(tor_mem_is_zero((const char*)node->hsdir_index->next, + DIGEST256_LEN))) { + return 0; + } + return 1; } @@ -1244,7 +1251,7 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, 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)) { + if (!node_has_hsdir_index(n, is_next_period)) { log_info(LD_GENERAL, "Node %s was found without hsdir index.", node_describe(n)); continue; From 1f7b8012ae9b6226e5853751685c98782a48c0c4 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sun, 13 Aug 2017 20:16:21 +0300 Subject: [PATCH 03/62] prop224: Only upload descriptor if we have good hash ring and SRV. Make sure we have a live consensus (for SRV) and enough descriptors (for hash ring). Also fix unittests that broke. --- src/or/hs_service.c | 11 +++++++++++ src/or/nodelist.c | 4 ++-- src/or/nodelist.h | 2 +- src/test/test_hs_service.c | 13 +++++++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 3f6de0ba2d..cf5f319f80 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2288,6 +2288,17 @@ should_service_upload_descriptor(const hs_service_t *service, goto cannot; } + /* Don't upload desc if we don't have a live consensus */ + if (!networkstatus_get_live_consensus(now)) { + goto cannot; + } + + /* Do we know enough router descriptors to have adequate vision of the HSDir + hash ring? */ + if (!router_have_minimum_dir_info()) { + goto cannot; + } + /* Can upload! */ return 1; cannot: diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 0fcaea626d..a9b77262ce 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -1741,8 +1741,8 @@ static char dir_info_status[512] = ""; * no exits in the consensus." * To obtain the final weighted bandwidth, we multiply the * weighted bandwidth fraction for each position (guard, middle, exit). */ -int -router_have_minimum_dir_info(void) +MOCK_IMPL(int, +router_have_minimum_dir_info,(void)) { static int logged_delay=0; const char *delay_fetches_msg = NULL; diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 405b79d820..06a08a288c 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -105,7 +105,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1, * no exits in the consensus, we wait for enough info to create internal * paths, and should avoid creating exit paths, as they will simply fail. * We make sure we create all available circuit types at the same time. */ -int router_have_minimum_dir_info(void); +MOCK_DECL(int, router_have_minimum_dir_info,(void)); /** Set to CONSENSUS_PATH_EXIT if there is at least one exit node * in the consensus. We update this flag in compute_frac_paths_available if diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index c0dd9fe251..7263e04579 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1177,6 +1177,12 @@ test_build_update_descriptors(void *arg) UNMOCK(hs_overlap_mode_is_active); } +static int +mock_router_have_minimum_dir_info(void) +{ + return 1; +} + static void test_upload_descriptors(void *arg) { @@ -1191,7 +1197,6 @@ test_upload_descriptors(void *arg) MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true); MOCK(get_or_state, get_or_state_replacement); - dummy_state = tor_malloc_zero(sizeof(or_state_t)); /* Create a service with no descriptor. It's added to the global map. */ @@ -1229,9 +1234,13 @@ test_upload_descriptors(void *arg) ip->circuit_established = 1; service_intro_point_add(service->desc_current->intro_points.map, ip); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info); + setup_full_capture_of_logs(LOG_WARN); run_upload_descriptor_event(now); - expect_log_msg_containing("No valid consensus so we can't get the"); teardown_capture_of_logs(); tt_u64_op(service->desc_current->next_upload_time, OP_GE, now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN); From 26c85fcc863678c06cc79ec1a58bd6c504d3321a Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sun, 13 Aug 2017 23:25:08 +0300 Subject: [PATCH 04/62] test: Improve get_responsible_hsdirs test. --- src/test/test_hs_common.c | 94 ++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 2461e7cd6e..db37663075 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -357,6 +357,37 @@ test_desc_overlap_period_testnet(void *arg) tor_free(dummy_consensus); } +static void +helper_add_hsdir_to_networkstatus(networkstatus_t *ns, + const uint8_t *identity, + const uint8_t *curr_hsdir_index, + const char *nickname, + int is_hsdir) +{ + routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t)); + routerinfo_t *ri = tor_malloc_zero(sizeof(routerinfo_t)); + + tor_addr_t ipv4_addr; + memcpy(rs->identity_digest, identity, DIGEST_LEN); + rs->is_hs_dir = is_hsdir; + rs->supports_v3_hsdir = 1; + tor_addr_parse(&ipv4_addr, "1.2.3.4"); + ri->addr = tor_addr_to_ipv4h(&ipv4_addr); + ri->nickname = tor_strdup(nickname); + ri->protocol_list = tor_strdup("HSDir=1-2 LinkAuth=3"); + memcpy(ri->cache_info.identity_digest, identity, DIGEST_LEN); + tt_assert(nodelist_set_routerinfo(ri, NULL)); + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + tt_assert(node); + node->rs = rs; + memcpy(node->hsdir_index->current, curr_hsdir_index, + sizeof(node->hsdir_index->current)); + smartlist_add(ns->routerstatus_list, rs); + + done: + ; +} + static networkstatus_t *mock_ns = NULL; static networkstatus_t * @@ -389,7 +420,7 @@ test_responsible_hsdirs(void *arg) time_t now = approx_time(); smartlist_t *responsible_dirs = smartlist_new(); networkstatus_t *ns = NULL; - routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t)); + int retval; (void) arg; @@ -401,34 +432,55 @@ test_responsible_hsdirs(void *arg) ns = networkstatus_get_latest_consensus(); { /* First router: HSdir */ - tor_addr_t ipv4_addr; - memset(rs->identity_digest, 'A', DIGEST_LEN); - rs->is_hs_dir = 1; - rs->supports_v3_hsdir = 1; - routerinfo_t ri; - memset(&ri, 0 ,sizeof(routerinfo_t)); - tor_addr_parse(&ipv4_addr, "127.0.0.1"); - ri.addr = tor_addr_to_ipv4h(&ipv4_addr); - ri.nickname = (char *) "fatal"; - ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3"; - memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); - tt_assert(nodelist_set_routerinfo(&ri, NULL)); - node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest); - memset(node->hsdir_index->current, 'Z', - sizeof(node->hsdir_index->current)); - smartlist_add(ns->routerstatus_list, rs); + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "let_me"; + memset(identity, 1, sizeof(identity)); + memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 1); } - ed25519_public_key_t blinded_pk; + { /* Second HSDir */ + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "show_you"; + memset(identity, 2, sizeof(identity)); + memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 1); + } + + { /* Third relay but not HSDir */ + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "how_to_dance"; + memset(identity, 3, sizeof(identity)); + memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 0); + } + + ed25519_keypair_t kp; + retval = ed25519_keypair_generate(&kp, 0); + tt_int_op(retval, OP_EQ , 0); + uint64_t time_period_num = hs_get_time_period_num(now); - hs_get_responsible_hsdirs(&blinded_pk, time_period_num, + hs_get_responsible_hsdirs(&kp.pubkey, time_period_num, 0, 0, responsible_dirs); - tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1); + + /* Make sure that we only found 2 responsible HSDirs. + * The third relay was not an hsdir! */ + tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 2); /** TODO: Build a bigger network and do more tests here */ done: - routerstatus_free(rs); + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); smartlist_free(responsible_dirs); smartlist_clear(ns->routerstatus_list); networkstatus_vote_free(mock_ns); From 7823c98a38556237a86c7235d411d7d2237cc2d6 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 19 Aug 2017 16:26:46 +0300 Subject: [PATCH 05/62] prop224: Improve descriptor reupload logic. We want to reupload our descriptor if its set of responsible HSDirs changed to minimize reachability issues. This patch adds a callback everytime we get new dirinfo which checks if the hash ring changed and reuploads descriptor if needed. --- src/or/hs_descriptor.c | 8 +- src/or/hs_descriptor.h | 7 +- src/or/hs_service.c | 136 ++++++++++++++++++++++++++++++- src/or/hs_service.h | 15 ++++ src/or/nodelist.c | 1 + src/test/test_hs_common.c | 163 +++++++++++++++++++++++++++++++++++++- 6 files changed, 320 insertions(+), 10 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 9a1e377155..3cdc023f79 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -2357,10 +2357,10 @@ static int * * Return 0 on success and encoded_out is a valid pointer. On error, -1 is * returned and encoded_out is set to NULL. */ -int -hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out) +MOCK_IMPL(int, +hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)) { int ret = -1; uint32_t version; diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index fa211d3917..19e52333be 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -211,9 +211,10 @@ 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); +MOCK_DECL(int, + hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)); int hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, diff --git a/src/or/hs_service.c b/src/or/hs_service.c index cf5f319f80..e213efe06a 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -972,6 +972,10 @@ service_descriptor_free(hs_service_descriptor_t *desc) /* Cleanup all intro points. */ digest256map_free(desc->intro_points.map, service_intro_point_free_); digestmap_free(desc->intro_points.failed_id, tor_free_); + if (desc->previous_hsdirs) { + SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s)); + smartlist_free(desc->previous_hsdirs); + } tor_free(desc); } @@ -985,6 +989,7 @@ service_descriptor_new(void) sdesc->intro_points.map = digest256map_new(); sdesc->intro_points.failed_id = digestmap_new(); sdesc->hsdir_missing_info = smartlist_new(); + sdesc->previous_hsdirs = smartlist_new(); return sdesc; } @@ -1511,6 +1516,52 @@ pick_needed_intro_points(hs_service_t *service, return i; } +/** Clear previous cached HSDirs in desc. */ +static void +service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc) +{ + if (BUG(!desc->previous_hsdirs)) { + return; + } + + SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s)); + smartlist_clear(desc->previous_hsdirs); +} + +/** Note that we attempted to upload desc to hsdir. */ +static void +service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir->identity); + + if (BUG(!desc->previous_hsdirs)) { + return; + } + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + smartlist_add_strdup(desc->previous_hsdirs, b64_digest); + smartlist_sort_strings(desc->previous_hsdirs); + } +} + +/** Schedule an upload of desc. If descriptor_changed is set, it + * means that this descriptor is dirty. */ +STATIC void +service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed) + +{ + desc->next_upload_time = now; + + /* If the descriptor changed, clean up the old HSDirs list. We want to + * re-upload no matter what. */ + if (descriptor_changed) { + service_desc_clear_previous_hsdirs(desc); + } +} + /* Update the given descriptor from the given service. The possible update * actions includes: * - Picking missing intro points if needed. @@ -1543,7 +1594,7 @@ update_service_descriptor(hs_service_t *service, /* 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; + service_desc_schedule_upload(desc, now, 1); } /* 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 @@ -1972,6 +2023,9 @@ upload_descriptor_to_hsdir(const hs_service_t *service, directory_initiate_request(dir_req); directory_request_free(dir_req); + /* Add this node to previous_hsdirs list */ + service_desc_note_upload(desc, hsdir); + /* Logging so we know where it was sent. */ { int is_next_desc = (service->desc_next == desc); @@ -2189,7 +2243,7 @@ set_descriptor_revision_counter(hs_descriptor_t *hs_desc) * 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 +STATIC void upload_descriptor_to_all(const hs_service_t *service, hs_service_descriptor_t *desc, int for_next_period) { @@ -2629,10 +2683,88 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) smartlist_add(list, hs_path_from_filename(s_dir, fname)); } +/** The set of HSDirs have changed: check if the change affects our descriptor + * HSDir placement, and if it does, reupload the desc. */ +static int +service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + int retval = 0; + smartlist_t *responsible_dirs = smartlist_new(); + smartlist_t *b64_responsible_dirs = smartlist_new(); + + /* No desc upload has happened yet: it will happen eventually */ + if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { + goto done; + } + + /* Get list of responsible hsdirs */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /* Make a second list with their b64ed identity digests, so that we can + * compare it with out previous list of hsdirs */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir_rs->identity_digest); + smartlist_add_strdup(b64_responsible_dirs, b64_digest); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Sort this new smartlist so that we can compare it with the other one */ + smartlist_sort_strings(b64_responsible_dirs); + + /* Check whether the set of HSDirs changed */ + if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) { + log_warn(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!"); + retval = 1; + } else { + log_warn(LD_GENERAL, "No change in hsdir set!"); + } + + done: + smartlist_free(responsible_dirs); + + SMARTLIST_FOREACH(b64_responsible_dirs, char*, s, tor_free(s)); + smartlist_free(b64_responsible_dirs); + + return retval; +} + /* ========== */ /* Public API */ /* ========== */ +/* We just received a new batch of descriptors which might affect the shape of + * the HSDir hash ring. Signal that we should re-upload our HS descriptors. */ +void +hs_hsdir_set_changed_consider_reupload(void) +{ + time_t now = approx_time(); + + /* Check if HS subsystem is initialized */ + if (!hs_service_map) { + return; + } + + /* Basic test: If we have not bootstrapped 100% yet, no point in even trying + to upload descriptor. */ + if (!router_have_minimum_dir_info()) { + return; + } + + log_info(LD_GENERAL, "Received new descriptors. Set of HSdirs changed."); + + /* Go over all descriptors and check if the set of HSDirs changed for any of + * them. Schedule reupload if so. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (service_desc_hsdirs_changed(service, desc)) { + service_desc_schedule_upload(desc, now, 0); + } + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + /* Return the number of service we have configured and usable. */ unsigned int hs_service_get_num_services(void) diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 8d613d23ed..5bd19931fc 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -129,6 +129,12 @@ typedef struct hs_service_descriptor_t { * list are re-tried to upload this descriptor when our directory information * have been updated. */ smartlist_t *hsdir_missing_info; + + /** List of the responsible HSDirs (their b64ed identity digest) last time we + * uploaded this descriptor. If the set of responsible HSDirs is different + * from this list, this means we received new dirinfo and we need to + * reupload our descriptor. This list is always sorted lexicographically. */ + smartlist_t *previous_hsdirs; } hs_service_descriptor_t; /* Service key material. */ @@ -260,6 +266,7 @@ 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_hsdir_set_changed_consider_reupload(void); void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); @@ -338,6 +345,14 @@ check_state_line_for_service_rev_counter(const char *state_line, STATIC int write_address_to_file(const hs_service_t *service, const char *fname_); +STATIC void upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc, + int for_next_period); + +STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed); + #endif /* TOR_UNIT_TESTS */ #endif /* HS_SERVICE_PRIVATE */ diff --git a/src/or/nodelist.c b/src/or/nodelist.c index a9b77262ce..d75b386e01 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -1789,6 +1789,7 @@ router_dir_info_changed(void) { need_to_update_have_min_dir_info = 1; rend_hsdir_routers_changed(); + hs_hsdir_set_changed_consider_reupload(); } /** Return a string describing what we're missing before we have enough diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index db37663075..c60deb2ab9 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -18,7 +18,9 @@ #include "hs_service.h" #include "config.h" #include "networkstatus.h" +#include "directory.h" #include "nodelist.h" +#include "statefile.h" /** Test the validation of HS v3 addresses */ static void @@ -486,6 +488,163 @@ test_responsible_hsdirs(void *arg) networkstatus_vote_free(mock_ns); } +static void +mock_directory_initiate_request(directory_request_t *req) +{ + (void)req; + return; +} + +static int +mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out) +{ + (void)desc; + (void)signing_kp; + + tor_asprintf(encoded_out, "lulu"); + return 0; +} + +static or_state_t dummy_state; + +/* Mock function to get fake or state (used for rev counters) */ +static or_state_t * +get_or_state_replacement(void) +{ + return &dummy_state; +} + +static int +mock_router_have_minimum_dir_info(void) +{ + return 1; +} + +/** Test that we correctly detect when the HSDir hash ring changes so that we + * reupload our descriptor. */ +static void +test_desc_reupload_logic(void *arg) +{ + networkstatus_t *ns = NULL; + + (void) arg; + + hs_init(); + + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + MOCK(directory_initiate_request, + mock_directory_initiate_request); + MOCK(hs_desc_encode_descriptor, + mock_hs_desc_encode_descriptor); + + ns = networkstatus_get_latest_consensus(); + + /** Test logic: + * 1) Upload descriptor to HSDirs + * CHECK that previous_hsdirs list was populated. + * 2) Then call router_dir_info_changed() without an HSDir set change. + * CHECK that no reuplod occurs. + * 3) Now change the HSDir set, and call dir_info_changed() again. + * CHECK that reupload occurs. + * 4) Finally call service_desc_schedule_upload(). + * CHECK that previous_hsdirs list was cleared. + **/ + + /* Let's start by building our descriptor and service */ + hs_service_descriptor_t *desc = service_descriptor_new(); + hs_service_t *service = NULL; + char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_public_key_t pubkey; + memset(&pubkey, '\x42', sizeof(pubkey)); + hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); + service = tor_malloc_zero(sizeof(hs_service_t)); + memcpy(service->onion_address, onion_addr, sizeof(service->onion_address)); + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + service->desc_current = desc; + /* Also add service to service map */ + hs_service_ht *service_map = get_hs_service_map(); + tt_assert(service_map); + tt_int_op(hs_service_get_num_services(), OP_EQ, 0); + register_service(service_map, service); + tt_int_op(hs_service_get_num_services(), OP_EQ, 1); + + /* Now let's create our hash ring: */ + { /* First HSDir */ + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "let_me"; + memset(identity, 1, sizeof(identity)); + memset(curr_hsdir_index, 1, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 1); + } + + { /* Second HSDir */ + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "show_you"; + memset(identity, 2, sizeof(identity)); + memset(curr_hsdir_index, 2, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 1); + } + + /* Now let's upload our desc to all hsdirs */ + upload_descriptor_to_all(service, desc, 0); + /* Check that previous hsdirs were populated */ + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 2); + + /* Poison next upload time so that we can see if it was changed by + * router_dir_info_changed(). No changes in hash ring so far, so the upload + * time should stay as is. */ + desc->next_upload_time = 42; + router_dir_info_changed(); + tt_int_op(desc->next_upload_time, OP_EQ, 42); + + /* Now change the HSDir hash ring by adding another node */ + + { /* Third HSDir */ + uint8_t identity[DIGEST_LEN]; + uint8_t curr_hsdir_index[DIGEST256_LEN]; + char nickname[] = "how_to_dance"; + memset(identity, 3, sizeof(identity)); + memset(curr_hsdir_index, 3, sizeof(curr_hsdir_index)); + + helper_add_hsdir_to_networkstatus(ns, identity, + curr_hsdir_index, nickname, 1); + } + + /* Now call router_dir_info_changed() again and see that it detected the hash + ring change and updated the upload time */ + time_t now = approx_time(); + tt_assert(now); + router_dir_info_changed(); + tt_int_op(desc->next_upload_time, OP_EQ, now); + + /* Now pretend that the descriptor changed, and order a reupload to all + HSDirs. Make sure that the set of previous HSDirs was cleared. */ + service_desc_schedule_upload(desc, now, 1); + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 0); + + /* Now reupload again: see that the prev hsdir set got populated again. */ + upload_descriptor_to_all(service, desc, 0); + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 3); + + done: + hs_free_all(); +} + /** Test disaster SRV computation and caching */ static void test_disaster_srv(void *arg) @@ -550,7 +709,9 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK, NULL, NULL }, - { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK, + { "responsible_hsdirs", test_responsible_hsdirs, TT_FORK, + NULL, NULL }, + { "desc_reupload_logic", test_desc_reupload_logic, TT_FORK, NULL, NULL }, { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL }, From 3e593f09addb210f1da39bd46f5fb904cac4e410 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 19 Aug 2017 16:00:58 +0300 Subject: [PATCH 06/62] prop224: Recompute all HSDir indices when we enter overlap mode. When we enter overlap mode we start using the next hsdir index of relays. However, we only compute the next hsdir index of relays when we receive a consensus or their descriptor. This means that there is a window of time between entering the overlap period and fetching the consensus where relays have their next hsdir index uninitialized. This patch fixes this by recomputing all hsdir indices when we first enter the overlap period. --- src/or/hs_service.c | 7 +++++++ src/or/nodelist.c | 21 +++++++++++++++++++++ src/or/nodelist.h | 2 ++ 3 files changed, 30 insertions(+) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index e213efe06a..31776c8b5e 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1739,6 +1739,13 @@ rotate_all_descriptors(time_t now) * it in order to make sure we don't rotate at next check. */ service->state.in_overlap_period = 1; + /* We just entered overlap period: recompute all HSDir indices. We need to + * do this otherwise nodes can get stuck with old HSDir indices until we + * fetch a new consensus, and we might need to reupload our desc before + * that. */ + /* XXX find a better place than rotate_all_descriptors() to do this */ + nodelist_recompute_all_hsdir_indices(); + /* If we have a next descriptor lined up, rotate the descriptors so that it * becomes current. */ if (service->desc_next) { diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d75b386e01..6acc87f967 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -238,6 +238,27 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) return; } +/** Recompute all node hsdir indices. */ +void +nodelist_recompute_all_hsdir_indices(void) +{ + networkstatus_t *consensus; + if (!the_nodelist) { + return; + } + + /* Get a live consensus. Abort if not found */ + consensus = networkstatus_get_live_consensus(approx_time()); + if (!consensus) { + return; + } + + /* Recompute all hsdir indices */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node_set_hsdir_index(node, consensus); + } SMARTLIST_FOREACH_END(node); +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 06a08a288c..d16cf0ecf7 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -28,6 +28,8 @@ void nodelist_remove_routerinfo(routerinfo_t *ri); void nodelist_purge(void); smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); +void nodelist_recompute_all_hsdir_indices(void); + void nodelist_free_all(void); void nodelist_assert_ok(void); From bce18a764252c66e605680e29a27ea30375a6db1 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 13:21:03 +0300 Subject: [PATCH 07/62] prop224: Refactor parse_extended_hostname() to parse v3 addrs. We need this func so that we recognize SOCKS conns to v3 addresses. - Also rename rend_valid_service_id() to rend_valid_v2_service_id() - Also move parse_extended_hostname() tests to their own unittest, and add a v3 address to the test as well. Signed-off-by: David Goulet --- src/or/connection_edge.c | 21 +++++++++++++-------- src/or/connection_edge.h | 3 ++- src/or/control.c | 4 ++-- src/or/rendcache.c | 4 ++-- src/or/rendclient.c | 2 +- src/or/rendcommon.c | 2 +- src/or/rendcommon.h | 2 +- src/or/rendservice.c | 2 +- src/test/test.c | 17 ----------------- src/test/test_hs_common.c | 38 +++++++++++++++++++++++++++++++++++++- 10 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 12ddc7e829..941b273cc6 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1558,7 +1558,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Now, we handle everything that isn't a .onion address. */ - if (addresstype != ONION_HOSTNAME) { + if (addresstype != ONION_V2_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, * possibly with a .exit that we stripped off. We're going to check * if we're allowed to connect/resolve there, and then launch the @@ -3679,10 +3679,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn, } /** If address is of the form "y.onion" with a well-formed handle y: - * Put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or + * ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "x.y.onion" with a well-formed handle x: - * Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Drop "x.", put a NUL after y, lower-case it, and return + * ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "y.onion" with a badly-formed handle y: * Return BAD_HOSTNAME and log a message. @@ -3698,7 +3700,7 @@ parse_extended_hostname(char *address) { char *s; char *q; - char query[REND_SERVICE_ID_LEN_BASE32+1]; + char query[HS_SERVICE_ADDR_LEN_BASE32+1]; s = strrchr(address,'.'); if (!s) @@ -3718,14 +3720,17 @@ parse_extended_hostname(char *address) goto failed; /* reject sub-domain, as DNS does */ } q = (NULL == q) ? address : q + 1; - if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >= - REND_SERVICE_ID_LEN_BASE32+1) + if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >= + HS_SERVICE_ADDR_LEN_BASE32+1) goto failed; if (q != address) { memmove(address, q, strlen(q) + 1 /* also get \0 */); } - if (rend_valid_service_id(query)) { - return ONION_HOSTNAME; /* success */ + if (rend_valid_v2_service_id(query)) { + return ONION_V2_HOSTNAME; /* success */ + } + if (hs_address_is_valid(query)) { + return ONION_V3_HOSTNAME; } failed: /* otherwise, return to previous state and return 0 */ diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 9987f88b85..914238fc1f 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -98,7 +98,8 @@ int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, /** Possible return values for parse_extended_hostname. */ typedef enum hostname_type_t { - NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME + NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME, + EXIT_HOSTNAME, BAD_HOSTNAME } hostname_type_t; hostname_type_t parse_extended_hostname(char *address); diff --git a/src/or/control.c b/src/or/control.c index 724d4b35c0..e8cbd046db 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -4132,7 +4132,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* Extract the first argument (either HSAddress or DescID). */ arg1 = smartlist_get(args, 0); /* Test if it's an HS address without the .onion part. */ - if (rend_valid_service_id(arg1)) { + if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; } else if (strcmpstart(arg1, v2_str) == 0 && rend_valid_descriptor_id(arg1 + v2_str_len) && @@ -4771,7 +4771,7 @@ handle_control_del_onion(control_connection_t *conn, return 0; const char *service_id = smartlist_get(args, 0); - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } diff --git a/src/or/rendcache.c b/src/or/rendcache.c index 11b60b36a1..792f6a1302 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) tor_assert(rend_cache); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } @@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e) tor_assert(rend_cache_local_service); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } diff --git a/src/or/rendclient.c b/src/or/rendclient.c index e47e1ef639..fbfba27a23 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -1466,7 +1466,7 @@ rend_parse_service_authorization(const or_options_t *options, goto err; } strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1); - if (!rend_valid_service_id(auth->onion_address)) { + if (!rend_valid_v2_service_id(auth->onion_address)) { log_warn(LD_CONFIG, "Onion address has wrong format: '%s'", onion_address); goto err; diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 8b555a3164..8829ede960 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -695,7 +695,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out) /** Return true iff query is a syntactically valid service ID (as * generated by rend_get_service_id). */ int -rend_valid_service_id(const char *query) +rend_valid_v2_service_id(const char *query) { if (strlen(query) != REND_SERVICE_ID_LEN_BASE32) return 0; diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 292f9277e8..f03a57f2e1 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free( rend_encoded_v2_service_descriptor_t *desc); void rend_intro_point_free(rend_intro_point_t *intro); -int rend_valid_service_id(const char *query); +int rend_valid_v2_service_id(const char *query); int rend_valid_descriptor_id(const char *query); int rend_valid_client_name(const char *client_name); int rend_encode_v2_descriptors(smartlist_t *descs_out, diff --git a/src/or/rendservice.c b/src/or/rendservice.c index a205b00c6b..8dbab2e2a7 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -890,7 +890,7 @@ int rend_service_del_ephemeral(const char *service_id) { rend_service_t *s; - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal."); return -1; } diff --git a/src/test/test.c b/src/test/test.c index 994a97ab00..f775102ba6 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -534,25 +534,8 @@ test_rend_fns(void *arg) size_t intro_points_size; size_t encoded_size; int i; - char address1[] = "fooaddress.onion"; - char address2[] = "aaaaaaaaaaaaaaaa.onion"; - char address3[] = "fooaddress.exit"; - char address4[] = "www.torproject.org"; - char address5[] = "foo.abcdefghijklmnop.onion"; - char address6[] = "foo.bar.abcdefghijklmnop.onion"; - char address7[] = ".abcdefghijklmnop.onion"; (void)arg; - tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1)); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address2)); - tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa"); - tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3)); - tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4)); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address5)); - tt_str_op(address5,OP_EQ, "abcdefghijklmnop"); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address6)); - tt_str_op(address6,OP_EQ, "abcdefghijklmnop"); - tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7)); /* Initialize the service cache. */ rend_cache_init(); diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index c60deb2ab9..f7e8e2811f 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -14,6 +14,7 @@ #include "log_test_helpers.h" #include "hs_test_helpers.h" +#include "connection_edge.h" #include "hs_common.h" #include "hs_service.h" #include "config.h" @@ -696,6 +697,38 @@ test_disaster_srv(void *arg) ; } +static void +test_parse_extended_hostname(void *arg) +{ + (void) arg; + + char address1[] = "fooaddress.onion"; + char address2[] = "aaaaaaaaaaaaaaaa.onion"; + char address3[] = "fooaddress.exit"; + char address4[] = "www.torproject.org"; + char address5[] = "foo.abcdefghijklmnop.onion"; + char address6[] = "foo.bar.abcdefghijklmnop.onion"; + char address7[] = ".abcdefghijklmnop.onion"; + char address8[] = + "www.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion"; + + tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1)); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address2)); + tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa"); + tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3)); + tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4)); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address5)); + tt_str_op(address5,OP_EQ, "abcdefghijklmnop"); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address6)); + tt_str_op(address6,OP_EQ, "abcdefghijklmnop"); + tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7)); + tt_assert(ONION_V3_HOSTNAME == parse_extended_hostname(address8)); + tt_str_op(address8, OP_EQ, + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad"); + + done: ; +} + struct testcase_t hs_common_tests[] = { { "build_address", test_build_address, TT_FORK, NULL, NULL }, @@ -713,7 +746,10 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "desc_reupload_logic", test_desc_reupload_logic, TT_FORK, NULL, NULL }, - { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL }, + { "disaster_srv", test_disaster_srv, TT_FORK, + NULL, NULL }, + { "parse_extended_hostname", test_parse_extended_hostname, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From 5d89ea1e6c148ce584dc2059c4d353d12d01e8d1 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 13:24:28 +0300 Subject: [PATCH 08/62] prop224: Decouple the HS part of connection_ap_handshake_rewrite_and_attach(). We will need to edit this function, and it's already pretty huge. Let's make it a bit smaller. This commit moves code, fixes a 80 char line and add two lines at the start to make it compile. Trivial change. Signed-off-by: David Goulet --- src/or/connection_edge.c | 226 +++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 107 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 941b273cc6..a0aa7c195f 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1392,6 +1392,124 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } +static int +connection_ap_handle_onion(entry_connection_t *conn, + socks_request_t *socks, + origin_circuit_t *circ) +{ + time_t now = approx_time(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + + /* If .onion address requests are disabled, refuse the request */ + if (!conn->entry_cfg.onion_traffic) { + log_warn(LD_APP, "Onion address %s requested from a port with .onion " + "disabled", safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + + /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those + * for hidden service addresses. */ + if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { + /* if it's a resolve request, fail it right now, rather than + * building all the circuits and then realizing it won't work. */ + log_warn(LD_APP, + "Resolve requests to hidden services not allowed. Failing."); + connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, + 0,NULL,-1,TIME_MAX); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + return -1; + } + + /* If we were passed a circuit, then we need to fail. .onion addresses + * only work when we launch our own circuits for now. */ + if (circ) { + log_warn(LD_CONTROL, "Attachstream to a circuit is not " + "supported for .onion addresses currently. Failing."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization(socks->address); + + const uint8_t *cookie = NULL; + rend_auth_type_t auth_type = REND_NO_AUTH; + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + auth_type = client_auth->auth_type; + cookie = client_auth->descriptor_cookie; + } + + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = + rend_data_client_create(socks->address, NULL, (char *) cookie, + auth_type); + if (rend_data == NULL) { + return -1; + } + const char *onion_address = rend_data_get_address(rend_data); + log_info(LD_REND,"Got a hidden service request for ID '%s'", + safe_str_client(onion_address)); + + /* Lookup the given onion address. If invalid, stop right now. + * Otherwise, we might have it in the cache or not. */ + unsigned int refetch_desc = 0; + rend_cache_entry_t *entry = NULL; + const int rend_cache_lookup_result = + rend_cache_lookup_entry(onion_address, -1, &entry); + if (rend_cache_lookup_result < 0) { + switch (-rend_cache_lookup_result) { + case EINVAL: + /* We should already have rejected this address! */ + log_warn(LD_BUG,"Invalid service name '%s'", + safe_str_client(onion_address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + case ENOENT: + /* We didn't have this; we should look it up. */ + refetch_desc = 1; + break; + default: + log_warn(LD_BUG, "Unknown cache lookup error %d", + rend_cache_lookup_result); + return -1; + } + } + + /* Help predict that we'll want to do hidden service circuits in the + * future. We're not sure if it will need a stable circuit yet, but + * we know we'll need *something*. */ + rep_hist_note_used_internal(now, 0, 1); + + /* Now we have a descriptor but is it usable or not? If not, refetch. + * Also, a fetch could have been requested if the onion address was not + * found in the cache previously. */ + if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { + connection_ap_mark_as_non_pending_circuit(conn); + base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; + log_info(LD_REND, "Unknown descriptor %s. Fetching.", + safe_str_client(onion_address)); + rend_client_refetch_v2_renddesc(rend_data); + return 0; + } + + /* We have the descriptor! So launch a connection to the HS. */ + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + log_info(LD_REND, "Descriptor is here. Great."); + + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ + connection_ap_mark_as_pending_circuit(conn); + return 0; +} + /** Connection conn just finished its socks handshake, or the * controller asked us to take care of it. If circ is defined, * then that's where we'll want to attach it. Otherwise we have to @@ -1837,115 +1955,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } else { /* If we get here, it's a request for a .onion address! */ tor_assert(!automap); - - /* If .onion address requests are disabled, refuse the request */ - if (!conn->entry_cfg.onion_traffic) { - log_warn(LD_APP, "Onion address %s requested from a port with .onion " - "disabled", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + if (connection_ap_handle_onion(conn, socks, circ) < 0) { return -1; } - - /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those - * for hidden service addresses. */ - if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { - /* if it's a resolve request, fail it right now, rather than - * building all the circuits and then realizing it won't work. */ - log_warn(LD_APP, - "Resolve requests to hidden services not allowed. Failing."); - connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, - 0,NULL,-1,TIME_MAX); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_SOCKSPROTOCOL | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return -1; - } - - /* If we were passed a circuit, then we need to fail. .onion addresses - * only work when we launch our own circuits for now. */ - if (circ) { - log_warn(LD_CONTROL, "Attachstream to a circuit is not " - "supported for .onion addresses currently. Failing."); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - } - - /* Look up if we have client authorization configured for this hidden - * service. If we do, associate it with the rend_data. */ - rend_service_authorization_t *client_auth = - rend_client_lookup_service_authorization(socks->address); - - const uint8_t *cookie = NULL; - rend_auth_type_t auth_type = REND_NO_AUTH; - if (client_auth) { - log_info(LD_REND, "Using previously configured client authorization " - "for hidden service request."); - auth_type = client_auth->auth_type; - cookie = client_auth->descriptor_cookie; - } - - /* Fill in the rend_data field so we can start doing a connection to - * a hidden service. */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = - rend_data_client_create(socks->address, NULL, (char *) cookie, - auth_type); - if (rend_data == NULL) { - return -1; - } - const char *onion_address = rend_data_get_address(rend_data); - log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(onion_address)); - - /* Lookup the given onion address. If invalid, stop right now. - * Otherwise, we might have it in the cache or not. */ - unsigned int refetch_desc = 0; - rend_cache_entry_t *entry = NULL; - const int rend_cache_lookup_result = - rend_cache_lookup_entry(onion_address, -1, &entry); - if (rend_cache_lookup_result < 0) { - switch (-rend_cache_lookup_result) { - case EINVAL: - /* We should already have rejected this address! */ - log_warn(LD_BUG,"Invalid service name '%s'", - safe_str_client(onion_address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - case ENOENT: - /* We didn't have this; we should look it up. */ - refetch_desc = 1; - break; - default: - log_warn(LD_BUG, "Unknown cache lookup error %d", - rend_cache_lookup_result); - return -1; - } - } - - /* Help predict that we'll want to do hidden service circuits in the - * future. We're not sure if it will need a stable circuit yet, but - * we know we'll need *something*. */ - rep_hist_note_used_internal(now, 0, 1); - - /* Now we have a descriptor but is it usable or not? If not, refetch. - * Also, a fetch could have been requested if the onion address was not - * found in the cache previously. */ - if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { - connection_ap_mark_as_non_pending_circuit(conn); - base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; - log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(onion_address)); - rend_client_refetch_v2_renddesc(rend_data); - return 0; - } - - /* We have the descriptor! So launch a connection to the HS. */ - base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - log_info(LD_REND, "Descriptor is here. Great."); - - /* We'll try to attach it at the next event loop, or whenever - * we call connection_ap_attach_pending() */ - connection_ap_mark_as_pending_circuit(conn); - return 0; } return 0; /* unreached but keeps the compiler happy */ From 7aef3ec0fde0b320343ecb3aa7080b6e1d9a2e62 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 13:37:11 +0300 Subject: [PATCH 09/62] prop224: Add client-side HS descriptor cache. Signed-off-by: David Goulet --- src/or/hs_cache.c | 259 ++++++++++++++++++++++++++++++++++++++++- src/or/hs_cache.h | 25 ++++ src/or/hs_client.c | 39 +++++++ src/or/hs_client.h | 7 ++ src/or/hs_descriptor.c | 31 +++++ src/or/hs_descriptor.h | 1 + src/or/main.c | 2 +- 7 files changed, 357 insertions(+), 7 deletions(-) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 30215d8681..6f7bf1abd7 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -9,15 +9,19 @@ /* For unit tests.*/ #define HS_CACHE_PRIVATE -#include "hs_cache.h" - #include "or.h" #include "config.h" +#include "hs_ident.h" #include "hs_common.h" +#include "hs_client.h" #include "hs_descriptor.h" #include "networkstatus.h" #include "rendcache.h" +#include "hs_cache.h" + +/********************** Directory HS cache ******************/ + /* Directory descriptor cache. Map indexed by blinded key. */ static digest256map_t *hs_cache_v3_dir; @@ -98,7 +102,7 @@ cache_dir_desc_new(const char *desc) /* Return the size of a cache entry in bytes. */ static size_t -cache_get_entry_size(const hs_cache_dir_descriptor_t *entry) +cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry) { return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data) + strlen(entry->encoded_desc)); @@ -134,7 +138,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) * remove the entry we currently have from our cache so we can then * store the new one. */ remove_v3_desc_as_dir(cache_entry); - rend_cache_decrement_allocation(cache_get_entry_size(cache_entry)); + rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry)); cache_dir_desc_free(cache_entry); } /* Store the descriptor we just got. We are sure here that either we @@ -144,7 +148,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) /* Update our total cache size with this entry for the OOM. This uses the * old HS protocol cache subsystem for which we are tied with. */ - rend_cache_increment_allocation(cache_get_entry_size(desc)); + rend_cache_increment_allocation(cache_get_dir_entry_size(desc)); /* XXX: Update HS statistics. We should have specific stats for v3. */ @@ -221,7 +225,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) } /* Here, our entry has expired, remove and free. */ MAP_DEL_CURRENT(key); - entry_size = cache_get_entry_size(entry); + entry_size = cache_get_dir_entry_size(entry); bytes_removed += entry_size; /* Entry is not in the cache anymore, destroy it. */ cache_dir_desc_free(entry); @@ -315,6 +319,243 @@ hs_cache_clean_as_dir(time_t now) cache_clean_v3_as_dir(now, 0); } +/********************** Client-side HS cache ******************/ + +/* Client-side HS descriptor cache. Map indexed by service identity key. */ +static digest256map_t *hs_cache_v3_client; + +/* Remove a given descriptor from our cache. */ +static void +remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_remove(hs_cache_v3_client, desc->key.pubkey); +} + +/* Store a given descriptor in our cache. */ +static void +store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc); +} + +/* Query our cache and return the entry or NULL if not found. */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key) +{ + tor_assert(key); + return digest256map_get(hs_cache_v3_client, key); +} + +/* Return the size of a client cache entry in bytes. */ +static size_t +cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) +{ + return sizeof(*entry) + + strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); +} + +/* Parse the encoded descriptor in desc_str using + * service_identity_pk to decrypt it first. + * + * If everything goes well, allocate and return a new + * hs_cache_client_descriptor_t object. In case of error, return NULL. */ +static hs_cache_client_descriptor_t * +cache_client_desc_new(const char *desc_str, + const ed25519_public_key_t *service_identity_pk) +{ + hs_descriptor_t *desc = NULL; + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + + /* Decode the descriptor we just fetched. */ + if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) { + goto end; + } + tor_assert(desc); + + /* All is good: make a cache object for this descriptor */ + client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t)); + ed25519_pubkey_copy(&client_desc->key, service_identity_pk); + client_desc->created_ts = approx_time(); + client_desc->desc = desc; + client_desc->encoded_desc = tor_strdup(desc_str); + + end: + return client_desc; +} + +/** Free memory allocated by desc. */ +static void +cache_client_desc_free(hs_cache_client_descriptor_t *desc) +{ + if (desc == NULL) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->key, 0, sizeof(desc->key)); + memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc)); + tor_free(desc->encoded_desc); + tor_free(desc); +} + +/** Helper function: Use by the free all function to clear the client cache */ +static void +cache_client_desc_free_(void *ptr) +{ + hs_cache_client_descriptor_t *desc = ptr; + cache_client_desc_free(desc); +} + +/** Check whether client_desc is useful for us, and store it in the + * client-side HS cache if so. The client_desc is freed if we already have a + * fresher (higher revision counter count) in the cache. */ +static int +cache_store_as_client(hs_cache_client_descriptor_t *client_desc) +{ + hs_cache_client_descriptor_t *cache_entry; + + /* TODO: Heavy code duplication with cache_store_as_dir(). Consider + * refactoring and uniting! */ + + tor_assert(client_desc); + + /* Check if we already have a descriptor from this HS in cache. If we do, + * check if this descriptor is newer than the cached one */ + cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); + if (cache_entry != NULL) { + /* If we have an entry in our cache that has a revision counter greater + * than the one we just fetched, discard the one we fetched. */ + if (cache_entry->desc->plaintext_data.revision_counter > + client_desc->desc->plaintext_data.revision_counter) { + log_info(LD_REND, "We already have fresher descriptor. Ignoring."); + cache_client_desc_free(client_desc); + goto done; + } + /* Remove old entry. Make space for the new one! */ + remove_v3_desc_as_client(cache_entry); + rend_cache_decrement_allocation(cache_get_client_entry_size(cache_entry)); + cache_client_desc_free(cache_entry); + } + + /* Store descriptor in cache */ + store_v3_desc_as_client(client_desc); + + /* Update cache size with this entry for the OOM handler. */ + rend_cache_increment_allocation(cache_get_client_entry_size(client_desc)); + + done: + return 0; +} + +/* Clean the client cache using now as the current time. Return the total size + * of removed bytes from the cache. */ +static size_t +cache_clean_v3_as_client(time_t now) +{ + size_t bytes_removed = 0; + + if (!hs_cache_v3_client) { /* No cache to clean. Just return. */ + return 0; + } + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size; + time_t cutoff = now - rend_cache_max_entry_lifetime(); + + /* If the entry has been created _after_ the cutoff, not expired so + * continue to the next entry in our v3 cache. */ + if (entry->created_ts > cutoff) { + continue; + } + /* Here, our entry has expired, remove and free. */ + MAP_DEL_CURRENT(key); + entry_size = cache_get_client_entry_size(entry); + bytes_removed += entry_size; + /* Entry is not in the cache anymore, destroy it. */ + cache_client_desc_free(entry); + /* Update our cache entry allocation size for the OOM. */ + rend_cache_decrement_allocation(entry_size); + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + base64_encode(key_b64, sizeof(key_b64), (const char *) key, + DIGEST256_LEN, 0); + log_info(LD_REND, "Removing hidden service v3 descriptor '%s' " + "from client cache", + safe_str_client(key_b64)); + } + } DIGEST256MAP_FOREACH_END; + + return bytes_removed; +} + +/** Public API: Given the HS ed25519 identity public key in key, return + * its HS descriptor if it's stored in our cache, or NULL if not. */ +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (cached_desc) { + tor_assert(cached_desc->desc); + return cached_desc->desc; + } + + return NULL; +} + +/** Public API: Given an encoded descriptor, store it in the client HS + * cache. Return -1 on error, 0 on success .*/ +int +hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk) +{ + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(identity_pk); + + /* Create client cache descriptor object */ + client_desc = cache_client_desc_new(desc_str, identity_pk); + if (!client_desc) { + log_warn(LD_GENERAL, "Failed to parse received descriptor %s.", + escaped(desc_str)); + goto err; + } + + /* Push it to the cache */ + if (cache_store_as_client(client_desc) < 0) { + goto err; + } + + return 0; + + err: + cache_client_desc_free(client_desc); + return -1; +} + +/* Clean all client caches using the current time now. */ +void +hs_cache_clean_as_client(time_t now) +{ + /* Start with v2 cache cleaning. */ + rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); + /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function + * to compute the cutoff by itself using the lifetime value. */ + cache_clean_v3_as_client(now); +} + +/**************** Generics *********************************/ + /* Do a round of OOM cleanup on all directory caches. Return the amount of * removed bytes. It is possible that the returned value is lower than * min_remove_bytes if the caches get emptied out so the caller should be @@ -388,6 +629,9 @@ hs_cache_init(void) /* Calling this twice is very wrong code flow. */ tor_assert(!hs_cache_v3_dir); hs_cache_v3_dir = digest256map_new(); + + tor_assert(!hs_cache_v3_client); + hs_cache_v3_client = digest256map_new(); } /* Cleanup the hidden service cache subsystem. */ @@ -396,5 +640,8 @@ hs_cache_free_all(void) { digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_); hs_cache_v3_dir = NULL; + + digest256map_free(hs_cache_v3_client, cache_client_desc_free_); + hs_cache_v3_client = NULL; } diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index ed00424234..79456f69c8 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -53,10 +53,35 @@ int hs_cache_store_as_dir(const char *desc); int hs_cache_lookup_as_dir(uint32_t version, const char *query, const char **desc_out); +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key); +int hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk); +void hs_cache_clean_as_client(time_t now); + #ifdef HS_CACHE_PRIVATE +/** Represents a locally cached HS descriptor on a hidden service client. */ +typedef struct hs_cache_client_descriptor_t { + /* This object is indexed using the service identity public key */ + ed25519_public_key_t key; + + /* When was this entry created. Used to expire entries. */ + time_t created_ts; + + /* The cached descriptor, this object is the owner. It can't be NULL. A + * cache object without a valid descriptor is not possible. */ + hs_descriptor_t *desc; + + /* Encoded descriptor in string form. Can't be NULL. */ + char *encoded_desc; +} hs_cache_client_descriptor_t; + STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff); +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key); + #endif /* HS_CACHE_PRIVATE */ #endif /* TOR_HS_CACHE_H */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 051490aaaf..37981c8b65 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -46,3 +46,42 @@ hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) } } +/* With the given encoded descriptor in desc_str and the service key in + * service_identity_pk, decode the descriptor and set the desc pointer with a + * newly allocated descriptor object. + * + * Return 0 on success else a negative value and desc is set to NULL. */ +int +hs_client_decode_descriptor(const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc) +{ + int ret; + uint8_t subcredential[DIGEST256_LEN]; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + tor_assert(desc); + + /* Create subcredential for this HS so that we can decrypt */ + { + ed25519_public_key_t blinded_pubkey; + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, + &blinded_pubkey); + hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); + } + + /* Parse descriptor */ + ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); + memwipe(subcredential, 0, sizeof(subcredential)); + if (ret < 0) { + log_warn(LD_GENERAL, "Could not parse received descriptor as client"); + goto err; + } + + return 0; + err: + return -1; +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 4f28937b03..348724ab3e 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -9,8 +9,15 @@ #ifndef TOR_HS_CLIENT_H #define TOR_HS_CLIENT_H +#include "hs_descriptor.h" + void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); +int hs_client_decode_descriptor( + const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc); + #endif /* TOR_HS_CLIENT_H */ diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 3cdc023f79..1e595f3a3d 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -2437,6 +2437,37 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } +/* Return the size in bytes of the given encrypted data object. Used by OOM + * subsystem. */ +static size_t +hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) +{ + tor_assert(data); + size_t intro_size = 0; + if (data->intro_auth_types) { + intro_size += + smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types); + } + if (data->intro_points) { + /* XXX could follow pointers here and get more accurate size */ + intro_size += + smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t); + } + + return sizeof(*data) + intro_size; +} + +/* Return the size in bytes of the given descriptor object. Used by OOM + * subsystem. */ + size_t +hs_desc_obj_size(const hs_descriptor_t *data) +{ + tor_assert(data); + return (hs_desc_plaintext_obj_size(&data->plaintext_data) + + hs_desc_encrypted_obj_size(&data->encrypted_data) + + sizeof(data->subcredential)); +} + /* Return a newly allocated descriptor intro point. */ hs_desc_intro_point_t * hs_desc_intro_point_new(void) diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 19e52333be..256a04dd6e 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -224,6 +224,7 @@ int hs_desc_decode_plaintext(const char *encoded, int hs_desc_decode_encrypted(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_out); +size_t hs_desc_obj_size(const hs_descriptor_t *data); 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); diff --git a/src/or/main.c b/src/or/main.c index 86fdb93282..3db0b5e901 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1827,8 +1827,8 @@ clean_caches_callback(time_t now, const or_options_t *options) { /* Remove old information from rephist and the rend cache. */ rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); + hs_cache_clean_as_client(now); hs_cache_clean_as_dir(now); microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) From 912c11761c5b5b0a80e232bdc96788677300ca67 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 13:48:03 +0300 Subject: [PATCH 10/62] prop224: Move some rendclient.c code to hs_common.c Specifically move the pick_hsdir() function and all the HSDir request tracking code. We plan to use all that code both for v2 and v3. This commit only moves code. Signed-off-by: David Goulet --- src/or/hs_common.c | 224 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 11 +++ src/or/rendclient.c | 224 -------------------------------------------- src/or/rendclient.h | 1 - 4 files changed, 235 insertions(+), 225 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 0529c0cdb0..48d5f78bbb 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -22,6 +22,7 @@ #include "rendcommon.h" #include "rendservice.h" #include "router.h" +#include "routerset.h" #include "shared_random.h" #include "shared_random_state.h" @@ -1320,6 +1321,229 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, smartlist_free(sorted_nodes); } +/*********************** HSDir request tracking ***************************/ + +/** Return the period for which a hidden service directory cannot be queried + * for the same descriptor ID again, taking TestingTorNetwork into account. */ +static time_t +hsdir_requery_period(const or_options_t *options) +{ + tor_assert(options); + + if (options->TestingTorNetwork) { + return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; + } else { + return REND_HID_SERV_DIR_REQUERY_PERIOD; + } +} + +/** Contains the last request times to hidden service directories for + * certain queries; each key is a string consisting of the + * concatenation of a base32-encoded HS directory identity digest and + * base32-encoded HS descriptor ID; each value is a pointer to a time_t + * holding the time of the last request for that descriptor ID to that + * HS directory. */ +static strmap_t *last_hid_serv_requests_ = NULL; + +/** Returns last_hid_serv_requests_, initializing it to a new strmap if + * necessary. */ +static strmap_t * +get_last_hid_serv_requests(void) +{ + if (!last_hid_serv_requests_) + last_hid_serv_requests_ = strmap_new(); + return last_hid_serv_requests_; +} + +#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ + REND_DESC_ID_V2_LEN_BASE32) + +/** Look up the last request time to hidden service directory hs_dir + * for descriptor ID desc_id_base32. If set is non-zero, + * assign the current time now and return that. Otherwise, return the + * most recent request time, or 0 if no such request has been sent before. + */ +static time_t +lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *desc_id_base32, + time_t now, int set) +{ + char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; + time_t *last_request_ptr; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), + hs_dir->identity_digest, DIGEST_LEN); + tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", + hsdir_id_base32, + desc_id_base32); + /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == + LAST_HID_SERV_REQUEST_KEY_LEN); */ + if (set) { + time_t *oldptr; + last_request_ptr = tor_malloc_zero(sizeof(time_t)); + *last_request_ptr = now; + oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, + last_request_ptr); + tor_free(oldptr); + } else + last_request_ptr = strmap_get_lc(last_hid_serv_requests, + hsdir_desc_comb_id); + return (last_request_ptr) ? *last_request_ptr : 0; +} + +/** Clean the history of request times to hidden service directories, so that + * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD + * seconds any more. */ +static void +directory_clean_last_hid_serv_requests(time_t now) +{ + strmap_iter_t *iter; + time_t cutoff = now - hsdir_requery_period(get_options()); + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + time_t *ent; + strmap_iter_get(iter, &key, &val); + ent = (time_t *) val; + if (*ent < cutoff) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(ent); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Remove all requests related to the descriptor ID desc_id from the + * history of times of requests to hidden service directories. + * desc_id is an unencoded descriptor ID of size DIGEST_LEN. + * + * This is called from rend_client_note_connection_attempt_ended(), which + * must be idempotent, so any future changes to this function must leave it + * idempotent too. */ +void +purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) +{ + strmap_iter_t *iter; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + + /* Key is stored with the base32 encoded desc_id. */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + strmap_iter_get(iter, &key, &val); + /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ + if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - + REND_DESC_ID_V2_LEN_BASE32, + desc_id_base32, + REND_DESC_ID_V2_LEN_BASE32)) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(val); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Purge the history of request times to hidden service directories, + * so that future lookups of an HS descriptor will not fail because we + * accessed all of the HSDir relays responsible for the descriptor + * recently. */ +void +rend_client_purge_last_hid_serv_requests(void) +{ + /* Don't create the table if it doesn't exist yet (and it may very + * well not exist if the user hasn't accessed any HSes)... */ + strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; + /* ... and let get_last_hid_serv_requests re-create it for us if + * necessary. */ + last_hid_serv_requests_ = NULL; + + if (old_last_hid_serv_requests != NULL) { + log_info(LD_REND, "Purging client last-HS-desc-request-time table"); + strmap_free(old_last_hid_serv_requests, tor_free_); + } +} + +/***********************************************************************/ + +/** This returns a good valid hs dir that should be used for the given + * descriptor id. + * + * Return NULL on error else the hsdir node pointer. */ +routerstatus_t * +pick_hsdir(const char *desc_id, const char *desc_id_base32) +{ + smartlist_t *responsible_dirs = smartlist_new(); + smartlist_t *usable_responsible_dirs = smartlist_new(); + const or_options_t *options = get_options(); + routerstatus_t *hs_dir; + time_t now = time(NULL); + int excluded_some; + + tor_assert(desc_id); + tor_assert(desc_id_base32); + + /* Determine responsible dirs. Even if we can't get all we want, work with + * the ones we have. If it's empty, we'll notice below. */ + hid_serv_get_responsible_directories(responsible_dirs, desc_id); + + /* Clean request history first. */ + directory_clean_last_hid_serv_requests(now); + + /* Only select those hidden service directories to which we did not send a + * request recently and for which we have a router descriptor here. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { + time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, + 0, 0); + const node_t *node = node_get_by_id(dir->identity_digest); + if (last + hsdir_requery_period(options) >= now || + !node || !node_has_descriptor(node)) { + SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + continue; + } + if (!routerset_contains_node(options->ExcludeNodes, node)) { + smartlist_add(usable_responsible_dirs, dir); + } + } SMARTLIST_FOREACH_END(dir); + + excluded_some = + smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); + + hs_dir = smartlist_choose(usable_responsible_dirs); + if (!hs_dir && !options->StrictNodes) { + hs_dir = smartlist_choose(responsible_dirs); + } + + smartlist_free(responsible_dirs); + smartlist_free(usable_responsible_dirs); + if (!hs_dir) { + log_info(LD_REND, "Could not pick one of the responsible hidden " + "service directories, because we requested them all " + "recently without success."); + if (options->StrictNodes && excluded_some) { + log_warn(LD_REND, "Could not pick a hidden service directory for the " + "requested hidden service: they are all either down or " + "excluded, and StrictNodes is set."); + } + } else { + /* Remember that we are requesting a descriptor from this hidden service + * directory now. */ + lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); + } + + return hs_dir; +} + +/***********************************************************************/ + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index fd2a1f4e32..aa810c0db8 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -187,6 +187,10 @@ 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 rend_client_purge_last_hid_serv_requests(void); +void purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); +routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); + void hs_get_subcredential(const ed25519_public_key_t *identity_pk, const ed25519_public_key_t *blinded_pk, uint8_t *subcred_out); @@ -229,6 +233,13 @@ void hs_dec_rdv_stream_counter(origin_circuit_t *circ); STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); +/** The period for which a hidden service directory cannot be queried for + * the same descriptor ID again. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) +/** Test networks generate a new consensus every 5 or 10 seconds. + * So allow them to requery HSDirs much faster. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) + #ifdef TOR_UNIT_TESTS STATIC uint64_t get_time_period_length(void); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index fbfba27a23..28e02a65e7 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -465,230 +465,6 @@ rend_client_introduction_acked(origin_circuit_t *circ, return 0; } -/** The period for which a hidden service directory cannot be queried for - * the same descriptor ID again. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) -/** Test networks generate a new consensus every 5 or 10 seconds. - * So allow them to requery HSDirs much faster. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) - -/** Return the period for which a hidden service directory cannot be queried - * for the same descriptor ID again, taking TestingTorNetwork into account. */ -static time_t -hsdir_requery_period(const or_options_t *options) -{ - tor_assert(options); - - if (options->TestingTorNetwork) { - return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; - } else { - return REND_HID_SERV_DIR_REQUERY_PERIOD; - } -} - -/** Contains the last request times to hidden service directories for - * certain queries; each key is a string consisting of the - * concatenation of a base32-encoded HS directory identity digest and - * base32-encoded HS descriptor ID; each value is a pointer to a time_t - * holding the time of the last request for that descriptor ID to that - * HS directory. */ -static strmap_t *last_hid_serv_requests_ = NULL; - -/** Returns last_hid_serv_requests_, initializing it to a new strmap if - * necessary. */ -static strmap_t * -get_last_hid_serv_requests(void) -{ - if (!last_hid_serv_requests_) - last_hid_serv_requests_ = strmap_new(); - return last_hid_serv_requests_; -} - -#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ - REND_DESC_ID_V2_LEN_BASE32) - -/** Look up the last request time to hidden service directory hs_dir - * for descriptor ID desc_id_base32. If set is non-zero, - * assign the current time now and return that. Otherwise, return the - * most recent request time, or 0 if no such request has been sent before. - */ -static time_t -lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, - time_t now, int set) -{ - char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; - time_t *last_request_ptr; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), - hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, - desc_id_base32); - /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == - LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (set) { - time_t *oldptr; - last_request_ptr = tor_malloc_zero(sizeof(time_t)); - *last_request_ptr = now; - oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, - last_request_ptr); - tor_free(oldptr); - } else - last_request_ptr = strmap_get_lc(last_hid_serv_requests, - hsdir_desc_comb_id); - return (last_request_ptr) ? *last_request_ptr : 0; -} - -/** Clean the history of request times to hidden service directories, so that - * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD - * seconds any more. */ -static void -directory_clean_last_hid_serv_requests(time_t now) -{ - strmap_iter_t *iter; - time_t cutoff = now - hsdir_requery_period(get_options()); - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - time_t *ent; - strmap_iter_get(iter, &key, &val); - ent = (time_t *) val; - if (*ent < cutoff) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(ent); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Remove all requests related to the descriptor ID desc_id from the - * history of times of requests to hidden service directories. - * desc_id is an unencoded descriptor ID of size DIGEST_LEN. - * - * This is called from rend_client_note_connection_attempt_ended(), which - * must be idempotent, so any future changes to this function must leave it - * idempotent too. */ -static void -purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) -{ - strmap_iter_t *iter; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - - /* Key is stored with the base32 encoded desc_id. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - strmap_iter_get(iter, &key, &val); - /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - - REND_DESC_ID_V2_LEN_BASE32, - desc_id_base32, - REND_DESC_ID_V2_LEN_BASE32)) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(val); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Purge the history of request times to hidden service directories, - * so that future lookups of an HS descriptor will not fail because we - * accessed all of the HSDir relays responsible for the descriptor - * recently. */ -void -rend_client_purge_last_hid_serv_requests(void) -{ - /* Don't create the table if it doesn't exist yet (and it may very - * well not exist if the user hasn't accessed any HSes)... */ - strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; - /* ... and let get_last_hid_serv_requests re-create it for us if - * necessary. */ - last_hid_serv_requests_ = NULL; - - if (old_last_hid_serv_requests != NULL) { - log_info(LD_REND, "Purging client last-HS-desc-request-time table"); - strmap_free(old_last_hid_serv_requests, tor_free_); - } -} - -/** This returns a good valid hs dir that should be used for the given - * descriptor id. - * - * Return NULL on error else the hsdir node pointer. */ -static routerstatus_t * -pick_hsdir(const char *desc_id, const char *desc_id_base32) -{ - smartlist_t *responsible_dirs = smartlist_new(); - smartlist_t *usable_responsible_dirs = smartlist_new(); - const or_options_t *options = get_options(); - routerstatus_t *hs_dir; - time_t now = time(NULL); - int excluded_some; - - tor_assert(desc_id); - tor_assert(desc_id_base32); - - /* Determine responsible dirs. Even if we can't get all we want, work with - * the ones we have. If it's empty, we'll notice below. */ - hid_serv_get_responsible_directories(responsible_dirs, desc_id); - - /* Clean request history first. */ - directory_clean_last_hid_serv_requests(now); - - /* Only select those hidden service directories to which we did not send a - * request recently and for which we have a router descriptor here. */ - SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); - const node_t *node = node_get_by_id(dir->identity_digest); - if (last + hsdir_requery_period(options) >= now || - !node || !node_has_descriptor(node)) { - SMARTLIST_DEL_CURRENT(responsible_dirs, dir); - continue; - } - if (!routerset_contains_node(options->ExcludeNodes, node)) { - smartlist_add(usable_responsible_dirs, dir); - } - } SMARTLIST_FOREACH_END(dir); - - excluded_some = - smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); - - hs_dir = smartlist_choose(usable_responsible_dirs); - if (!hs_dir && !options->StrictNodes) { - hs_dir = smartlist_choose(responsible_dirs); - } - - smartlist_free(responsible_dirs); - smartlist_free(usable_responsible_dirs); - if (!hs_dir) { - log_info(LD_REND, "Could not pick one of the responsible hidden " - "service directories, because we requested them all " - "recently without success."); - if (options->StrictNodes && excluded_some) { - log_warn(LD_REND, "Could not pick a hidden service directory for the " - "requested hidden service: they are all either down or " - "excluded, and StrictNodes is set."); - } - } else { - /* Remember that we are requesting a descriptor from this hidden service - * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); - } - - return hs_dir; -} - /** Determine the responsible hidden service directories for desc_id * and fetch the descriptor with that ID from one of them. Only * send a request to a hidden service directory that we have not yet tried diff --git a/src/or/rendclient.h b/src/or/rendclient.h index ff0f4084fd..375bc973b7 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -24,7 +24,6 @@ int rend_client_introduction_acked(origin_circuit_t *circ, void rend_client_refetch_v2_renddesc(rend_data_t *rend_query); int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs); void rend_client_cancel_descriptor_fetches(void); -void rend_client_purge_last_hid_serv_requests(void); int rend_client_report_intro_point_failure(extend_info_t *failed_intro, rend_data_t *rend_data, From 15c9b7e891dea655476dc77a07cef8824402fb00 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 13:56:43 +0300 Subject: [PATCH 11/62] prop224: Fix hidserv request code to work for both v2 and v3. See documentation of `last_hid_serv_requests_` for how it works. strmaps are cool! Signed-off-by: David Goulet --- src/or/hs_common.c | 121 +++++++++++++++++++++++++------------------- src/or/hs_common.h | 11 +++- src/or/rendclient.c | 8 +-- 3 files changed, 82 insertions(+), 58 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 48d5f78bbb..dbd3848332 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1325,8 +1325,8 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, /** Return the period for which a hidden service directory cannot be queried * for the same descriptor ID again, taking TestingTorNetwork into account. */ -static time_t -hsdir_requery_period(const or_options_t *options) +time_t +hs_hsdir_requery_period(const or_options_t *options) { tor_assert(options); @@ -1337,17 +1337,25 @@ hsdir_requery_period(const or_options_t *options) } } -/** Contains the last request times to hidden service directories for - * certain queries; each key is a string consisting of the - * concatenation of a base32-encoded HS directory identity digest and - * base32-encoded HS descriptor ID; each value is a pointer to a time_t - * holding the time of the last request for that descriptor ID to that - * HS directory. */ +/** Tracks requests for fetching hidden service descriptors. It's used by + * hidden service clients, to avoid querying HSDirs that have already failed + * giving back a descriptor. The same data structure is used to track both v2 + * and v3 HS descriptor requests. + * + * The string map is a key/value store that contains the last request times to + * hidden service directories for certain queries. Specifically: + * + * key = base32(hsdir_identity) + base32(hs_identity) + * value = time_t of last request for that hs_identity to that HSDir + * + * where 'hsdir_identity' is the identity digest of the HSDir node, and + * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519 + * identity public key of the HS in the v3 case. */ static strmap_t *last_hid_serv_requests_ = NULL; /** Returns last_hid_serv_requests_, initializing it to a new strmap if * necessary. */ -static strmap_t * +STATIC strmap_t * get_last_hid_serv_requests(void) { if (!last_hid_serv_requests_) @@ -1355,30 +1363,26 @@ get_last_hid_serv_requests(void) return last_hid_serv_requests_; } -#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ - REND_DESC_ID_V2_LEN_BASE32) - /** Look up the last request time to hidden service directory hs_dir - * for descriptor ID desc_id_base32. If set is non-zero, - * assign the current time now and return that. Otherwise, return the - * most recent request time, or 0 if no such request has been sent before. - */ -static time_t -lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, - time_t now, int set) + * for descriptor request key req_key_str which is the descriptor ID + * for a v2 service or the blinded key for v3. If set is non-zero, + * assign the current time now and return that. Otherwise, return the + * most recent request time, or 0 if no such request has been sent before. */ +time_t +hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *req_key_str, + time_t now, int set) { char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; + char *hsdir_desc_comb_id = NULL; time_t *last_request_ptr; strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + /* Create the key */ base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, - desc_id_base32); - /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == - LAST_HID_SERV_REQUEST_KEY_LEN); */ + tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str); + if (set) { time_t *oldptr; last_request_ptr = tor_malloc_zero(sizeof(time_t)); @@ -1386,20 +1390,23 @@ lookup_last_hid_serv_request(routerstatus_t *hs_dir, oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, last_request_ptr); tor_free(oldptr); - } else - last_request_ptr = strmap_get_lc(last_hid_serv_requests, - hsdir_desc_comb_id); + } else { + last_request_ptr = strmap_get(last_hid_serv_requests, + hsdir_desc_comb_id); + } + + tor_free(hsdir_desc_comb_id); return (last_request_ptr) ? *last_request_ptr : 0; } /** Clean the history of request times to hidden service directories, so that * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD * seconds any more. */ -static void -directory_clean_last_hid_serv_requests(time_t now) +void +hs_clean_last_hid_serv_requests(time_t now) { strmap_iter_t *iter; - time_t cutoff = now - hsdir_requery_period(get_options()); + time_t cutoff = now - hs_hsdir_requery_period(get_options()); strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); for (iter = strmap_iter_init(last_hid_serv_requests); !strmap_iter_done(iter); ) { @@ -1417,33 +1424,43 @@ directory_clean_last_hid_serv_requests(time_t now) } } -/** Remove all requests related to the descriptor ID desc_id from the - * history of times of requests to hidden service directories. - * desc_id is an unencoded descriptor ID of size DIGEST_LEN. +/** Remove all requests related to the descriptor request key string + * req_key_str from the history of times of requests to hidden service + * directories. * * This is called from rend_client_note_connection_attempt_ended(), which * must be idempotent, so any future changes to this function must leave it * idempotent too. */ void -purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) +hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str) { strmap_iter_t *iter; strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - /* Key is stored with the base32 encoded desc_id. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); for (iter = strmap_iter_init(last_hid_serv_requests); !strmap_iter_done(iter); ) { const char *key; void *val; strmap_iter_get(iter, &key, &val); - /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - - REND_DESC_ID_V2_LEN_BASE32, - desc_id_base32, - REND_DESC_ID_V2_LEN_BASE32)) { + + /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of + * semantic, see #23305. */ + + /* Length check on the strings we are about to compare. The "key" contains + * both the base32 HSDir identity digest and the requested key at the + * directory. The "req_key_str" can either be a base32 descriptor ID or a + * base64 blinded key which should be the second part of "key". BUG on + * this check because both strings are internally controlled so this + * should never happen. */ + if (BUG((strlen(req_key_str) + REND_DESC_ID_V2_LEN_BASE32) < + strlen(key))) { + iter = strmap_iter_next(last_hid_serv_requests, iter); + continue; + } + + /* Check if the tracked request matches our request key */ + if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str, + strlen(req_key_str))) { iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); tor_free(val); } else { @@ -1457,9 +1474,9 @@ purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) * accessed all of the HSDir relays responsible for the descriptor * recently. */ void -rend_client_purge_last_hid_serv_requests(void) +hs_purge_last_hid_serv_requests(void) { - /* Don't create the table if it doesn't exist yet (and it may very + /* Don't create the table if it doesn't exist yet (and it may very * well not exist if the user hasn't accessed any HSes)... */ strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; /* ... and let get_last_hid_serv_requests re-create it for us if @@ -1496,15 +1513,15 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32) hid_serv_get_responsible_directories(responsible_dirs, desc_id); /* Clean request history first. */ - directory_clean_last_hid_serv_requests(now); + hs_clean_last_hid_serv_requests(now); /* Only select those hidden service directories to which we did not send a * request recently and for which we have a router descriptor here. */ SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); + time_t last = hs_lookup_last_hid_serv_request(dir, desc_id_base32, + 0, 0); const node_t *node = node_get_by_id(dir->identity_digest); - if (last + hsdir_requery_period(options) >= now || + if (last + hs_hsdir_requery_period(options) >= now || !node || !node_has_descriptor(node)) { SMARTLIST_DEL_CURRENT(responsible_dirs, dir); continue; @@ -1536,7 +1553,7 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32) } else { /* Remember that we are requesting a descriptor from this hidden service * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); + hs_lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); } return hs_dir; diff --git a/src/or/hs_common.h b/src/or/hs_common.h index aa810c0db8..4692a3a019 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -187,8 +187,6 @@ 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 rend_client_purge_last_hid_serv_requests(void); -void purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); void hs_get_subcredential(const ed25519_public_key_t *identity_pk, @@ -224,6 +222,14 @@ 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); +time_t hs_hsdir_requery_period(const or_options_t *options); +time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *desc_id_base32, + time_t now, int set); +void hs_clean_last_hid_serv_requests(time_t now); +void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); +void hs_purge_last_hid_serv_requests(void); + int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); void hs_inc_rdv_stream_counter(origin_circuit_t *circ); @@ -242,6 +248,7 @@ STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); #ifdef TOR_UNIT_TESTS +STATIC strmap_t *get_last_hid_serv_requests(void); STATIC uint64_t get_time_period_length(void); STATIC uint8_t *get_first_cached_disaster_srv(void); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 28e02a65e7..a621d27f9a 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -42,7 +42,7 @@ rend_client_purge_state(void) rend_cache_purge(); rend_cache_failure_purge(); rend_client_cancel_descriptor_fetches(); - rend_client_purge_last_hid_serv_requests(); + hs_purge_last_hid_serv_requests(); } /** Called when we've established a circuit to an introduction point: @@ -636,7 +636,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs) sizeof(descriptor_id)) != 0) { /* Not equal from what we currently have so purge the last hid serv * request cache and update the descriptor ID with the new value. */ - purge_hid_serv_from_last_hid_serv_requests( + hs_purge_hid_serv_from_last_hid_serv_requests( rend_data->descriptor_id[chosen_replica]); memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, sizeof(rend_data->descriptor_id[chosen_replica])); @@ -1036,14 +1036,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id); replica++) { const char *desc_id = rend_data_v2->descriptor_id[replica]; - purge_hid_serv_from_last_hid_serv_requests(desc_id); + hs_purge_hid_serv_from_last_hid_serv_requests(desc_id); } log_info(LD_REND, "Connection attempt for %s has ended; " "cleaning up temporary state.", safe_str_client(onion_address)); } else { /* We only have an ID for a fetch. Probably used by HSFETCH. */ - purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); + hs_purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); } } From 5c9cd912eece244f90ecf31712722dc6b993f6da Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:01:48 +0300 Subject: [PATCH 12/62] prop224: Refactor rendclient.c to use the new hsdir_req code. - Also add tests for the hidserv_req subsystem. - Introduce purge_v2_hidserv_req() wrapper to simplify v2 code. Signed-off-by: David Goulet --- src/or/rendclient.c | 21 ++++++-- src/test/test_hs_common.c | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index a621d27f9a..9cf4059469 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -562,6 +562,20 @@ directory_get_from_hs_dir(const char *desc_id, return 1; } +/** Remove tracked HSDir requests from our history for this hidden service + * descriptor desc_id (of size DIGEST_LEN) */ +static void +purge_v2_hidserv_req(const char *desc_id) +{ + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + + /* The hsdir request tracker stores v2 keys using the base32 encoded + desc_id. Do it: */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32); +} + /** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are * given, they will be used instead. * @@ -636,8 +650,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs) sizeof(descriptor_id)) != 0) { /* Not equal from what we currently have so purge the last hid serv * request cache and update the descriptor ID with the new value. */ - hs_purge_hid_serv_from_last_hid_serv_requests( - rend_data->descriptor_id[chosen_replica]); + purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]); memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, sizeof(rend_data->descriptor_id[chosen_replica])); } @@ -1036,14 +1049,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id); replica++) { const char *desc_id = rend_data_v2->descriptor_id[replica]; - hs_purge_hid_serv_from_last_hid_serv_requests(desc_id); + purge_v2_hidserv_req(desc_id); } log_info(LD_REND, "Connection attempt for %s has ended; " "cleaning up temporary state.", safe_str_client(onion_address)); } else { /* We only have an ID for a fetch. Probably used by HSFETCH. */ - hs_purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); + purge_v2_hidserv_req(rend_data_v2->desc_id_fetch); } } diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index f7e8e2811f..b63cb7c946 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -697,6 +697,104 @@ test_disaster_srv(void *arg) ; } +/** Test our HS descriptor request tracker by making various requests and + * checking whether they get tracked properly. */ +static void +test_hid_serv_request_tracker(void *arg) +{ + (void) arg; + time_t retval; + routerstatus_t *hsdir = NULL, *hsdir2 = NULL; + time_t now = approx_time(); + + const char *req_key_str_first = + "vd4zb6zesaubtrjvdqcr2w7x7lhw2up4Xnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs"; + const char *req_key_str_second = + "g53o7iavcd62oihswhr24u6czmqws5kpXnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs"; + + /*************************** basic test *******************************/ + + /* Get request tracker and make sure it's empty */ + strmap_t *request_tracker = get_last_hid_serv_requests(); + tt_int_op(strmap_size(request_tracker),OP_EQ, 0); + + /* Let's register a hid serv request */ + hsdir = tor_malloc_zero(sizeof(routerstatus_t)); + memset(hsdir->identity_digest, 'Z', DIGEST_LEN); + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now, 1); + tt_int_op(retval, OP_EQ, now); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /* Let's lookup a non-existent hidserv request */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_second, + now+1, 0); + tt_int_op(retval, OP_EQ, 0); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /* Let's lookup a real hidserv request */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now+2, 0); + tt_int_op(retval, OP_EQ, now); /* we got it */ + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /**********************************************************************/ + + /* Let's add another request for the same HS but on a different HSDir. */ + hsdir2 = tor_malloc_zero(sizeof(routerstatus_t)); + memset(hsdir->identity_digest, 2, DIGEST_LEN); + retval = hs_lookup_last_hid_serv_request(hsdir2, req_key_str_first, + now+3, 1); + tt_int_op(retval, OP_EQ, now+3); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + + /* Check that we can clean the first request based on time */ + hs_clean_last_hid_serv_requests(now+3+REND_HID_SERV_DIR_REQUERY_PERIOD); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + /* Check that it doesn't exist anymore */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now+2, 0); + tt_int_op(retval, OP_EQ, 0); + + /*************************** deleting entries **************************/ + + /* Add another request with very short key */ + retval = hs_lookup_last_hid_serv_request(hsdir, "l", now, 1); + + /* Try deleting entries with a dummy key. Check that our previous requests + * are still there */ + tor_capture_bugs_(1); + hs_purge_hid_serv_from_last_hid_serv_requests("a"); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + tor_end_capture_bugs_(); + + /* Try another dummy key. Check that requests are still there */ + { + char dummy[2000]; + memset(dummy, 'Z', 2000); + dummy[1999] = '\x00'; + hs_purge_hid_serv_from_last_hid_serv_requests(dummy); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + } + + /* Another dummy key! */ + hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_second); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + + /* Now actually delete a request! */ + hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_first); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /* Purge it all! */ + hs_purge_last_hid_serv_requests(); + request_tracker = get_last_hid_serv_requests(); + tt_int_op(strmap_size(request_tracker),OP_EQ, 0); + + done: + tor_free(hsdir); + tor_free(hsdir2); +} + static void test_parse_extended_hostname(void *arg) { @@ -748,6 +846,8 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL }, + { "hid_serv_request_tracker", test_hid_serv_request_tracker, TT_FORK, + NULL, NULL }, { "parse_extended_hostname", test_parse_extended_hostname, TT_FORK, NULL, NULL }, From 0f6633abb29743d920c27e8d7888dba4d83217b5 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:07:53 +0300 Subject: [PATCH 13/62] prop224: Refactor pick_hsdir() to be used by both v2 and v3. Also refactor rendclient.c to use the new hs_pick_hdsir() func. Signed-off-by: David Goulet --- src/or/hs_common.c | 29 ++++++++++++++--------------- src/or/hs_common.h | 2 ++ src/or/rendclient.c | 7 ++++++- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index dbd3848332..bc44265d53 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -21,6 +21,7 @@ #include "hs_service.h" #include "rendcommon.h" #include "rendservice.h" +#include "routerset.h" #include "router.h" #include "routerset.h" #include "shared_random.h" @@ -1491,35 +1492,33 @@ hs_purge_last_hid_serv_requests(void) /***********************************************************************/ -/** This returns a good valid hs dir that should be used for the given - * descriptor id. +/** Given the list of responsible HSDirs in responsible_dirs, pick the + * one that we should use to fetch a descriptor right now. Take into account + * previous failed attempts at fetching this descriptor from HSDirs using the + * string identifier req_key_str. * - * Return NULL on error else the hsdir node pointer. */ + * Steals ownership of responsible_dirs. + * + * Return the routerstatus of the chosen HSDir if successful, otherwise return + * NULL if no HSDirs are worth trying right now. */ routerstatus_t * -pick_hsdir(const char *desc_id, const char *desc_id_base32) +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) { - smartlist_t *responsible_dirs = smartlist_new(); smartlist_t *usable_responsible_dirs = smartlist_new(); const or_options_t *options = get_options(); routerstatus_t *hs_dir; time_t now = time(NULL); int excluded_some; - tor_assert(desc_id); - tor_assert(desc_id_base32); + tor_assert(req_key_str); - /* Determine responsible dirs. Even if we can't get all we want, work with - * the ones we have. If it's empty, we'll notice below. */ - hid_serv_get_responsible_directories(responsible_dirs, desc_id); - - /* Clean request history first. */ + /* Clean outdated request history first. */ hs_clean_last_hid_serv_requests(now); /* Only select those hidden service directories to which we did not send a * request recently and for which we have a router descriptor here. */ SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = hs_lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); + time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0); const node_t *node = node_get_by_id(dir->identity_digest); if (last + hs_hsdir_requery_period(options) >= now || !node || !node_has_descriptor(node)) { @@ -1553,7 +1552,7 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32) } else { /* Remember that we are requesting a descriptor from this hidden service * directory now. */ - hs_lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); + hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); } return hs_dir; diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 4692a3a019..6ad68d0a8a 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -221,6 +221,8 @@ 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); +routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, + const char *req_key_str); time_t hs_hsdir_requery_period(const or_options_t *options); time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 9cf4059469..a6fb88624d 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -497,7 +497,12 @@ directory_get_from_hs_dir(const char *desc_id, /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { - hs_dir = pick_hsdir(desc_id, desc_id_base32); + /* Determine responsible dirs. Even if we can't get all we want, work with + * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ + smartlist_t *responsible_dirs = smartlist_new(); + hid_serv_get_responsible_directories(responsible_dirs, desc_id); + + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR"); From c754864cfda8cf1fffcf754c400be0c21b74b50d Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:12:33 +0300 Subject: [PATCH 14/62] prop224: Add code that launches v3 HS desc fetches. Entry point is hs_client_refetch_v3_renddesc(). Will be used in subsequent commits. Signed-off-by: David Goulet --- src/or/directory.c | 7 ++ src/or/hs_client.c | 161 ++++++++++++++++++++++++++++++++++++++++++++- src/or/hs_client.h | 3 + 3 files changed, 169 insertions(+), 2 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index e079a5941f..7c96b43ca4 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1859,6 +1859,13 @@ directory_send_command(dir_connection_t *conn, httpcommand = "GET"; tor_asprintf(&url, "/tor/rendezvous2/%s", resource); break; + case DIR_PURPOSE_FETCH_HSDESC: + tor_assert(resource); + tor_assert(strlen(resource) <= ED25519_BASE64_LEN); + tor_assert(!payload); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/hs/3/%s", resource); + break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: tor_assert(!resource); tor_assert(payload); diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 37981c8b65..66068092f3 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -2,7 +2,7 @@ /* See LICENSE for licensing information */ /** - * \file hs_service.c + * \file hs_client.c * \brief Implement next generation hidden service client functionality **/ @@ -11,8 +11,12 @@ #include "hs_ident.h" #include "connection_edge.h" #include "rendclient.h" - +#include "hs_descriptor.h" +#include "hs_cache.h" +#include "config.h" +#include "directory.h" #include "hs_client.h" +#include "router.h" /** A prop224 v3 HS circuit successfully connected to the hidden * service. Update the stream state at hs_conn_ident appropriately. */ @@ -25,6 +29,117 @@ hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) return; } +/* Given the pubkey of a hidden service in onion_identity_pk, fetch its + * descriptor by launching a dir connection to hsdir. Return 1 on + * success or -1 on error. */ +static int +directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, + const routerstatus_t *hsdir) +{ + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + ed25519_public_key_t blinded_pubkey; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + hs_ident_dir_conn_t hs_conn_dir_ident; + int retval; + + tor_assert(hsdir); + tor_assert(onion_identity_pk); + + /* Get blinded pubkey */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return -1; + } + + /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ + ed25519_pubkey_copy(&hs_conn_dir_ident.identity_pk, onion_identity_pk); + + /* Setup directory request */ + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_HSDESC); + directory_request_set_routerstatus(req, hsdir); + directory_request_set_indirection(req, DIRIND_ANONYMOUS); + directory_request_set_resource(req, base64_blinded_pubkey); + directory_request_upload_set_hs_ident(req, &hs_conn_dir_ident); + directory_initiate_request(req); + directory_request_free(req); + + log_info(LD_REND, "Descriptor fetch request for service %s with blinded " + "key %s to directory %s", + safe_str_client(ed25519_fmt(onion_identity_pk)), + safe_str_client(base64_blinded_pubkey), + safe_str_client(routerstatus_describe(hsdir))); + + /* Cleanup memory. */ + memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey)); + memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey)); + memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident)); + + return 1; +} + +/** Return the HSDir we should use to fetch the descriptor of the hidden + * service with identity key onion_identity_pk. */ +static routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) +{ + int retval; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + smartlist_t *responsible_hsdirs; + ed25519_public_key_t blinded_pubkey; + routerstatus_t *hsdir_rs = NULL; + + tor_assert(onion_identity_pk); + + responsible_hsdirs = smartlist_new(); + + /* Get blinded pubkey of hidden service */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return NULL; + } + + /* Get responsible hsdirs of service for this time period */ + hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, 0, 1, + responsible_hsdirs); + + log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.", + smartlist_len(responsible_hsdirs)); + + /* Pick an HSDir from the responsible ones. The ownership of + * responsible_hsdirs is given to this function so no need to free it. */ + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + + return hsdir_rs; +} + +/** Fetch a v3 descriptor using the given onion_identity_pk. + * + * On success, 1 is returned. If no hidden service is left to ask, return 0. + * On error, -1 is returned. */ +static int +fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk) +{ + routerstatus_t *hsdir_rs =NULL; + + tor_assert(onion_identity_pk); + + hsdir_rs = pick_hsdir_v3(onion_identity_pk); + if (!hsdir_rs) { + log_info(LD_REND, "Couldn't pick a v3 hsdir."); + return 0; + } + + return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); +} + /** A circuit just finished connecting to a hidden service that the stream * conn has been waiting for. Let the HS subsystem know about this. */ void @@ -85,3 +200,45 @@ hs_client_decode_descriptor(const char *desc_str, return -1; } +/** Return true if there are any usable intro points in the v3 HS descriptor + * desc. */ +int +hs_client_any_intro_points_usable(const hs_descriptor_t *desc) +{ + /* XXX stub waiting for more client-side work: + equivalent to v2 rend_client_any_intro_points_usable() */ + tor_assert(desc); + return 1; +} + +/** Launch a connection to a hidden service directory to fetch a hidden + * service descriptor using identity_pk to get the necessary keys. + * + * On success, 1 is returned. If no hidden service is left to ask, return 0. + * On error, -1 is returned. (retval is only used by unittests right now) */ +int +hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) +{ + tor_assert(identity_pk); + + /* Are we configured to fetch descriptors? */ + if (!get_options()->FetchHidServDescriptors) { + log_warn(LD_REND, "We received an onion address for a hidden service " + "descriptor but we are configured to not fetch."); + return 0; + } + + /* Check if fetching a desc for this HS is useful to us right now */ + { + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(identity_pk); + if (cached_desc && hs_client_any_intro_points_usable(cached_desc)) { + log_warn(LD_GENERAL, "We would fetch a v3 hidden service descriptor " + "but we already have a useable descriprot."); + return 0; + } + } + + return fetch_v3_desc(identity_pk); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 348724ab3e..0b446c00bf 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -9,6 +9,7 @@ #ifndef TOR_HS_CLIENT_H #define TOR_HS_CLIENT_H +#include "crypto_ed25519.h" #include "hs_descriptor.h" void hs_client_note_connection_attempt_succeeded( @@ -18,6 +19,8 @@ int hs_client_decode_descriptor( const char *desc_str, const ed25519_public_key_t *service_identity_pk, hs_descriptor_t **desc); +int hs_client_any_intro_points_usable(const hs_descriptor_t *desc); +int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); #endif /* TOR_HS_CLIENT_H */ From 776e796d96a9f60bf41f62cbf7cc3e16c8452bdf Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:17:49 +0300 Subject: [PATCH 15/62] prop224: Connect to v3 services in connection_ap_handle_onion(). Recognize and handle v3 addresses. Signed-off-by: David Goulet --- src/or/connection_edge.c | 122 +++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 32 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index a0aa7c195f..8ef3379c4c 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,6 +76,8 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_cache.h" +#include "hs_client.h" #include "hs_circuit.h" #include "main.h" #include "nodelist.h" @@ -1392,10 +1394,13 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } +/** We just received a SOCKS request in conn to an onion address of type + * addresstype. Start connecting to the onion service. */ static int connection_ap_handle_onion(entry_connection_t *conn, socks_request_t *socks, - origin_circuit_t *circ) + origin_circuit_t *circ, + hostname_type_t addresstype) { time_t now = approx_time(); connection_t *base_conn = ENTRY_TO_CONN(conn); @@ -1432,38 +1437,81 @@ connection_ap_handle_onion(entry_connection_t *conn, return -1; } - /* Look up if we have client authorization configured for this hidden - * service. If we do, associate it with the rend_data. */ - rend_service_authorization_t *client_auth = - rend_client_lookup_service_authorization(socks->address); + /* Interface: Regardless of HS version after the block below we should have + set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */ + const char *onion_address = NULL; + int rend_cache_lookup_result = -ENOENT; + int descriptor_is_usable = 0; - const uint8_t *cookie = NULL; - rend_auth_type_t auth_type = REND_NO_AUTH; - if (client_auth) { - log_info(LD_REND, "Using previously configured client authorization " - "for hidden service request."); - auth_type = client_auth->auth_type; - cookie = client_auth->descriptor_cookie; - } + if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */ + rend_cache_entry_t *entry = NULL; + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization(socks->address); - /* Fill in the rend_data field so we can start doing a connection to - * a hidden service. */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = - rend_data_client_create(socks->address, NULL, (char *) cookie, - auth_type); - if (rend_data == NULL) { - return -1; + const uint8_t *cookie = NULL; + rend_auth_type_t auth_type = REND_NO_AUTH; + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + auth_type = client_auth->auth_type; + cookie = client_auth->descriptor_cookie; + } + + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = + rend_data_client_create(socks->address, NULL, (char *) cookie, + auth_type); + if (rend_data == NULL) { + return -1; + } + onion_address = rend_data_get_address(rend_data); + log_info(LD_REND,"Got a hidden service request for ID '%s'", + safe_str_client(onion_address)); + + rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1, + &entry); + if (!rend_cache_lookup_result && entry) { + descriptor_is_usable = rend_client_any_intro_points_usable(entry); + } + } else { /* it's a v3 hidden service */ + tor_assert(addresstype == ONION_V3_HOSTNAME); + const hs_descriptor_t *cached_desc = NULL; + int retval; + /* Create HS conn identifier with HS pubkey */ + hs_ident_edge_conn_t *hs_conn_ident = + tor_malloc_zero(sizeof(hs_ident_edge_conn_t)); + + retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk, + NULL, NULL); + if (retval < 0) { + log_warn(LD_GENERAL, "failed to parse hs address"); + tor_free(hs_conn_ident); + return -1; + } + ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident; + + onion_address = socks->address; + + /* Check the v3 desc cache */ + cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk); + if (cached_desc) { + rend_cache_lookup_result = 0; + descriptor_is_usable = hs_client_any_intro_points_usable(cached_desc); + log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.", + (descriptor_is_usable) ? "usable" : "unusable", + safe_str_client(onion_address), + (descriptor_is_usable) ? "Not fetching." : "Refecting."); + } else { + rend_cache_lookup_result = -ENOENT; + } } - const char *onion_address = rend_data_get_address(rend_data); - log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(onion_address)); /* Lookup the given onion address. If invalid, stop right now. * Otherwise, we might have it in the cache or not. */ unsigned int refetch_desc = 0; - rend_cache_entry_t *entry = NULL; - const int rend_cache_lookup_result = - rend_cache_lookup_entry(onion_address, -1, &entry); if (rend_cache_lookup_result < 0) { switch (-rend_cache_lookup_result) { case EINVAL: @@ -1474,6 +1522,8 @@ connection_ap_handle_onion(entry_connection_t *conn, return -1; case ENOENT: /* We didn't have this; we should look it up. */ + log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.", + safe_str_client(onion_address)); refetch_desc = 1; break; default: @@ -1491,12 +1541,18 @@ connection_ap_handle_onion(entry_connection_t *conn, /* Now we have a descriptor but is it usable or not? If not, refetch. * Also, a fetch could have been requested if the onion address was not * found in the cache previously. */ - if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { + if (refetch_desc || !descriptor_is_usable) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); connection_ap_mark_as_non_pending_circuit(conn); base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; - log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(onion_address)); - rend_client_refetch_v2_renddesc(rend_data); + if (addresstype == ONION_V2_HOSTNAME) { + tor_assert(edge_conn->rend_data); + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + } else { + tor_assert(addresstype == ONION_V3_HOSTNAME); + tor_assert(edge_conn->hs_ident); + hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + } return 0; } @@ -1676,7 +1732,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Now, we handle everything that isn't a .onion address. */ - if (addresstype != ONION_V2_HOSTNAME) { + if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, * possibly with a .exit that we stripped off. We're going to check * if we're allowed to connect/resolve there, and then launch the @@ -1954,8 +2010,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return 0; } else { /* If we get here, it's a request for a .onion address! */ + tor_assert(addresstype == ONION_V2_HOSTNAME || + addresstype == ONION_V3_HOSTNAME); tor_assert(!automap); - if (connection_ap_handle_onion(conn, socks, circ) < 0) { + if (connection_ap_handle_onion(conn, socks, circ, addresstype) < 0) { return -1; } } From ebacf4dd6e709f45d2e23da62312848161e78091 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:22:17 +0300 Subject: [PATCH 16/62] prop224: Introduce v2/v3 HS desc fetch retry functionality. Signed-off-by: David Goulet --- src/or/directory.c | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 7c96b43ca4..010bc8c7b7 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -25,6 +25,7 @@ #include "geoip.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -3345,6 +3346,33 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } +/** We are closing a dir connection: If dir_conn is a dir connection + * that tried to fetch an HS descriptor, check if it successfuly fetched it, + * or if we need to try again. */ +static void +refetch_hsdesc_if_needed(dir_connection_t *dir_conn) +{ + connection_t *conn = TO_CONN(dir_conn); + + /* If we were trying to fetch a v2 rend desc and did not succeed, + * retry as needed. (If a fetch is successful, the connection state + * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that + * refetching is unnecessary.) */ + if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && + dir_conn->rend_data && + rend_valid_v2_service_id( + rend_data_get_address(dir_conn->rend_data))) { + rend_client_refetch_v2_renddesc(dir_conn->rend_data); + } + + /* Check for v3 rend desc fetch */ + if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC && + dir_conn->hs_ident && + !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) { + hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk); + } +} + /** Called when we're about to finally unlink and free a directory connection: * perform necessary accounting and cleanup */ void @@ -3357,15 +3385,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) * failed: forget about this router, and maybe try again. */ connection_dir_request_failed(dir_conn); } - /* If we were trying to fetch a v2 rend desc and did not succeed, - * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that - * refetching is unnecessary.) */ - if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && - dir_conn->rend_data && - strlen(rend_data_get_address(dir_conn->rend_data)) == - REND_SERVICE_ID_LEN_BASE32) - rend_client_refetch_v2_renddesc(dir_conn->rend_data); + + refetch_hsdesc_if_needed(dir_conn); } /** Create an http response for the client conn out of From f93b77a18c5d1e77cb80fec0827d82d3672f9673 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:25:46 +0300 Subject: [PATCH 17/62] prop224: Add client code to handle fetched HS descriptors. This code handles received HS descriptors by storing them in the client-side HS cache. Signed-off-by: David Goulet --- src/or/directory.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++ src/or/directory.h | 4 ++++ 2 files changed, 59 insertions(+) diff --git a/src/or/directory.c b/src/or/directory.c index 010bc8c7b7..58d2e33070 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -2538,6 +2538,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case DIR_PURPOSE_UPLOAD_HSDESC: rv = handle_response_upload_hsdesc(conn, &args); break; + case DIR_PURPOSE_FETCH_HSDESC: + rv = handle_response_fetch_hsdesc_v3(conn, &args); + break; default: tor_assert_nonfatal_unreached(); rv = -1; @@ -3082,6 +3085,58 @@ handle_response_upload_signatures(dir_connection_t *conn, return 0; } +/** + * Handler function: processes a response to a request for a v3 hidden service + * descriptor. + **/ +STATIC int +handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + tor_assert(conn->hs_ident); + + log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))", + (int)body_len, status_code, escaped(reason)); + + switch (status_code) { + case 200: + /* We got something: Try storing it in the cache. */ + if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { + log_warn(LD_REND, "Failed to store hidden service descriptor"); + } else { + log_info(LD_REND, "Stored hidden service descriptor successfully."); + } + break; + case 404: + /* Not there. We'll retry when connection_about_to_close_connection() + * tries to clean this conn up. */ + log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " + "Retrying at another directory."); + /* TODO: Inform the control port */ + break; + case 400: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "query? Retrying at another directory.", + escaped(reason)); + break; + default: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status %d (%s) response unexpected from HSDir server " + "'%s:%d'. Retrying at another directory.", + status_code, escaped(reason), TO_CONN(conn)->address, + TO_CONN(conn)->port); + break; + } + + return 0; +} + /** * Handler function: processes a response to a request for a v2 hidden service * descriptor. diff --git a/src/or/directory.h b/src/or/directory.h index d3f8a45a82..92d9b266df 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -176,6 +176,10 @@ STATIC char *accept_encoding_header(void); STATIC int allowed_anonymous_connection_compression_method(compress_method_t); STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); +typedef struct response_handler_args_t response_handler_args_t; +STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args); + #endif #ifdef TOR_UNIT_TESTS From 343af1a9cf64e060150caf7b53e513315cc37f72 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 27 Jun 2017 09:46:16 -0400 Subject: [PATCH 18/62] prop224: Add the dir purpose HAS_FETCHED Once a descriptor has been successfully downloaded from an HSDir, we flag the directory connection to "has fetched descriptor" so the connection subsystem doesn't trigger a new fetch on success. Same has DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 but for prop224. Signed-off-by: David Goulet --- src/or/directory.c | 11 +++++++---- src/or/or.h | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 58d2e33070..698ee90139 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -184,6 +184,7 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_FETCH_EXTRAINFO: case DIR_PURPOSE_FETCH_MICRODESC: return 0; + case DIR_PURPOSE_HAS_FETCHED_HSDESC: case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: case DIR_PURPOSE_UPLOAD_RENDDESC_V2: case DIR_PURPOSE_FETCH_RENDDESC_V2: @@ -1126,6 +1127,7 @@ directory_request_new(uint8_t dir_purpose) tor_assert(dir_purpose <= DIR_PURPOSE_MAX_); tor_assert(dir_purpose != DIR_PURPOSE_SERVER); tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2); + tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC); directory_request_t *result = tor_malloc_zero(sizeof(*result)); tor_addr_make_null(&result->or_addr_port.addr, AF_INET); @@ -3110,6 +3112,7 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, log_warn(LD_REND, "Failed to store hidden service descriptor"); } else { log_info(LD_REND, "Stored hidden service descriptor successfully."); + TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; } break; case 404: @@ -3409,10 +3412,10 @@ refetch_hsdesc_if_needed(dir_connection_t *dir_conn) { connection_t *conn = TO_CONN(dir_conn); - /* If we were trying to fetch a v2 rend desc and did not succeed, - * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that - * refetching is unnecessary.) */ + /* If we were trying to fetch a v2 rend desc and did not succeed, retry as + * needed. (If a fetch is successful, the connection state is changed to + * DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to + * mark that refetching is unnecessary.) */ if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && dir_conn->rend_data && rend_valid_v2_service_id( diff --git a/src/or/or.h b/src/or/or.h index ff11c72790..3cd8239f1c 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -425,7 +425,10 @@ typedef enum { #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 +/** A connection to a directory server: set after a hidden service descriptor + * is downloaded. */ +#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22 +#define DIR_PURPOSE_MAX_ 22 /** True iff p is a purpose corresponding to uploading * data to a directory server. */ From b2a820958e8389853757c3e5ad59114bd82623d4 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 28 Jun 2017 14:20:28 -0400 Subject: [PATCH 19/62] prop224: Rename hs_client_note_connection_attempt_succeeded() This is a static function so don't polute the hs_client_ namespace. Signed-off-by: David Goulet --- src/or/hs_client.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 66068092f3..8cf98a6b90 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -18,10 +18,10 @@ #include "hs_client.h" #include "router.h" -/** A prop224 v3 HS circuit successfully connected to the hidden - * service. Update the stream state at hs_conn_ident appropriately. */ +/* A v3 HS circuit successfully connected to the hidden service. Update the + * stream state at hs_conn_ident appropriately. */ static void -hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) +note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) { (void) hs_conn_ident; @@ -153,7 +153,7 @@ hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) } if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */ - hs_client_attempt_succeeded(conn->hs_ident); + note_connection_attempt_succeeded(conn->hs_ident); return; } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */ rend_client_note_connection_attempt_ended(conn->rend_data); From 79ff2e014f5726648e3f2c4a3f521ede76b8c6e2 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 27 Jun 2017 09:28:15 -0400 Subject: [PATCH 20/62] hs: Fix comment of the get max size descriptor function Signed-off-by: David Goulet --- src/or/hs_cache.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 6f7bf1abd7..d93e682dc3 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -610,10 +610,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes) return bytes_removed; } -/** - * Return the maximum size of an HS descriptor we are willing to accept as an - * HSDir. - */ +/* Return the maximum size of a v3 HS descriptor. */ unsigned int hs_cache_get_max_descriptor_size(void) { From 6eb9de1b8c2ede739ebcd3514201c07365fadb18 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 14:35:39 +0300 Subject: [PATCH 21/62] test: Add tests for fetching descs and handling SOCKS conns. - Add tests that ensure that SOCKS requests for v2/v3 addresses get intercepted and handled. - Add test that stores and lookups an HS descriptor in the client-side cache. Signed-off-by: David Goulet --- src/or/directory.c | 10 ----- src/or/directory.h | 12 +++++- src/test/test_entryconn.c | 87 +++++++++++++++++++++++++++++++++++++++ src/test/test_hs_cache.c | 66 +++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 698ee90139..9268c3ca2f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -2203,16 +2203,6 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } -/** A structure to hold arguments passed into each directory response - * handler */ -typedef struct response_handler_args_t { - int status_code; - const char *reason; - const char *body; - size_t body_len; - const char *headers; -} response_handler_args_t; - static int handle_response_fetch_consensus(dir_connection_t *, const response_handler_args_t *); static int handle_response_fetch_certificate(dir_connection_t *, diff --git a/src/or/directory.h b/src/or/directory.h index 92d9b266df..1acdff3460 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -168,6 +168,16 @@ int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, #ifdef DIRECTORY_PRIVATE +/** A structure to hold arguments passed into each directory response + * handler */ +typedef struct response_handler_args_t { + int status_code; + const char *reason; + const char *body; + size_t body_len; + const char *headers; +} response_handler_args_t; + struct get_handler_args_t; STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, const struct get_handler_args_t *args); @@ -183,7 +193,7 @@ STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, #endif #ifdef TOR_UNIT_TESTS -/* Used only by test_dir.c */ +/* Used only by test_dir.c and test_hs_cache.c */ STATIC int parse_http_url(const char *headers, char **url); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, diff --git a/src/test/test_entryconn.c b/src/test/test_entryconn.c index 12a631630b..9fe3db26f7 100644 --- a/src/test/test_entryconn.c +++ b/src/test/test_entryconn.c @@ -14,6 +14,10 @@ #include "confparse.h" #include "connection.h" #include "connection_edge.h" +#include "nodelist.h" + +#include "hs_cache.h" +#include "rendcache.h" static void * entryconn_rewrite_setup(const struct testcase_t *tc) @@ -743,6 +747,87 @@ test_entryconn_rewrite_mapaddress_automap_onion4(void *arg) test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1); } +/** Test that rewrite functions can handle v2 addresses */ +static void +test_entryconn_rewrite_onion_v2(void *arg) +{ + int retval; + entry_connection_t *conn = arg; + + (void) arg; + + rend_cache_init(); + + /* Make a SOCKS request */ + conn->socks_request->command = SOCKS_COMMAND_CONNECT; + strlcpy(conn->socks_request->address, + "pqeed46efnwmfuid.onion", + sizeof(conn->socks_request->address)); + + /* Make an onion connection using the SOCKS request */ + conn->entry_cfg.onion_traffic = 1; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT; + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + + /* Handle SOCKS and rewrite! */ + retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL); + tt_int_op(retval, OP_EQ, 0); + + /* Check connection state after rewrite */ + tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT); + /* check that the address got rewritten */ + tt_str_op(conn->socks_request->address, OP_EQ, + "pqeed46efnwmfuid"); + /* check that HS information got attached to the connection */ + tt_assert(ENTRY_TO_EDGE_CONN(conn)->rend_data); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident); + + done: + rend_cache_free_all(); + /* 'conn' is cleaned by handler */ +} + +/** Test that rewrite functions can handle v3 onion addresses */ +static void +test_entryconn_rewrite_onion_v3(void *arg) +{ + int retval; + entry_connection_t *conn = arg; + + (void) arg; + + hs_cache_init(); + + /* Make a SOCKS request */ + conn->socks_request->command = SOCKS_COMMAND_CONNECT; + strlcpy(conn->socks_request->address, + "git.p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad.onion", + sizeof(conn->socks_request->address)); + + /* Make an onion connection using the SOCKS request */ + conn->entry_cfg.onion_traffic = 1; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT; + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident); + + /* Handle SOCKS and rewrite! */ + retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL); + tt_int_op(retval, OP_EQ, 0); + + /* Check connection state after rewrite */ + tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT); + /* check that the address got rewritten */ + tt_str_op(conn->socks_request->address, OP_EQ, + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad"); + /* check that HS information got attached to the connection */ + tt_assert(ENTRY_TO_EDGE_CONN(conn)->hs_ident); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + + done: + hs_free_all(); + /* 'conn' is cleaned by handler */ +} + #define REWRITE(name) \ { #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL } @@ -763,6 +848,8 @@ struct testcase_t entryconn_tests[] = { REWRITE(rewrite_mapaddress_automap_onion2), REWRITE(rewrite_mapaddress_automap_onion3), REWRITE(rewrite_mapaddress_automap_onion4), + REWRITE(rewrite_onion_v2), + REWRITE(rewrite_onion_v3), END_OF_TESTCASES }; diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index 6c2addef9a..d3950c0c29 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -7,6 +7,7 @@ */ #define CONNECTION_PRIVATE +#define DIRECTORY_PRIVATE #define HS_CACHE_PRIVATE #include "ed25519_cert.h" @@ -431,6 +432,69 @@ test_hsdir_revision_counter_check(void *arg) tor_free(published_desc_str); } +/** Test that we can store HS descriptors in the client HS cache. */ +static void +test_client_cache(void *arg) +{ + int retval; + ed25519_keypair_t signing_kp; + hs_descriptor_t *published_desc = NULL; + char *published_desc_str = NULL; + + response_handler_args_t *args = NULL; + dir_connection_t *conn = NULL; + + (void) arg; + + /* Initialize HSDir cache subsystem */ + init_test(); + + /* Generate a valid descriptor with normal values. */ + { + retval = ed25519_keypair_generate(&signing_kp, 0); + tt_int_op(retval, ==, 0); + published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); + tt_assert(published_desc); + retval = hs_desc_encode_descriptor(published_desc, &signing_kp, + &published_desc_str); + tt_int_op(retval, OP_EQ, 0); + } + + /* Test handle_response_fetch_hsdesc_v3() */ + { + args = tor_malloc_zero(sizeof(response_handler_args_t)); + args->status_code = 200; + args->reason = NULL; + args->body = published_desc_str; + args->body_len = strlen(published_desc_str); + + conn = tor_malloc_zero(sizeof(dir_connection_t)); + conn->hs_ident = tor_malloc_zero(sizeof(hs_ident_dir_conn_t)); + ed25519_pubkey_copy(&conn->hs_ident->identity_pk, &signing_kp.pubkey); + } + + /* store the descriptor! */ + retval = handle_response_fetch_hsdesc_v3(conn, args); + tt_int_op(retval, == , 0); + + /* fetch the descriptor and make sure it's there */ + { + hs_cache_client_descriptor_t *cached_desc = NULL; + cached_desc = lookup_v3_desc_as_client(signing_kp.pubkey.pubkey); + tt_assert(cached_desc); + tt_str_op(cached_desc->encoded_desc, OP_EQ, published_desc_str); + } + + done: + tor_free(args); + hs_descriptor_free(published_desc); + tor_free(published_desc_str); + if (conn) { + tor_free(conn->hs_ident); + tor_free(conn); + } +} + struct testcase_t hs_cache[] = { /* Encoding tests. */ { "directory", test_directory, TT_FORK, @@ -441,6 +505,8 @@ struct testcase_t hs_cache[] = { NULL, NULL }, { "upload_and_download_hs_desc", test_upload_and_download_hs_desc, TT_FORK, NULL, NULL }, + { "client_cache", test_client_cache, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From 6222eae8cabd7ab99e634ad7463a8c38d414fa19 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 18 Jul 2017 11:41:41 -0400 Subject: [PATCH 22/62] conn: Add a function to return a list of connection by state This will be useful to the hidden service subsystem that needs to go over all connections of a certain state to attach them to a hidden service circuit. Signed-off-by: David Goulet --- src/or/connection.c | 21 +++++++++++++++++++++ src/or/connection.h | 1 + 2 files changed, 22 insertions(+) diff --git a/src/or/connection.c b/src/or/connection.c index 5c65e886c0..31a682387d 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -4102,6 +4102,27 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +#define CONN_GET_ALL_TEMPLATE(var, test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *ret_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \ + if (var && (test) && !var->marked_for_close) \ + smartlist_add(ret_conns, var); \ + } SMARTLIST_FOREACH_END(var); \ + return ret_conns; \ + STMT_END + +/* Return a list of connections that aren't close and matches the given state. + * The returned list can be empty and must be freed using smartlist_free(). + * The caller does NOT have owernship of the objects in the list so it must + * not free them nor reference them as they can disapear. */ +smartlist_t * +connection_list_by_type_state(int type, int state) +{ + CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state)); +} + /** Return a connection_t * from get_connection_array() that satisfies test on * var, and that is not marked for close. */ #define CONN_GET_TEMPLATE(var, test) \ diff --git a/src/or/connection.h b/src/or/connection.h index 36e45aef38..0bcf0ccdce 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -182,6 +182,7 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +smartlist_t *connection_list_by_type_state(int type, int state); smartlist_t *connection_dir_list_by_purpose_and_resource( int purpose, const char *resource); From b13ee8e4ae59f85ce75800aa7dd90cfe58c04a5e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 18 Jul 2017 13:56:19 -0400 Subject: [PATCH 23/62] hs: Move link specifier encoding to a function This commit only moves code into a function. The client code will need a way to take a bunch of descriptor link specifier object and encode them into link specifiers objects. Make this a public function so it can be used outside of hs_descriptor.c. Signed-off-by: David Goulet --- src/or/hs_descriptor.c | 102 ++++++++++++++++++++++++----------------- src/or/hs_descriptor.h | 5 ++ 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 1e595f3a3d..71f16fee1a 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -332,50 +332,10 @@ encode_link_specifiers(const smartlist_t *specs) SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, spec) { - link_specifier_t *ls = link_specifier_new(); - link_specifier_set_ls_type(ls, spec->type); - - switch (spec->type) { - case LS_IPV4: - link_specifier_set_un_ipv4_addr(ls, - tor_addr_to_ipv4h(&spec->u.ap.addr)); - link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); - /* Four bytes IPv4 and two bytes port. */ - link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + - sizeof(spec->u.ap.port)); - break; - case LS_IPV6: - { - size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); - const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); - uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); - memcpy(ipv6_array, in6_addr, addr_len); - link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); - /* Sixteen bytes IPv6 and two bytes port. */ - link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); - break; + link_specifier_t *ls = hs_desc_encode_lspec(spec); + if (ls) { + link_specifier_list_add_spec(lslist, ls); } - case LS_LEGACY_ID: - { - size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); - uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); - memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); - 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); - } - - link_specifier_list_add_spec(lslist, ls); } SMARTLIST_FOREACH_END(spec); { @@ -2575,3 +2535,59 @@ hs_descriptor_clear_intro_points(hs_descriptor_t *desc) } } +/* From a descriptor link specifier object spec, returned a newly allocated + * link specifier object that is the encoded representation of spec. Return + * NULL on error. */ +link_specifier_t * +hs_desc_encode_lspec(const hs_desc_link_specifier_t *spec) +{ + tor_assert(spec); + + link_specifier_t *ls = link_specifier_new(); + link_specifier_set_ls_type(ls, spec->type); + + switch (spec->type) { + case LS_IPV4: + link_specifier_set_un_ipv4_addr(ls, + tor_addr_to_ipv4h(&spec->u.ap.addr)); + link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + + sizeof(spec->u.ap.port)); + break; + case LS_IPV6: + { + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); + break; + } + case LS_LEGACY_ID: + { + size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); + uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); + memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); + 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_nonfatal_unreached(); + link_specifier_free(ls); + ls = NULL; + } + + return ls; +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 256a04dd6e..b4cda7b845 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -18,6 +18,9 @@ #include "crypto_ed25519.h" #include "torcert.h" +/* Trunnel */ +#include "ed25519_cert.h" + /* The earliest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 /* The latest descriptor format version we support. */ @@ -230,6 +233,8 @@ 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); +link_specifier_t *hs_desc_encode_lspec(const hs_desc_link_specifier_t *spec); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ From 8e2854372d777d6be63d1bf766ca6db9100490de Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 29 Jun 2017 13:29:23 -0400 Subject: [PATCH 24/62] prop224: Helper function to assert on invalid client intro circuit Put all the possible assert() we can do on a client introduction circuit in one helper function to make sure it is valid and usable. It is disabled for now so gcc doesn't complain that we have a unused function. Signed-off-by: David Goulet --- src/or/hs_client.c | 14 ++++++++++++++ src/or/hs_common.c | 1 + src/or/hs_ident.c | 22 ++++++++++++++++++++++ src/or/hs_ident.h | 3 +++ src/or/rendcommon.c | 2 +- src/or/rendcommon.h | 2 +- 6 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 8cf98a6b90..514ecf99ba 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -140,6 +140,20 @@ fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk) return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); } +#if 0 +/* Make sure that the given origin circuit circ is a valid correct + * introduction circuit. This asserts on validation failure. */ +static void +assert_intro_circ(const origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + tor_assert(circ->hs_ident); + tor_assert(hs_ident_intro_circ_is_valid(circ->hs_ident)); + assert_circ_anonymity_ok(circ, get_options()); +} +#endif + /** A circuit just finished connecting to a hidden service that the stream * conn has been waiting for. Let the HS subsystem know about this. */ void diff --git a/src/or/hs_common.c b/src/or/hs_common.c index bc44265d53..e0c7dca4bc 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -18,6 +18,7 @@ #include "nodelist.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_ident.h" #include "hs_service.h" #include "rendcommon.h" #include "rendservice.h" diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c index e69350d82e..df39285158 100644 --- a/src/or/hs_ident.c +++ b/src/or/hs_ident.c @@ -86,3 +86,25 @@ hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident) tor_free(ident); } +/* Return true if the given ident is valid for an introduction circuit. */ +int +hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->identity_pk)) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) { + goto invalid; + } + + /* Valid. */ + return 1; + invalid: + return 0; +} + diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h index e259fde54d..cfcde781d1 100644 --- a/src/or/hs_ident.h +++ b/src/or/hs_ident.h @@ -126,5 +126,8 @@ hs_ident_edge_conn_t *hs_ident_edge_conn_new( const ed25519_public_key_t *identity_pk); void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident); +/* Validators */ +int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident); + #endif /* TOR_HS_IDENT_H */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 8829ede960..a6b59881ad 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -990,7 +990,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options) * service. */ void -assert_circ_anonymity_ok(origin_circuit_t *circ, +assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options) { tor_assert(options); diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index f03a57f2e1..af8dd60099 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -60,7 +60,7 @@ int rend_auth_decode_cookie(const char *cookie_in, int rend_allow_non_anonymous_connection(const or_options_t* options); int rend_non_anonymous_mode_enabled(const or_options_t *options); -void assert_circ_anonymity_ok(origin_circuit_t *circ, +void assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options); #ifdef RENDCOMMON_PRIVATE From 8a552bf49bb94dbca6163e7063d776f5f3a96694 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 18 Jul 2017 15:17:37 -0400 Subject: [PATCH 25/62] prop224: Make lspecs to extend info public The hs circuit file had this function that takes a list of link specifiers and return a newly allocated extend info object. Make it public so the client side can also use it to be able to extend to introduction point. Signed-off-by: David Goulet --- src/or/hs_circuit.c | 124 ++------------------------------------------ src/or/hs_common.c | 122 +++++++++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 4 ++ 3 files changed, 129 insertions(+), 121 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 6d1cdd4eb0..7704fd0bad 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -341,125 +341,6 @@ send_establish_intro(const hs_service_t *service, 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 @@ -483,8 +364,9 @@ launch_rendezvous_point_circuit(const hs_service_t *service, /* 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); + info = hs_get_extend_info_from_lspecs(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; diff --git a/src/or/hs_common.c b/src/or/hs_common.c index e0c7dca4bc..03dd07f6ca 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -14,12 +14,14 @@ #include "or.h" #include "config.h" +#include "circuitbuild.h" #include "networkstatus.h" #include "nodelist.h" #include "hs_cache.h" #include "hs_common.h" #include "hs_ident.h" #include "hs_service.h" +#include "policies.h" #include "rendcommon.h" #include "rendservice.h" #include "routerset.h" @@ -28,6 +30,9 @@ #include "shared_random.h" #include "shared_random_state.h" +/* Trunnel */ +#include "ed25519_cert.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 = @@ -1559,6 +1564,123 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) return hs_dir; } +/* 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 fulfill the conditions. */ +extend_info_t * +hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + 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(lspecs); + + SMARTLIST_FOREACH_BEGIN(lspecs, 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 and legacy ID are mandatory. */ + 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, "Requested 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; +} + /***********************************************************************/ /* Initialize the entire HS subsytem. This is called in tor_init() before any diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 6ad68d0a8a..79d92d915f 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -237,6 +237,10 @@ 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); +extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn); + #ifdef HS_COMMON_PRIVATE STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); From d599325b5e1f92395742c3380112b4d2d7cbe9db Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 14 Jul 2017 10:16:48 -0400 Subject: [PATCH 26/62] prop224: Build INTRODUCE1 cell and send logic Add a function in hs_cell.{c|h} for a client to build an INTRODUCE1 cell using an object that contains all the needed keys to do so. Add an entry point in hs_client.c that allows a tor client to send an INTRODUCE1 cell on a given introduction circuit. It includes the building of the cell, sending it and the setup of the rendezvous circuit with the circuit identifier. The entry point function is still unused at this commit. Signed-off-by: David Goulet --- src/or/hs_cell.c | 265 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_cell.h | 30 +++++ src/or/hs_circuit.c | 147 ++++++++++++++++++++++++ src/or/hs_circuit.h | 4 + src/or/hs_client.c | 180 +++++++++++++++++++++++++++++- src/or/hs_client.h | 3 + 6 files changed, 626 insertions(+), 3 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 7728b77053..889cf7749b 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -10,6 +10,7 @@ #include "config.h" #include "rendservice.h" #include "replaycache.h" +#include "util.h" #include "hs_cell.h" #include "hs_ntor.h" @@ -245,6 +246,229 @@ parse_introduce2_cell(const hs_service_t *service, return -1; } +/* Set the onion public key onion_pk in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, + const uint8_t *onion_pk) +{ + tor_assert(cell); + tor_assert(onion_pk); + /* There is only one possible key type for a non legacy cell. */ + trn_cell_introduce_encrypted_set_onion_key_type(cell, + HS_CELL_ONION_KEY_TYPE_NTOR); + trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN); + trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN); + memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk, + trn_cell_introduce_encrypted_getlen_onion_key(cell)); +} + +/* Set the link specifiers in lspecs in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, + const smartlist_t *lspecs) +{ + tor_assert(cell); + tor_assert(lspecs); + tor_assert(smartlist_len(lspecs) > 0); + tor_assert(smartlist_len(lspecs) <= UINT8_MAX); + + uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs); + trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num); + /* We aren't duplicating the link specifiers object here which means that + * the ownership goes to the trn_cell_introduce_encrypted_t cell and those + * object will be freed when the cell is. */ + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, + trn_cell_introduce_encrypted_add_nspecs(cell, ls)); +} + +/* Set padding in the enc_cell only if needed that is the total length of both + * sections are below the mininum required for an INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, + trn_cell_introduce_encrypted_t *enc_cell) +{ + tor_assert(cell); + tor_assert(enc_cell); + /* This is the length we expect to have once encoded of the whole cell. */ + ssize_t full_len = trn_cell_introduce1_encoded_len(cell) + + trn_cell_introduce_encrypted_encoded_len(enc_cell); + tor_assert(full_len > 0); + if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) { + size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len; + trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding); + memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0, + trn_cell_introduce_encrypted_getlen_pad(enc_cell)); + } +} + +/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell + * and the INTRODUCE1 data. + * + * This can't fail but it is very important that the caller sets every field + * in data so the computation of the INTRODUCE1 keys doesn't fail. */ +static void +introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell, + const trn_cell_introduce_encrypted_t *enc_cell, + const hs_cell_introduce1_data_t *data) +{ + size_t offset = 0; + ssize_t encrypted_len; + ssize_t encoded_cell_len, encoded_enc_cell_len; + uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t *encrypted = NULL; + uint8_t mac[DIGEST256_LEN]; + crypto_cipher_t *cipher = NULL; + hs_ntor_intro_cell_keys_t keys; + + tor_assert(cell); + tor_assert(enc_cell); + tor_assert(data); + + /* Encode the cells up to now of what we have to we can perform the MAC + * computation on it. */ + encoded_cell_len = trn_cell_introduce1_encode(encoded_cell, + sizeof(encoded_cell), cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_cell_len > 0); + + encoded_enc_cell_len = + trn_cell_introduce_encrypted_encode(encoded_enc_cell, + sizeof(encoded_enc_cell), enc_cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_enc_cell_len > 0); + + /* Get the key material for the encryption. */ + if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk, + data->client_kp, + data->subcredential, &keys) < 0) { + tor_assert_unreached(); + } + + /* Prepare cipher with the encryption key just computed. */ + cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key, + sizeof(keys.enc_key) * 8); + tor_assert(cipher); + + /* Compute the length of the ENCRYPTED section which is the CLIENT_PK, + * ENCRYPTED_DATA and MAC length. */ + encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len + + sizeof(mac); + tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE); + encrypted = tor_malloc_zero(encrypted_len); + + /* Put the CLIENT_PK first. */ + memcpy(encrypted, data->client_kp->pubkey.public_key, + sizeof(data->client_kp->pubkey.public_key)); + offset += sizeof(data->client_kp->pubkey.public_key); + /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) encrypted + offset, + (const char *) encoded_enc_cell, encoded_enc_cell_len); + crypto_cipher_free(cipher); + offset += encoded_enc_cell_len; + /* Compute MAC from the above and put it in the buffer. This function will + * make the adjustment to the encryptled_len to ommit the MAC length. */ + compute_introduce_mac(encoded_cell, encoded_cell_len, + encrypted, encrypted_len, + keys.mac_key, sizeof(keys.mac_key), + mac, sizeof(mac)); + memcpy(encrypted + offset, mac, sizeof(mac)); + offset += sizeof(mac); + tor_assert(offset == (size_t) encrypted_len); + + /* Set the ENCRYPTED section in the cell. */ + trn_cell_introduce1_setlen_encrypted(cell, encrypted_len); + memcpy(trn_cell_introduce1_getarray_encrypted(cell), + encrypted, encrypted_len); + + /* Cleanup. */ + memwipe(&keys, 0, sizeof(keys)); + memwipe(mac, 0, sizeof(mac)); + memwipe(encrypted, 0, sizeof(encrypted_len)); + memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell)); + tor_free(encrypted); +} + +/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means + * set it, encrypt it and encode it. */ +static void +introduce1_set_encrypted(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + trn_cell_introduce_encrypted_t *enc_cell; + trn_cell_extension_t *ext; + + tor_assert(cell); + tor_assert(data); + + enc_cell = trn_cell_introduce_encrypted_new(); + tor_assert(enc_cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce_encrypted_set_extensions(enc_cell, ext); + + /* Set the rendezvous cookie. */ + memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell), + data->rendezvous_cookie, REND_COOKIE_LEN); + + /* Set the onion public key. */ + introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key); + + /* Set the link specifiers. */ + introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers); + + /* Set padding. */ + introduce1_set_encrypted_padding(cell, enc_cell); + + /* Encrypt and encode it in the cell. */ + introduce1_encrypt_and_encode(cell, enc_cell, data); + + /* Cleanup. */ + trn_cell_introduce_encrypted_free(enc_cell); +} + +/* Set the authentication key in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_auth_key(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + /* There is only one possible type for a non legacy cell. */ + trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519); + trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN); + trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN); + memcpy(trn_cell_introduce1_getarray_auth_key(cell), + data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell)); +} + +/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_legacy_id(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + + if (data->is_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) { + return; + } + memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell), + digest, trn_cell_introduce1_getlen_legacy_key_id(cell)); + } else { + /* We have to zeroed the LEGACY_KEY_ID field. */ + memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0, + trn_cell_introduce1_getlen_legacy_key_id(cell)); + } +} + /* ========== */ /* Public API */ /* ========== */ @@ -582,3 +806,44 @@ hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, return cell_len; } +/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in + * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the + * encoded length is returned else a negative value and the content of + * cell_out should be ignored. */ +ssize_t +hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_introduce1_t *cell; + trn_cell_extension_t *ext; + + tor_assert(data); + tor_assert(cell_out); + + cell = trn_cell_introduce1_new(); + tor_assert(cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce1_set_extensions(cell, ext); + + /* Set the legacy ID field. */ + introduce1_set_legacy_id(cell, data); + + /* Set the authentication key. */ + introduce1_set_auth_key(cell, data); + + /* Set the encrypted section. This will set, encrypt and encode the + * ENCRYPTED section in the cell. After this, we'll be ready to encode. */ + introduce1_set_encrypted(cell, data); + + /* Final encoding. */ + cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + + trn_cell_introduce1_free(cell); + return cell_len; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index f32f7a4216..a72009510c 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -12,11 +12,39 @@ #include "or.h" #include "hs_service.h" +/* An INTRODUCE1 cell requires at least this amount of bytes (see section + * 3.2.2 of the specification). Below this value, the cell must be padded. */ +#define HS_CELL_INTRODUCE1_MIN_SIZE 246 + /* 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 build an INTRODUCE1 cell + * used by the INTRODUCE1 build function. */ +typedef struct hs_cell_introduce1_data_t { + /* Is this a legacy introduction point? */ + unsigned int is_legacy : 1; + /* (Legacy only) The encryption key for a legacy intro point. Only set if + * is_legacy is true. */ + const crypto_pk_t *legacy_key; + /* Introduction point authentication public key. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption public key. */ + const curve25519_public_key_t *enc_pk; + /* Subcredentials of the service. */ + const uint8_t *subcredential; + /* Onion public key for the ntor handshake. */ + const curve25519_public_key_t *onion_pk; + /* Rendezvous cookie. */ + const uint8_t *rendezvous_cookie; + /* Public key put before the encrypted data (CLIENT_PK). */ + const curve25519_keypair_t *client_kp; + /* Rendezvous point link specifiers. */ + smartlist_t *link_specifiers; +} hs_cell_introduce1_data_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 @@ -63,6 +91,8 @@ ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, const uint8_t *rendezvous_handshake_info, size_t rendezvous_handshake_info_len, uint8_t *cell_out); +ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out); /* Parse cell API. */ ssize_t hs_cell_parse_intro_established(const uint8_t *payload, diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 7704fd0bad..95100e9b3a 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -530,6 +530,83 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) return; } +/* Using an extend info object ei, set all possible link specifiers in lspecs. + * IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */ +static void +get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) +{ + link_specifier_t *ls; + + tor_assert(ei); + tor_assert(lspecs); + + /* IPv4 is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr)); + link_specifier_set_un_ipv4_port(ls, ei->port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) + + sizeof(ei->port)); + smartlist_add(lspecs, ls); + + /* Legacy ID is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest, + link_specifier_getlen_un_legacy_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); + smartlist_add(lspecs, ls); + + /* ed25519 ID is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + + /* XXX: IPv6 is not clearly a thing in extend_info_t? */ +} + +/* Using the given descriptor intro point ip, the extend information of the + * rendezvous point rp_ei and the service's subcredential, populate the + * already allocated intro1_data object with the needed key material and link + * specifiers. + * + * This can't fail but the ip MUST be a valid object containing the needed + * keys and authentication method. */ +static void +setup_introduce1_data(const hs_desc_intro_point_t *ip, + const extend_info_t *rp_ei, + const uint8_t *subcredential, + hs_cell_introduce1_data_t *intro1_data) +{ + smartlist_t *rp_lspecs; + + tor_assert(ip); + tor_assert(rp_ei); + tor_assert(subcredential); + tor_assert(intro1_data); + + /* Build the link specifiers from the extend information of the rendezvous + * circuit that we've picked previously. */ + rp_lspecs = smartlist_new(); + get_lspecs_from_extend_info(rp_ei, rp_lspecs); + + /* Populate the introduce1 data object. */ + memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t)); + if (ip->legacy.key != NULL) { + intro1_data->is_legacy = 1; + intro1_data->legacy_key = ip->legacy.key; + } + intro1_data->auth_pk = &ip->auth_key_cert->signed_key; + intro1_data->enc_pk = &ip->enc_key; + intro1_data->subcredential = subcredential; + intro1_data->onion_pk = &rp_ei->curve25519_onion_key; + intro1_data->link_specifiers = rp_lspecs; +} + /* ========== */ /* Public API */ /* ========== */ @@ -937,3 +1014,73 @@ hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, return 0; } +/* Given the introduction circuit intro_circ, the rendezvous circuit + * rend_circ, a descriptor intro point object ip and the service's + * subcredential, send an INTRODUCE1 cell on intro_circ. + * + * This will also setup the circuit identifier on rend_circ containing the key + * material for the handshake and e2e encryption. Return 0 on success else + * negative value. Because relay_send_command_from_edge() closes the circuit + * on error, it is possible that intro_circ is closed on error. */ +int +hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential) +{ + int ret = -1; + ssize_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + hs_cell_introduce1_data_t intro1_data; + + tor_assert(intro_circ); + tor_assert(rend_circ); + tor_assert(ip); + tor_assert(subcredential); + + /* This takes various objects in order to populate the introduce1 data + * object which is used to build the content of the cell. */ + setup_introduce1_data(ip, rend_circ->build_state->chosen_exit, + subcredential, &intro1_data); + + /* Final step before we encode a cell, we setup the circuit identifier which + * will generate both the rendezvous cookie and client keypair for this + * connection. Those are put in the ident. */ + intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie; + intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp; + + memcpy(intro_circ->hs_ident->rendezvous_cookie, + rend_circ->hs_ident->rendezvous_cookie, + sizeof(intro_circ->hs_ident->rendezvous_cookie)); + + /* From the introduce1 data object, this will encode the INTRODUCE1 cell + * into payload which is then ready to be sent as is. */ + payload_len = hs_cell_build_introduce1(&intro1_data, payload); + if (BUG(payload_len < 0)) { + goto done; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ), + RELAY_COMMAND_INTRODUCE1, + (const char *) payload, payload_len, + intro_circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.", + TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Success. */ + ret = 0; + goto done; + + done: + /* Object in this list have been moved to the cell object when building it + * so they've been freed earlier. We do that in order to avoid duplicating + * them leading to more memory and CPU time being used for nothing. */ + smartlist_free(intro1_data.link_specifiers); + memwipe(&intro1_data, 0, sizeof(intro1_data)); + memwipe(payload, 0, sizeof(payload)); + return ret; +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 9e359394e8..f35ebf17db 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -44,6 +44,10 @@ int hs_circ_handle_introduce2(const hs_service_t *service, hs_service_intro_point_t *ip, const uint8_t *subcredential, const uint8_t *payload, size_t payload_len); +int hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential); /* e2e circuit API. */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 514ecf99ba..3f951a21af 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -10,13 +10,48 @@ #include "hs_circuit.h" #include "hs_ident.h" #include "connection_edge.h" +#include "container.h" #include "rendclient.h" #include "hs_descriptor.h" #include "hs_cache.h" +#include "hs_cell.h" +#include "hs_ident.h" #include "config.h" #include "directory.h" #include "hs_client.h" #include "router.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "connection.h" +#include "circpathbias.h" + +/* Get all connections that are waiting on a circuit and flag them back to + * waiting for a hidden service descriptor for the given service key + * service_identity_pk. */ +static void +flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) +{ + tor_assert(service_identity_pk); + + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + edge_connection_t *edge_conn; + if (BUG(!CONN_IS_EDGE(conn))) { + continue; + } + edge_conn = TO_EDGE_CONN(conn); + if (edge_conn->hs_ident && + ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + service_identity_pk)) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); + conn->state = AP_CONN_STATE_RENDDESC_WAIT; + } + } SMARTLIST_FOREACH_END(conn); + + smartlist_free(conns); +} /* A v3 HS circuit successfully connected to the hidden service. Update the * stream state at hs_conn_ident appropriately. */ @@ -140,11 +175,10 @@ fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk) return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); } -#if 0 /* Make sure that the given origin circuit circ is a valid correct * introduction circuit. This asserts on validation failure. */ static void -assert_intro_circ(const origin_circuit_t *circ) +assert_intro_circ_ok(const origin_circuit_t *circ) { tor_assert(circ); tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); @@ -152,7 +186,131 @@ assert_intro_circ(const origin_circuit_t *circ) tor_assert(hs_ident_intro_circ_is_valid(circ->hs_ident)); assert_circ_anonymity_ok(circ, get_options()); } -#endif + +/* Find a descriptor intro point object that matches the given ident in the + * given descriptor desc. Return NULL if not found. */ +static const hs_desc_intro_point_t * +find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, + const hs_descriptor_t *desc) +{ + const hs_desc_intro_point_t *intro_point = NULL; + + tor_assert(ident); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (ed25519_pubkey_eq(&ident->intro_auth_pk, + &ip->auth_key_cert->signed_key)) { + intro_point = ip; + break; + } + } SMARTLIST_FOREACH_END(ip); + + return intro_point; +} + +/* Send an INTRODUCE1 cell along the intro circuit and populate the rend + * circuit identifier with the needed key material for the e2e encryption. + * Return 0 on success, -1 if there is a transient error such that an action + * has been taken to recover and -2 if there is a permanent error indicating + * that both circuits were closed. */ +static int +send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + int status; + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const ed25519_public_key_t *service_identity_pk = NULL; + const hs_desc_intro_point_t *ip; + + assert_intro_circ_ok(intro_circ); + tor_assert(rend_circ); + + service_identity_pk = &intro_circ->hs_ident->identity_pk; + /* For logging purposes. There will be a time where the hs_ident will have a + * version number but for now there is none because it's all v3. */ + hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address); + + log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u", + safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id); + + /* 1) Get descriptor from our cache. */ + const hs_descriptor_t *desc = + hs_cache_lookup_as_client(service_identity_pk); + if (desc == NULL || !hs_client_any_intro_points_usable(desc)) { + log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.", + safe_str_client(onion_address), + (desc) ? "didn't have usable intro points" : + "didn't have a descriptor"); + hs_client_refetch_hsdesc(service_identity_pk); + /* We just triggered a refetch, make sure every connections are back + * waiting for that descriptor. */ + flag_all_conn_wait_desc(service_identity_pk); + /* We just asked for a refetch so this is a transient error. */ + goto tran_err; + } + + /* We need to find which intro point in the descriptor we are connected to + * on intro_circ. */ + ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); + if (BUG(ip == NULL)) { + /* If we can find a descriptor from this introduction circuit ident, we + * must have a valid intro point object. Permanent error. */ + goto perm_err; + } + + /* Send the INTRODUCE1 cell. */ + if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, + desc->subcredential) < 0) { + /* Unable to send the cell, the intro circuit has been marked for close so + * this is a permanent error. */ + tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close); + goto perm_err; + } + + /* Cell has been sent successfully. Copy the introduction point + * authentication and encryption key in the rendezvous circuit identifier so + * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */ + memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key, + sizeof(rend_circ->hs_ident->intro_enc_pk)); + ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk, + &intro_circ->hs_ident->intro_auth_pk); + + /* Now, we wait for an ACK or NAK on this circuit. */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */ + TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(intro_circ); + + /* Success. */ + status = 0; + goto end; + + perm_err: + /* Permanent error: it is possible that the intro circuit was closed prior + * because we weren't able to send the cell. Make sure we don't double close + * it which would result in a warning. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL); + } + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL); + status = -2; + goto end; + + tran_err: + status = -1; + + end: + memwipe(onion_address, 0, sizeof(onion_address)); + return status; +} + +/* ========== */ +/* Public API */ +/* ========== */ /** A circuit just finished connecting to a hidden service that the stream * conn has been waiting for. Let the HS subsystem know about this. */ @@ -256,3 +414,19 @@ hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) return fetch_v3_desc(identity_pk); } +/* This is called when we are trying to attach an AP connection to these + * hidden service circuits from connection_ap_handshake_attach_circuit(). + * Return 0 on success, -1 for a transient error that is actions were + * triggered to recover or -2 for a permenent error where both circuits will + * marked for close. + * + * The following supports every hidden service version. */ +int +hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) : + rend_client_send_introduction(intro_circ, + rend_circ); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 0b446c00bf..d8348ddb3f 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -22,5 +22,8 @@ int hs_client_decode_descriptor( int hs_client_any_intro_points_usable(const hs_descriptor_t *desc); int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); +int hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ); + #endif /* TOR_HS_CLIENT_H */ From 6eb125d14bf69e9dad427fe4b1005477177acc04 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 13:56:10 -0400 Subject: [PATCH 27/62] prop224: Client has opened circuit logic Make a single entry point for the entire HS subsystem when a client circuit opens (every HS version). Signed-off-by: David Goulet --- src/or/circuituse.c | 4 +- src/or/hs_client.c | 106 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 2 + 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 21cc9c540f..5b35155ee3 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1636,7 +1636,7 @@ circuit_has_opened(origin_circuit_t *circ) switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - rend_client_rendcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); /* Start building an intro circ if we don't have one yet. */ connection_ap_attach_pending(1); /* This isn't a call to circuit_try_attaching_streams because a @@ -1648,7 +1648,7 @@ circuit_has_opened(origin_circuit_t *circ) * state. */ break; case CIRCUIT_PURPOSE_C_INTRODUCING: - rend_client_introcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_C_GENERAL: /* Tell any AP connections that have been waiting for a new diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 3f951a21af..b06f3d9355 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -308,6 +308,82 @@ send_introduce1(origin_circuit_t *intro_circ, return status; } +/* Using the introduction circuit circ, setup the authentication key of the + * intro point this circuit has extended to. */ +static void +setup_intro_circ_auth_key(origin_circuit_t *circ) +{ + const hs_descriptor_t *desc; + + tor_assert(circ); + + desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* Opening intro circuit without the descriptor is no good... */ + goto end; + } + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + /* Not all tor node have an ed25519 identity key so we still rely on the + * legacy identity digest. */ + if (lspec->type != LS_LEGACY_ID) { + continue; + } + if (fast_memneq(circ->build_state->chosen_exit->identity_digest, + lspec->u.legacy_id, DIGEST_LEN)) { + break; + } + /* We got it, copy its authentication key to the identifier. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_cert->signed_key); + goto end; + } SMARTLIST_FOREACH_END(lspec); + } SMARTLIST_FOREACH_END(ip); + + /* Reaching this point means we didn't find any intro point for this circuit + * which is not suppose to happen. */ + tor_assert_nonfatal_unreached(); + + end: + return; +} + +/* Called when an introduction circuit has opened. */ +static void +client_intro_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + + /* This is an introduction circuit so we'll attach the correct + * authentication key to the circuit identifier so it can be identified + * properly later on. */ + setup_intro_circ_auth_key(circ); + + connection_ap_attach_pending(1); +} + +/* Called when a rendezvous circuit has opened. */ +static void +client_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + log_info(LD_REND, "Rendezvous circuit has opened to %s.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + + /* XXX Send ESTABLISH REND cell. */ +} + /* ========== */ /* Public API */ /* ========== */ @@ -430,3 +506,33 @@ hs_client_send_introduce1(origin_circuit_t *intro_circ, rend_circ); } +/* Called when the client circuit circ has been established. It can be either + * an introduction or rendezvous circuit. This function handles all hidden + * service versions. */ +void +hs_client_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_C_INTRODUCING: + if (circ->hs_ident) { + client_intro_circ_has_opened(circ); + } else { + rend_client_introcirc_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: + if (circ->hs_ident) { + client_rendezvous_circ_has_opened(circ); + } else { + rend_client_rendcirc_has_opened(circ); + } + break; + default: + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index d8348ddb3f..a716fc02e4 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -25,5 +25,7 @@ int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); int hs_client_send_introduce1(origin_circuit_t *intro_circ, origin_circuit_t *rend_circ); +void hs_client_circuit_has_opened(origin_circuit_t *circ); + #endif /* TOR_HS_CLIENT_H */ From b91693f7c3099c691d0e5dbb60ac55df14b7bd68 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 13:57:10 -0400 Subject: [PATCH 28/62] prop224: Build ESTABLISH_RENDEZVOUS cell and logic Add a function to build the cell. Add a the logic to send the cell when the rendezvous circuit opens. Signed-off-by: David Goulet --- src/or/hs_cell.c | 17 +++++++++++++++ src/or/hs_cell.h | 2 ++ src/or/hs_circuit.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_circuit.h | 1 + src/or/hs_client.c | 4 +++- 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 889cf7749b..64e164c061 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -847,3 +847,20 @@ hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, return cell_len; } +/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The + * encoded cell is put in cell_out which must be of at least + * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the + * caller should clear up the content of the cell. + * + * This function can't fail. */ +ssize_t +hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out) +{ + tor_assert(rendezvous_cookie); + tor_assert(cell_out); + + memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN); + return HS_REND_COOKIE_LEN; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index a72009510c..29e451cf0f 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -93,6 +93,8 @@ ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, uint8_t *cell_out); ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, uint8_t *cell_out); +ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out); /* Parse cell API. */ ssize_t hs_cell_parse_intro_established(const uint8_t *payload, diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 95100e9b3a..ee453d0d52 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -1084,3 +1084,54 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, return ret; } +/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On + * success, 0 is returned else -1 and the circuit is marked for close. */ +int +hs_circ_send_establish_rendezvous(origin_circuit_t *circ) +{ + ssize_t cell_len = 0; + uint8_t cell[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u", + TO_CIRCUIT(circ)->n_circ_id); + + /* Set timestamp_dirty, because circuit_expire_building expects it, + * and the rend cookie also means we've used the circ. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* We've attempted to use this circuit. Probe it if we fail */ + pathbias_count_use_attempt(circ); + + /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can + * complete the handshake when receiving the acknowledgement. */ + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN); + /* Generate the client keypair. No need to be extra strong, not long term */ + curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0); + + cell_len = + hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie, + cell); + if (BUG(cell_len < 0)) { + goto err; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_RENDEZVOUS, + (const char *) cell, cell_len, + circ->cpath->prev) < 0) { + /* Circuit has been marked for close */ + log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on " + "circuit %u", TO_CIRCUIT(circ)->n_circ_id); + memwipe(cell, 0, cell_len); + goto err; + } + + memwipe(cell, 0, cell_len); + return 0; + err: + return -1; +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index f35ebf17db..3b0e3aca1c 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -48,6 +48,7 @@ int hs_circ_send_introduce1(origin_circuit_t *intro_circ, origin_circuit_t *rend_circ, const hs_desc_intro_point_t *ip, const uint8_t *subcredential); +int hs_circ_send_establish_rendezvous(origin_circuit_t *circ); /* e2e circuit API. */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index b06f3d9355..8865bb5fb5 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -381,7 +381,9 @@ client_rendezvous_circ_has_opened(origin_circuit_t *circ) safe_str_client( extend_info_describe(circ->build_state->chosen_exit))); - /* XXX Send ESTABLISH REND cell. */ + /* Ignore returned value, nothing we can really do. On failure, the circuit + * will be marked for close. */ + hs_circ_send_establish_rendezvous(circ); } /* ========== */ From 89eb96c19a091b1e892e4a7c05f06e188131aed0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 14:20:37 -0400 Subject: [PATCH 29/62] prop224: Handle client RENDEZVOUS_ESTABLISHED cell Client now handles a RENDEZVOUS_ESTABLISHED cell when it arrives on the rendezvous circuit. This new function applies for both the legacy system and prop224. Signed-off-by: David Goulet --- src/or/hs_client.c | 42 ++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 4 ++++ src/or/rendcommon.c | 3 ++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 8865bb5fb5..2674e2c1e7 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -538,3 +538,45 @@ hs_client_circuit_has_opened(origin_circuit_t *circ) } } +/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of + * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a + * negative value and the circuit marked for close. */ +int +hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + tor_assert(circ); + tor_assert(payload); + + (void) payload_len; + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not " + "expecting one. Closing circuit."); + goto err; + } + + log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is " + "now ready for rendezvous."); + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); + + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_REND_READY state. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* From a path bias point of view, this circuit is now successfully used. + * Waiting any longer opens us up to attacks from malicious hidden services. + * They could induce the client to attempt to connect to their hidden + * service and never reply to the client's rend requests */ + pathbias_mark_use_success(circ); + + /* If we already have the introduction circuit built, make sure we send + * the INTRODUCE cell _now_ */ + connection_ap_attach_pending(1); + + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index a716fc02e4..0f82a830f4 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -27,5 +27,9 @@ int hs_client_send_introduce1(origin_circuit_t *intro_circ, void hs_client_circuit_has_opened(origin_circuit_t *circ); +int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + #endif /* TOR_HS_CLIENT_H */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index a6b59881ad..7e5ba6b6f6 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -19,6 +19,7 @@ #include "rendcommon.h" #include "rendmid.h" #include "hs_intropoint.h" +#include "hs_client.h" #include "rendservice.h" #include "rephist.h" #include "router.h" @@ -797,7 +798,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = rend_client_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); break; default: tor_fragile_assert(); From c38144bb0f1a38cd4d72b60efd038aab63182b8b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 14:32:47 -0400 Subject: [PATCH 30/62] prop224: Client callback when descriptor has arrived When a descriptor fetch has completed and it has been successfully stored in the client cache, this callback will take appropriate actions to attach streams and/or launch neede circuits to connect to the service. Signed-off-by: David Goulet --- src/or/directory.c | 1 + src/or/hs_client.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 3 +++ 3 files changed, 67 insertions(+) diff --git a/src/or/directory.c b/src/or/directory.c index 9268c3ca2f..f3e76be62f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -3103,6 +3103,7 @@ handle_response_fetch_hsdesc_v3(dir_connection_t *conn, } else { log_info(LD_REND, "Stored hidden service descriptor successfully."); TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; + hs_client_desc_has_arrived(conn->hs_ident); } break; case 404: diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 2674e2c1e7..cf9bdba5a4 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -24,6 +24,7 @@ #include "circuituse.h" #include "connection.h" #include "circpathbias.h" +#include "connection.h" /* Get all connections that are waiting on a circuit and flag them back to * waiting for a hidden service descriptor for the given service key @@ -580,3 +581,65 @@ hs_client_receive_rendezvous_acked(origin_circuit_t *circ, return -1; } +/* This is called when a descriptor has arrived following a fetch request and + * has been stored in the client cache. Every entry connection that matches + * the service identity key in the ident will get attached to the hidden + * service circuit. */ +void +hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) +{ + time_t now = time(NULL); + smartlist_t *conns = NULL; + + tor_assert(ident); + + conns = connection_list_by_type_state(CONN_TYPE_AP, + AP_CONN_STATE_RENDDESC_WAIT); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const hs_descriptor_t *desc; + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we just fetched its descriptor. */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(&ident->identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + + /* We were just called because we stored the descriptor for this service + * so not finding a descriptor means we have a bigger problem. */ + desc = hs_cache_lookup_as_client(&ident->identity_pk); + if (BUG(desc == NULL)) { + goto end; + } + + if (!hs_client_any_intro_points_usable(desc)) { + log_info(LD_REND, "Hidden service descriptor is unusable. " + "Closing streams."); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_RESOLVEFAILED); + /* XXX: Note the connection attempt. */ + goto end; + } + + log_info(LD_REND, "Descriptor has arrived. Launching circuits."); + + /* Restart their timeout values, so they get a fair shake at connecting to + * the hidden service. XXX: Improve comment on why this is needed. */ + base_conn->timestamp_created = now; + base_conn->timestamp_lastread = now; + base_conn->timestamp_lastwritten = now; + /* Change connection's state into waiting for a circuit. */ + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + + connection_ap_mark_as_pending_circuit(entry_conn); + } SMARTLIST_FOREACH_END(base_conn); + + end: + /* We don't have ownership of the objects in this list. */ + smartlist_free(conns); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 0f82a830f4..50d0ad7601 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -11,6 +11,7 @@ #include "crypto_ed25519.h" #include "hs_descriptor.h" +#include "hs_ident.h" void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); @@ -31,5 +32,7 @@ int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); + #endif /* TOR_HS_CLIENT_H */ From abb840bf6431cfeb4f5e32ae2774fcde29e7724d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 15:19:32 -0400 Subject: [PATCH 31/62] prop224: Client function to pick intro point From an edge connection object, add a function that randomly pick an introduction point for the requested service. This follows the code design of rend_client_get_random_intro() and returns an extend_info_t object ready to be used to extend to. At this commit, it is not used yet. Signed-off-by: David Goulet --- src/or/hs_client.c | 117 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 4 +- 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index cf9bdba5a4..a593999f26 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -20,6 +20,7 @@ #include "directory.h" #include "hs_client.h" #include "router.h" +#include "routerset.h" #include "circuitlist.h" #include "circuituse.h" #include "connection.h" @@ -387,6 +388,109 @@ client_rendezvous_circ_has_opened(origin_circuit_t *circ) hs_circ_send_establish_rendezvous(circ); } +/* This is an helper function that convert a descriptor intro point object ip + * to a newly allocated extend_info_t object fully initialized. Return NULL if + * we can't convert it for which chances are that we are missing or malformed + * link specifiers. */ +static extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) +{ + extend_info_t *ei; + smartlist_t *lspecs = smartlist_new(); + + tor_assert(ip); + + /* We first encode the descriptor link specifiers into the binary + * representation which is a trunnel object. */ + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, desc_lspec) { + link_specifier_t *lspec = hs_desc_encode_lspec(desc_lspec); + smartlist_add(lspecs, lspec); + } SMARTLIST_FOREACH_END(desc_lspec); + + /* Explicitely put the direct connection option to 0 because this is client + * side and there is no such thing as a non anonymous client. */ + ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0); + + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls)); + smartlist_free(lspecs); + return ei; +} + +/* Using a descriptor desc, return a newly allocated extend_info_t object of a + * randomly picked introduction point from its list. Return NULL if none are + * usable. */ +static extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk) +{ + extend_info_t *ei = NULL, *ei_excluded = NULL; + smartlist_t *usable_ips = NULL; + const hs_descriptor_t *desc; + const hs_desc_encrypted_data_t *enc_data; + const or_options_t *options = get_options(); + + tor_assert(service_pk); + + desc = hs_cache_lookup_as_client(service_pk); + if (desc == NULL || !hs_client_any_intro_points_usable(desc)) { + log_info(LD_REND, "Unable to randomly select an introduction point " + "because descriptor %s.", + (desc) ? "doesn't have usable intro point" : "is missing"); + goto end; + } + + enc_data = &desc->encrypted_data; + usable_ips = smartlist_new(); + smartlist_add_all(usable_ips, enc_data->intro_points); + while (smartlist_len(usable_ips) != 0) { + int idx; + const hs_desc_intro_point_t *ip; + + /* Pick a random intro point and immediately remove it from the usable + * list so we don't pick it again if we have to iterate more. */ + idx = crypto_rand_int(smartlist_len(usable_ips)); + ip = smartlist_get(usable_ips, idx); + smartlist_del(usable_ips, idx); + + /* Generate an extend info object from the intro point object. */ + ei = desc_intro_point_to_extend_info(ip); + if (ei == NULL) { + /* We can get here for instance if the intro point is a private address + * and we aren't allowed to extend to those. */ + continue; + } + + /* Test the pick against ExcludeNodes. */ + if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) { + /* If this pick is in the ExcludeNodes list, we keep its reference so if + * we ever end up not being able to pick anything else and StrictNodes is + * unset, we'll use it. */ + ei_excluded = ei; + continue; + } + /* XXX: Intro point can time out or just be unsuable, we need to keep + * track of this and check against such cache. */ + + /* Good pick! Let's go with this. */ + goto end; + } + + /* Reaching this point means a couple of things. Either we can't use any of + * the intro point listed because the IP address can't be extended to or it + * is listed in the ExcludeNodes list. In the later case, if StrictNodes is + * set, we are forced to not use anything. */ + ei = ei_excluded; + if (options->StrictNodes) { + log_warn(LD_REND, "Every introduction points are in the ExcludeNodes set " + "and StrictNodes is set. We can't connect."); + ei = NULL; + } + + end: + smartlist_free(usable_ips); + return ei; +} + /* ========== */ /* Public API */ /* ========== */ @@ -643,3 +747,16 @@ hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) smartlist_free(conns); } +/* Return a newly allocated extend_info_t for a randomly chosen introduction + * point for the given edge connection identifier ident. Return NULL if we + * can't pick any usable introduction points. */ +extend_info_t * +hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) +{ + tor_assert(edge_conn); + + return (edge_conn->hs_ident) ? + client_get_random_intro(&edge_conn->hs_ident->identity_pk) : + rend_client_get_random_intro(edge_conn->rend_data); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 50d0ad7601..c41baa2ce3 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -31,8 +31,10 @@ void hs_client_circuit_has_opened(origin_circuit_t *circ); int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); - void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); +extend_info_t *hs_client_get_random_intro_from_edge( + const edge_connection_t *edge_conn); + #endif /* TOR_HS_CLIENT_H */ From e7c06e694766c6e2fb1f06ed5e1bf9c216e7a976 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 16:31:27 -0400 Subject: [PATCH 32/62] prop224: Make client send INTRODUCE1 cell Signed-off-by: David Goulet --- src/or/circuituse.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 5b35155ee3..93bccf0eb8 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -2174,22 +2174,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* If this is a hidden service trying to start an introduction point, * handle that case. */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); /* need to pick an intro point */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; - tor_assert(rend_data); - extend_info = rend_client_get_random_intro(rend_data); + extend_info = hs_client_get_random_intro_from_edge(edge_conn); if (!extend_info) { - log_info(LD_REND, - "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(rend_data_get_address(rend_data))); - rend_client_refetch_v2_renddesc(rend_data); + log_info(LD_REND, "No intro points: re-fetching service descriptor."); + if (edge_conn->rend_data) { + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + } else { + hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + } connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(rend_data_get_address(rend_data))); + (edge_conn->rend_data) ? + safe_str_client(rend_data_get_address(edge_conn->rend_data)) : + "service"); } /* If we have specified a particular exit node for our @@ -2308,8 +2311,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* help predict this next time */ rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { - /* write the service_id into circ */ - circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + if (edge_conn->rend_data) { + /* write the service_id into circ */ + circ->rend_data = rend_data_dup(edge_conn->rend_data); + } else if (edge_conn->hs_ident) { + circ->hs_ident = + hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk, + HS_IDENT_CIRCUIT_INTRO); + } if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->base_.state == CIRCUIT_STATE_OPEN) circuit_has_opened(circ); @@ -2737,12 +2747,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { + int ret; log_info(LD_REND,"found open intro circ %u (rend %u); sending " "introduction. (stream %d sec old)", (unsigned)introcirc->base_.n_circ_id, (unsigned)rendcirc->base_.n_circ_id, conn_age); - switch (rend_client_send_introduction(introcirc, rendcirc)) { + ret = hs_client_send_introduce1(introcirc, rendcirc); + switch (ret) { case 0: /* success */ rendcirc->base_.timestamp_dirty = time(NULL); introcirc->base_.timestamp_dirty = time(NULL); From cb336a7062f87c5c306549a4f4a26eab66c5b825 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 17:06:04 -0400 Subject: [PATCH 33/62] prop224: Parse INTRODUCE_ACK cell Add a function to parse an INTRODUCE_ACK cell in hs_cell.c. Furthermore, add an enum that lists all possible expected status code. Signed-off-by: David Goulet --- src/or/hs_cell.c | 23 +++++++++++++++++++++++ src/or/hs_cell.h | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 64e164c061..1f9df825f0 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -864,3 +864,26 @@ hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, return HS_REND_COOKIE_LEN; } +/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. + * Return the status code on success else a negative value if the cell as not + * decodable. */ +int +hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + trn_cell_introduce_ack_t *cell = NULL; + + tor_assert(payload); + + if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it."); + goto end; + } + + ret = trn_cell_introduce_ack_get_status(cell); + + end: + trn_cell_introduce_ack_free(cell); + return ret; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 29e451cf0f..606a08dd66 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -16,6 +16,14 @@ * 3.2.2 of the specification). Below this value, the cell must be padded. */ #define HS_CELL_INTRODUCE1_MIN_SIZE 246 +/* Status code of an INTRODUCE_ACK cell. */ +typedef enum { + HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */ + HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */ + HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */ + HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */ +} hs_cell_introd_ack_status_t; + /* Onion key type found in the INTRODUCE1 cell. */ typedef enum { HS_CELL_ONION_KEY_TYPE_NTOR = 1, @@ -102,6 +110,7 @@ ssize_t hs_cell_parse_intro_established(const uint8_t *payload, ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, const origin_circuit_t *circ, const hs_service_t *service); +int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len); #endif /* TOR_HS_CELL_H */ From fca2f64e2f563c07e2d5467adc49914bc4545e36 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 21 Jul 2017 17:48:18 -0400 Subject: [PATCH 34/62] prop224: Handle INTRODUCE_ACK cell The client is now able to handle an INTRODUCE_ACK cell and do the appropriate actions. An intro point failure cache is missing and a way to close all intro point that were launched in parallel. Some notes are in the comment for that. Signed-off-by: David Goulet --- src/or/circuitlist.c | 27 ++++++++++ src/or/circuitlist.h | 3 ++ src/or/hs_cell.c | 12 +++++ src/or/hs_cell.h | 3 ++ src/or/hs_client.c | 121 +++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 4 ++ src/or/rendclient.c | 12 ----- src/or/rendcommon.c | 2 +- 8 files changed, 171 insertions(+), 13 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index d891c89f38..c2d947beae 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1498,6 +1498,33 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) return NULL; } +/* Return an origin circuit such that: + * - Identifier identity key matches, + * - Rendezvous cookie matches + * - Circuit is not marked for close + * - Circuit has purpose CIRCUIT_PURPOSE_C_REND_READY. + * + * Return NULL if no such circuit exits. */ +origin_circuit_t * +circuit_get_ready_rend_by_hs_ident(const hs_ident_circuit_t *ident) +{ + SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { + if (!circ->marked_for_close && + circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) { + origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); + if (ocirc->hs_ident && + ed25519_pubkey_eq(&ident->identity_pk, + ô->hs_ident->identity_pk) && + tor_memeq(ident->rendezvous_cookie, + ocirc->hs_ident->rendezvous_cookie, + HS_REND_COOKIE_LEN)) { + return ocirc; + } + } + } SMARTLIST_FOREACH_END(circ); + return NULL; +} + /** Return the first service introduction circuit originating from the global * circuit list after start or at the start of the list if start * is NULL. Return NULL if no circuit is found. diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 048cd5f763..f2a3c43134 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -13,6 +13,7 @@ #define TOR_CIRCUITLIST_H #include "testsupport.h" +#include "hs_ident.h" MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); smartlist_t *circuit_get_global_origin_circuit_list(void); @@ -45,6 +46,8 @@ void circuit_unlink_all_from_channel(channel_t *chan, int reason); origin_circuit_t *circuit_get_by_global_id(uint32_t id); origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( const rend_data_t *rend_data); +origin_circuit_t *circuit_get_ready_rend_by_hs_ident( + const hs_ident_circuit_t *ident); 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); diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 1f9df825f0..c5ea6e4db7 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -875,6 +875,18 @@ hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) tor_assert(payload); + /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a + * NACK is 1 byte. We can't use the legacy function for this so we have to + * do a special case. */ + if (payload_len <= 1) { + if (payload_len == 0) { + ret = HS_CELL_INTRO_ACK_SUCCESS; + } else { + ret = HS_CELL_INTRO_ACK_FAILURE; + } + goto end; + } + if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) { log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it."); goto end; diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 606a08dd66..14dd31c9de 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -111,6 +111,9 @@ ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, const origin_circuit_t *circ, const hs_service_t *service); int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len); +int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, + size_t handshake_info_len); #endif /* TOR_HS_CELL_H */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index a593999f26..ff3d12748c 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -491,6 +491,99 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) return ei; } +/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate + * actions for the rendezvous point and finally close intro_circ. */ +static void +handle_introduce_ack_success(origin_circuit_t *intro_circ) +{ + origin_circuit_t *rend_circ = NULL; + + tor_assert(intro_circ); + + log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous"); + + /* Get the rendezvous circuit matching this intro point circuit. + * XXX Replace this by our hs circuitmap to support client? */ + rend_circ = circuit_get_ready_rend_by_hs_ident(intro_circ->hs_ident); + if (rend_circ == NULL) { + log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping"); + goto end; + } + + assert_circ_anonymity_ok(rend_circ, get_options()); + circuit_change_purpose(TO_CIRCUIT(rend_circ), + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the + * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */ + TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL); + + end: + /* We don't need the intro circuit anymore. It did what it had to do! */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + + /* XXX: Close pending intro circuits we might have in parallel. */ + return; +} + +/* Called when we get an INTRODUCE_ACK failure status code. Depending on our + * failure cache status, either close the circuit or re-extend to a new + * introduction point. */ +static void +handle_introduce_ack_bad(origin_circuit_t *circ, int status) +{ + tor_assert(circ); + + log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u", + safe_str_client(extend_info_describe(circ->build_state->chosen_exit)), + status); + + /* It's a NAK. The introduction point didn't relay our request. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); + + /* XXX: Report this failure for the intro point failure cache. Depending on + * how many times we've tried this intro point, close it or reextend. */ +} + +/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded + * cell is in payload of length payload_len. Return 0 on success else a + * negative value. The circuit is either close or reuse to re-extend to a new + * introduction point. */ +static int +handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int status, ret = -1; + + tor_assert(circ); + tor_assert(circ->build_state); + tor_assert(circ->build_state->chosen_exit); + assert_circ_anonymity_ok(circ, get_options()); + tor_assert(payload); + + status = hs_cell_parse_introduce_ack(payload, payload_len); + switch (status) { + case HS_CELL_INTRO_ACK_SUCCESS: + ret = 0; + handle_introduce_ack_success(circ); + break; + case HS_CELL_INTRO_ACK_FAILURE: + case HS_CELL_INTRO_ACK_BADFMT: + case HS_CELL_INTRO_ACK_NORELAY: + handle_introduce_ack_bad(circ, status); + break; + default: + log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s", + status, + safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); + break; + } + + return ret; +} + /* ========== */ /* Public API */ /* ========== */ @@ -759,4 +852,32 @@ hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) client_get_random_intro(&edge_conn->hs_ident->identity_pk) : rend_client_get_random_intro(edge_conn->rend_data); } +/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ. + * Return 0 on success else a negative value is returned. The circuit will be + * closed or reuse to extend again to another intro point. */ +int +hs_client_receive_introduce_ack(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_C_INTRODUCE_ACK_WAIT) { + log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) : + rend_client_introduction_acked(circ, payload, + payload_len); + /* For path bias: This circuit was used successfully. NACK or ACK counts. */ + pathbias_mark_use_success(circ); + + end: + return ret; +} diff --git a/src/or/hs_client.h b/src/or/hs_client.h index c41baa2ce3..1c0f8a4d2a 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -31,6 +31,10 @@ void hs_client_circuit_has_opened(origin_circuit_t *circ); int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +int hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); extend_info_t *hs_client_get_random_intro_from_edge( diff --git a/src/or/rendclient.c b/src/or/rendclient.c index a6fb88624d..683a3916ec 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -391,23 +391,11 @@ rend_client_introduction_acked(origin_circuit_t *circ, origin_circuit_t *rendcirc; (void) request; // XXXX Use this. - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { - log_warn(LD_PROTOCOL, - "Received REND_INTRODUCE_ACK on unexpected circuit %u.", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - tor_assert(circ->build_state); tor_assert(circ->build_state->chosen_exit); assert_circ_anonymity_ok(circ, options); tor_assert(circ->rend_data); - /* For path bias: This circuit was used successfully. Valid - * nacks and acks count. */ - pathbias_mark_use_success(circ); - if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ /* Locate the rend circ which is waiting to hear about this ack, diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 7e5ba6b6f6..86dc913cd0 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -782,7 +782,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) - r = rend_client_introduction_acked(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) From 2f1b3d647ff1489f4cb0f25d96701eaf35e076da Mon Sep 17 00:00:00 2001 From: David Goulet Date: Sun, 23 Jul 2017 15:04:59 -0400 Subject: [PATCH 35/62] trunnel: Add RENDEZVOUS2 cell definition Signed-off-by: David Goulet --- src/trunnel/hs/cell_rendezvous.c | 178 +++++++++++++++++++++++++ src/trunnel/hs/cell_rendezvous.h | 69 ++++++++++ src/trunnel/hs/cell_rendezvous.trunnel | 13 +- 3 files changed, 259 insertions(+), 1 deletion(-) diff --git a/src/trunnel/hs/cell_rendezvous.c b/src/trunnel/hs/cell_rendezvous.c index e961cd09d4..e4d3244bd1 100644 --- a/src/trunnel/hs/cell_rendezvous.c +++ b/src/trunnel/hs/cell_rendezvous.c @@ -290,3 +290,181 @@ trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input } return result; } +trn_cell_rendezvous2_t * +trn_cell_rendezvous2_new(void) +{ + trn_cell_rendezvous2_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous2_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_rendezvous2_clear(trn_cell_rendezvous2_t *obj) +{ + (void) obj; +} + +void +trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *obj) +{ + if (obj == NULL) + return; + trn_cell_rendezvous2_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_rendezvous2_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp) +{ + (void)inp; return TRUNNEL_HANDSHAKE_INFO_LEN; +} + +uint8_t +trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx) +{ + trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN); + return inp->handshake_info[idx]; +} + +uint8_t +trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx) +{ + return trn_cell_rendezvous2_get_handshake_info((trn_cell_rendezvous2_t*)inp, idx); +} +int +trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN); + inp->handshake_info[idx] = elt; + return 0; +} + +uint8_t * +trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp) +{ + return inp->handshake_info; +} +const uint8_t * +trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous2_getarray_handshake_info((trn_cell_rendezvous2_t*)inp); +} +const char * +trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_rendezvous2_check(obj)) + return -1; + + + /* Length of u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + result += TRUNNEL_HANDSHAKE_INFO_LEN; + return result; +} +int +trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_rendezvous2_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous2_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_rendezvous2_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_rendezvous2_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + trunnel_assert(written <= avail); + if (avail - written < TRUNNEL_HANDSHAKE_INFO_LEN) + goto truncated; + memcpy(ptr, obj->handshake_info, TRUNNEL_HANDSHAKE_INFO_LEN); + written += TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_rendezvous2_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_rendezvous2_parse_into(trn_cell_rendezvous2_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + CHECK_REMAINING(TRUNNEL_HANDSHAKE_INFO_LEN, truncated); + memcpy(obj->handshake_info, ptr, TRUNNEL_HANDSHAKE_INFO_LEN); + remaining -= TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_rendezvous2_new(); + if (NULL == *output) + return -1; + result = trn_cell_rendezvous2_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_rendezvous2_free(*output); + *output = NULL; + } + return result; +} diff --git a/src/trunnel/hs/cell_rendezvous.h b/src/trunnel/hs/cell_rendezvous.h index 2387d77f4f..5dcc6539b6 100644 --- a/src/trunnel/hs/cell_rendezvous.h +++ b/src/trunnel/hs/cell_rendezvous.h @@ -9,6 +9,7 @@ #include "trunnel.h" #define TRUNNEL_REND_COOKIE_LEN 20 +#define TRUNNEL_HANDSHAKE_INFO_LEN 64 #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1) struct trn_cell_rendezvous1_st { uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN]; @@ -17,6 +18,13 @@ struct trn_cell_rendezvous1_st { }; #endif typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS2) +struct trn_cell_rendezvous2_st { + uint8_t handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN]; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_rendezvous2_st trn_cell_rendezvous2_t; /** Return a newly allocated trn_cell_rendezvous1 with all elements * set to zero. */ @@ -113,6 +121,67 @@ const uint8_t * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cel * failure. */ int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_rendezvous2 with all elements + * set to zero. + */ +trn_cell_rendezvous2_t *trn_cell_rendezvous2_new(void); +/** Release all storage held by the trn_cell_rendezvous2 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *victim); +/** Try to parse a trn_cell_rendezvous2 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_rendezvous2_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_rendezvous2 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj); +/** Try to encode the trn_cell_rendezvous2 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t trn_cell_rendezvous2_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous2_t *input); +/** Check whether the internal state of the trn_cell_rendezvous2 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj); +/** Return the (constant) length of the array holding the + * handshake_info field of the trn_cell_rendezvous2_t in 'inp'. + */ +size_t trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp); +/** Return the element at position 'idx' of the fixed array field + * handshake_info of the trn_cell_rendezvous2_t in 'inp'. + */ +uint8_t trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx); +/** As trn_cell_rendezvous2_get_handshake_info, but take and return a + * const pointer + */ +uint8_t trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * handshake_info of the trn_cell_rendezvous2_t in 'inp', so that it + * will hold the value 'elt'. + */ +int trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the TRUNNEL_HANDSHAKE_INFO_LEN-element array + * field handshake_info of 'inp'. + */ +uint8_t * trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp); +/** As trn_cell_rendezvous2_get_handshake_info, but take and return a + * const pointer + */ +const uint8_t * trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp); #endif diff --git a/src/trunnel/hs/cell_rendezvous.trunnel b/src/trunnel/hs/cell_rendezvous.trunnel index 27f1728b4a..2128b4d126 100644 --- a/src/trunnel/hs/cell_rendezvous.trunnel +++ b/src/trunnel/hs/cell_rendezvous.trunnel @@ -1,11 +1,16 @@ /* - * This contains the definition of the RENDEZVOUS1 cell for onion service + * This contains the definition of the RENDEZVOUS1/2 cell for onion service * version 3 and onward. The following format is specified in proposal 224 * section 4.2. */ /* Rendezvous cookie length. */ const TRUNNEL_REND_COOKIE_LEN = 20; +/* The HANDSHAKE_INFO field layout is as follow: + * SERVER_PK [PK_PUBKEY_LEN bytes] + * AUTH [MAC_LEN bytes] + * This means, the size is 32 bytes + 32 bytes. */ +const TRUNNEL_HANDSHAKE_INFO_LEN = 64; /* RENDEZVOUS1 payload. See details in section 4.2. */ struct trn_cell_rendezvous1 { @@ -16,3 +21,9 @@ struct trn_cell_rendezvous1 { * handshake type used. */ u8 handshake_info[]; }; + +/* RENDEZVOUS2 payload. See details in section 4.2. */ +struct trn_cell_rendezvous2 { + /* The HANDSHAKE_INFO field. */ + u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN]; +}; From ee15c16742aecb4795ce12847068b7907be9e8a2 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Sun, 23 Jul 2017 13:01:00 -0400 Subject: [PATCH 36/62] prop224: Parse RENDEZVOUS2 cell Signed-off-by: David Goulet --- src/or/hs_cell.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index c5ea6e4db7..482fa04e92 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -899,3 +899,35 @@ hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) return ret; } +/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On + * success, handshake_info contains the data in the HANDSHAKE_INFO field, and + * 0 is returned. On error, a negative value is returned. */ +int +hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, size_t handshake_info_len) +{ + int ret = -1; + trn_cell_rendezvous2_t *cell = NULL; + + tor_assert(payload); + tor_assert(handshake_info); + + if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it."); + goto end; + } + + /* Static size, we should never have an issue with this else we messed up + * our code flow. */ + tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) == + handshake_info_len); + memcpy(handshake_info, + trn_cell_rendezvous2_getconstarray_handshake_info(cell), + handshake_info_len); + ret = 0; + + end: + trn_cell_rendezvous2_free(cell); + return ret; +} + From 9af311630626b920aa74c4fb8d36eb086ebfb8a4 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 25 Jul 2017 10:14:00 -0400 Subject: [PATCH 37/62] prop224: Handle RENDEZVOUS2 cell The client can now handle RENDEZVOUS2 cell when they arrive. This consolidate both hidden service version in one function. Signed-off-by: David Goulet --- src/or/hs_client.c | 96 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 3 ++ src/or/rendclient.c | 11 ------ src/or/rendcommon.c | 2 +- 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index ff3d12748c..337c7477d3 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -26,6 +26,7 @@ #include "connection.h" #include "circpathbias.h" #include "connection.h" +#include "hs_ntor.h" /* Get all connections that are waiting on a circuit and flag them back to * waiting for a hidden service descriptor for the given service key @@ -584,6 +585,68 @@ handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, return ret; } +/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The + * encoded cell is in payload of length payload_len. Return 0 on success or a + * negative value on error. On error, the circuit is marked for close. */ +static int +handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + curve25519_public_key_t server_pk; + uint8_t auth_mac[DIGEST256_LEN] = {0}; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0}; + hs_ntor_rend_cell_keys_t keys; + const hs_ident_circuit_t *ident; + + tor_assert(circ); + tor_assert(payload); + + /* Make things easier. */ + ident = circ->hs_ident; + tor_assert(ident); + + if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info, + sizeof(handshake_info)) < 0) { + goto err; + } + /* Get from the handshake info the SERVER_PK and AUTH_MAC. */ + memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN); + memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac)); + + /* Generate the handshake info. */ + if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk, + &ident->rendezvous_client_kp, + &ident->intro_enc_pk, &server_pk, + &keys) < 0) { + log_info(LD_REND, "Unable to compute the rendezvous keys."); + goto err; + } + + /* Critical check, make sure that the MAC matches what we got with what we + * computed just above. */ + if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) { + log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell."); + goto err; + } + + /* Setup the e2e encryption on the circuit and finalize its state. */ + if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed, + sizeof(keys.ntor_key_seed), 0) < 0) { + log_info(LD_REND, "Unable to setup the e2e encryption."); + goto err; + } + /* Success. Hidden service connection finalized! */ + ret = 0; + goto end; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + end: + memwipe(&keys, 0, sizeof(keys)); + return ret; +} + /* ========== */ /* Public API */ /* ========== */ @@ -881,3 +944,36 @@ hs_client_receive_introduce_ack(origin_circuit_t *circ, return ret; } +/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return + * 0 on success else a negative value is returned. The circuit will be closed + * on error. */ +int +hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Circuit can possibly be in both state because we could receive a + * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. " + "Closing circuit.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.", + TO_CIRCUIT(circ)->n_circ_id); + + ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) : + rend_client_receive_rendezvous(circ, payload, + payload_len); + end: + return ret; +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 1c0f8a4d2a..6681505ee6 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -34,6 +34,9 @@ int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, int hs_client_receive_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +int hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 683a3916ec..cb7d99bed3 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -932,23 +932,12 @@ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { - if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && - circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) - || !circ->build_state->pending_final_cpath) { - log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not " - "expecting it. Closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - if (request_len != DH_KEY_LEN+DIGEST_LEN) { log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.", (int)request_len); goto err; } - log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service."); - if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) { log_warn(LD_GENERAL, "Failed to setup circ"); goto err; diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 86dc913cd0..f7ae7c61f1 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -790,7 +790,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = rend_client_receive_rendezvous(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) From e67f8623f95082341c06c9c70ef664ed7b186483 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 25 Jul 2017 10:31:44 -0400 Subject: [PATCH 38/62] hs: Only note a connection attempt with a valid rend_data For now, prop224 doesn't have a mechanism to note down connection attempts so we only do it for legacy system using rend_data. Signed-off-by: David Goulet --- src/or/connection_edge.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8ef3379c4c..3dd08b6d2a 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -155,7 +155,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason, * but we should fix it someday anyway. */ if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) && connection_edge_is_rendezvous_stream(edge_conn)) { - rend_client_note_connection_attempt_ended(edge_conn->rend_data); + if (edge_conn->rend_data) { + rend_client_note_connection_attempt_ended(edge_conn->rend_data); + } } if (base_conn->marked_for_close) { From 3152c583af052571cbcd058bb1569f2ad821ec42 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 25 Jul 2017 12:05:02 +0300 Subject: [PATCH 39/62] prop224: Add client-side rend circuit support to HS circuitmap Signed-off-by: David Goulet --- src/or/hs_circuitmap.c | 45 +++++++++++++++++++++++++++++++++++-- src/or/hs_circuitmap.h | 8 +++++++ src/test/test_circuitlist.c | 11 +++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index ea66fb5194..f235ecc82d 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -5,8 +5,10 @@ * \file hs_circuitmap.c * * \brief Hidden service circuitmap: A hash table that maps binary tokens to - * introduction and rendezvous circuits; it's used both by relays acting as - * intro points and rendezvous points, and also by hidden services themselves. + * introduction and rendezvous circuits; it's used: + * (a) by relays acting as intro points and rendezvous points + * (b) by hidden services to find intro and rend circuits and + * (c) by HS clients to find rendezvous circuits. **/ #define HS_CIRCUITMAP_PRIVATE @@ -404,6 +406,29 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) return circ; } +/* Public function: Return client-side rendezvous circuit with rendezvous + * cookie. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY + * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED. + * + * Return NULL if no such circuit is found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) +{ + origin_circuit_t *circ = NULL; + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + return circ; +} + /**** Public servide-side setters: */ /* Public function: Register v2 intro circuit with key digest to the @@ -439,6 +464,22 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, REND_TOKEN_LEN, cookie); } +/* Public function: Register rendezvous circuit with key cookie to the + * client-side circuitmap. */ +void +hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, + const uint8_t *cookie) +{ + circuit_t *circ = TO_CIRCUIT(or_circ); + { /* Basic circ purpose sanity checking */ + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_REND_READY || + circ->purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + } + + hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie); +} + /**** Misc public functions: */ /** Public function: Remove this circuit from the HS circuitmap. Clear its HS diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h index 33d5b64117..0903de2347 100644 --- a/src/or/hs_circuitmap.h +++ b/src/or/hs_circuitmap.h @@ -43,6 +43,8 @@ struct origin_circuit_t * hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest); struct origin_circuit_t * hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie); +struct origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie); void hs_circuitmap_register_intro_circ_v2_service_side( struct origin_circuit_t *circ, @@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side( void hs_circuitmap_register_rend_circ_service_side( struct origin_circuit_t *circ, const uint8_t *cookie); +void hs_circuitmap_register_rend_circ_client_side( + struct origin_circuit_t *circ, + const uint8_t *cookie); void hs_circuitmap_remove_circuit(struct circuit_t *circ); @@ -76,6 +81,9 @@ typedef enum { HS_TOKEN_INTRO_V2_SERVICE_SIDE, /** A v3 introduction point pubkey on a hidden service (256bit) */ HS_TOKEN_INTRO_V3_SERVICE_SIDE, + + /** A rendezvous cookie on the client side (128bit) */ + HS_TOKEN_REND_CLIENT_SIDE, } hs_token_type_t; /** Represents a token used in the HS protocol. Each such token maps to a diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c index 344ab27921..6b8dae31f4 100644 --- a/src/test/test_circuitlist.c +++ b/src/test/test_circuitlist.c @@ -180,6 +180,7 @@ static void test_rend_token_maps(void *arg) { or_circuit_t *c1, *c2, *c3, *c4; + origin_circuit_t *c5; const uint8_t tok1[REND_TOKEN_LEN] = "The cat can't tell y"; const uint8_t tok2[REND_TOKEN_LEN] = "ou its name, and it "; const uint8_t tok3[REND_TOKEN_LEN] = "doesn't really care."; @@ -194,6 +195,7 @@ test_rend_token_maps(void *arg) c2 = or_circuit_new(0, NULL); c3 = or_circuit_new(0, NULL); c4 = or_circuit_new(0, NULL); + c5 = origin_circuit_new(); /* Make sure we really filled up the tok* variables */ tt_int_op(tok1[REND_TOKEN_LEN-1], OP_EQ, 'y'); @@ -264,6 +266,13 @@ test_rend_token_maps(void *arg) tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_EQ, NULL); tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); + /* Now let's do a check for the client-side rend circuitmap */ + c5->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY; + hs_circuitmap_register_rend_circ_client_side(c5, tok1); + + tt_ptr_op(c5, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok2)); + done: if (c1) circuit_free(TO_CIRCUIT(c1)); @@ -273,6 +282,8 @@ test_rend_token_maps(void *arg) circuit_free(TO_CIRCUIT(c3)); if (c4) circuit_free(TO_CIRCUIT(c4)); + if (c5) + circuit_free(TO_CIRCUIT(c5)); } static void From 379ad6f6eb5393e74daba8ac71c252155c052a34 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 25 Jul 2017 12:17:20 +0300 Subject: [PATCH 40/62] prop224: Use the client-side circuitmap in hs_client.c Signed-off-by: David Goulet --- src/or/circuitlist.c | 27 --------------------------- src/or/circuitlist.h | 2 -- src/or/hs_circuit.c | 4 ++++ src/or/hs_client.c | 6 +++--- 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index c2d947beae..d891c89f38 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1498,33 +1498,6 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) return NULL; } -/* Return an origin circuit such that: - * - Identifier identity key matches, - * - Rendezvous cookie matches - * - Circuit is not marked for close - * - Circuit has purpose CIRCUIT_PURPOSE_C_REND_READY. - * - * Return NULL if no such circuit exits. */ -origin_circuit_t * -circuit_get_ready_rend_by_hs_ident(const hs_ident_circuit_t *ident) -{ - SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { - if (!circ->marked_for_close && - circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) { - origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - if (ocirc->hs_ident && - ed25519_pubkey_eq(&ident->identity_pk, - ô->hs_ident->identity_pk) && - tor_memeq(ident->rendezvous_cookie, - ocirc->hs_ident->rendezvous_cookie, - HS_REND_COOKIE_LEN)) { - return ocirc; - } - } - } SMARTLIST_FOREACH_END(circ); - return NULL; -} - /** Return the first service introduction circuit originating from the global * circuit list after start or at the start of the list if start * is NULL. Return NULL if no circuit is found. diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index f2a3c43134..b882b2c6a8 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -46,8 +46,6 @@ void circuit_unlink_all_from_channel(channel_t *chan, int reason); origin_circuit_t *circuit_get_by_global_id(uint32_t id); origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( const rend_data_t *rend_data); -origin_circuit_t *circuit_get_ready_rend_by_hs_ident( - const hs_ident_circuit_t *ident); 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); diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index ee453d0d52..819d254327 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -1070,6 +1070,10 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, goto done; } + /* Register rend circuit in circuitmap */ + hs_circuitmap_register_rend_circ_client_side(rend_circ, + rend_circ->hs_ident->rendezvous_cookie); + /* Success. */ ret = 0; goto done; diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 337c7477d3..5629550765 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -503,9 +503,9 @@ handle_introduce_ack_success(origin_circuit_t *intro_circ) log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous"); - /* Get the rendezvous circuit matching this intro point circuit. - * XXX Replace this by our hs circuitmap to support client? */ - rend_circ = circuit_get_ready_rend_by_hs_ident(intro_circ->hs_ident); + /* Get the rendezvous circuit for this rendezvous cookie. */ + uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie; + rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie); if (rend_circ == NULL) { log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping"); goto end; From 88f64eee68c972845131c8e372af067d8bb37cd1 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 25 Jul 2017 11:37:55 -0400 Subject: [PATCH 41/62] hs: Update the IP failure cache only if legacy Don't assert() on rend_data when closing circuits to report an IP failure. The prop224 code doesn't have yet the support for this. Signed-off-by: David Goulet --- src/or/circuitlist.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index d891c89f38..2f3fe327e6 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1961,8 +1961,8 @@ circuit_about_to_free(circuit_t *circ) int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); - tor_assert(ocirc->rend_data); - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { /* treat this like getting a nack from it */ log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", safe_str_client(rend_data_get_address(ocirc->rend_data)), @@ -1978,7 +1978,8 @@ circuit_about_to_free(circuit_t *circ) reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", From 88b843608accd10af6d12c53531950566ded5ef9 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 27 Jul 2017 12:37:52 -0400 Subject: [PATCH 42/62] prop224: Move and refactor rend_client_reextend_intro_circuit() This moves it to hs_client.c so it can be used by both system (legacy and prop224). For now, only the legacy system uses it. Signed-off-by: David Goulet --- src/or/hs_client.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_client.h | 2 ++ src/or/rendclient.c | 45 +++------------------------------------------ 3 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 5629550765..68b33b1969 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -27,6 +27,7 @@ #include "circpathbias.h" #include "connection.h" #include "hs_ntor.h" +#include "circuitbuild.h" /* Get all connections that are waiting on a circuit and flag them back to * waiting for a hidden service descriptor for the given service key @@ -977,3 +978,46 @@ hs_client_receive_rendezvous2(origin_circuit_t *circ, return ret; } +/* Extend the introduction circuit circ to another valid introduction point + * for the hidden service it is trying to connect to, or mark it and launch a + * new circuit if we can't extend it. Return 0 on success or possible + * success. Return -1 and mark the introduction circuit for close on permanent + * failure. + * + * On failure, the caller is responsible for marking the associated rendezvous + * circuit for close. */ +int +hs_client_reextend_intro_circuit(origin_circuit_t *circ) +{ + int ret = -1; + extend_info_t *ei; + + tor_assert(circ); + + ei = (circ->hs_ident) ? + client_get_random_intro(&circ->hs_ident->identity_pk) : + rend_client_get_random_intro(circ->rend_data); + if (ei == NULL) { + log_warn(LD_REND, "No usable introduction points left. Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + + if (circ->remaining_relay_early_cells) { + log_info(LD_REND, "Re-extending circ %u, this time to %s.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(extend_info_describe(ei))); + ret = circuit_extend_to_new_exit(circ, ei); + } else { + log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ + ret = 0; + } + + end: + extend_info_free(ei); + return ret; +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 6681505ee6..4406987884 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -43,5 +43,7 @@ void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); extend_info_t *hs_client_get_random_intro_from_edge( const edge_connection_t *edge_conn); +int hs_client_reextend_intro_circuit(origin_circuit_t *circ); + #endif /* TOR_HS_CLIENT_H */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index cb7d99bed3..d983dea7ed 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -18,6 +18,7 @@ #include "directory.h" #include "hs_common.h" #include "hs_circuit.h" +#include "hs_client.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -89,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) return 0; } -/** Extend the introduction circuit circ to another valid - * introduction point for the hidden service it is trying to connect - * to, or mark it and launch a new circuit if we can't extend it. - * Return 0 on success or possible success. Return -1 and mark the - * introduction circuit for close on permanent failure. - * - * On failure, the caller is responsible for marking the associated - * rendezvous circuit for close. */ -static int -rend_client_reextend_intro_circuit(origin_circuit_t *circ) -{ - extend_info_t *extend_info; - int result; - extend_info = rend_client_get_random_intro(circ->rend_data); - if (!extend_info) { - log_warn(LD_REND, - "No usable introduction points left for %s. Closing.", - safe_str_client(rend_data_get_address(circ->rend_data))); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); - return -1; - } - // XXX: should we not re-extend if hs_circ_has_timed_out? - if (circ->remaining_relay_early_cells) { - log_info(LD_REND, - "Re-extending circ %u, this time to %s.", - (unsigned)circ->base_.n_circ_id, - safe_str_client(extend_info_describe(extend_info))); - result = circuit_extend_to_new_exit(circ, extend_info); - } else { - log_info(LD_REND, - "Closing intro circ %u (out of RELAY_EARLY cells).", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); - /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ - result = 0; - } - extend_info_free(extend_info); - return result; -} - /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ @@ -202,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, introcirc->build_state->chosen_exit)), smartlist_len(entry->parsed->intro_nodes)); - if (rend_client_reextend_intro_circuit(introcirc)) { + if (hs_client_reextend_intro_circuit(introcirc)) { status = -2; goto perm_err; } else { @@ -437,7 +398,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, INTRO_POINT_FAILURE_GENERIC)>0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ - int result = rend_client_reextend_intro_circuit(circ); + int result = hs_client_reextend_intro_circuit(circ); /* XXXX If that call failed, should we close the rend circuit, * too? */ return result; From 2671399e67e19c125fbfb6f4b9f1ba71c4e52031 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 27 Jul 2017 15:51:32 -0400 Subject: [PATCH 43/62] prop224: Add a client intro point state cache This cache keeps track of the state of intro points which is needed when we have failures when using them. It is similar to the failure cache of the legacy system. At this commit, it is unused but initialized, cleanup and freed. Signed-off-by: David Goulet --- src/or/hs_cache.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_cache.h | 35 +++++++ src/or/main.c | 1 + 3 files changed, 267 insertions(+) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index d93e682dc3..28cb4d1bd9 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -324,6 +324,11 @@ hs_cache_clean_as_dir(time_t now) /* Client-side HS descriptor cache. Map indexed by service identity key. */ static digest256map_t *hs_cache_v3_client; +/* Client-side introduction point state cache. Map indexed by service public + * identity key (onion address). It contains hs_cache_client_intro_state_t + * objects all related to a specific service. */ +static digest256map_t *hs_cache_client_intro_state; + /* Remove a given descriptor from our cache. */ static void remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) @@ -410,6 +415,172 @@ cache_client_desc_free_(void *ptr) cache_client_desc_free(desc); } +/* Return a newly allocated and initialized hs_cache_intro_state_t object. */ +static hs_cache_intro_state_t * +cache_intro_state_new(void) +{ + hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state)); + state->created_ts = approx_time(); + return state; +} + +/* Free an hs_cache_intro_state_t object. */ +static void +cache_intro_state_free(hs_cache_intro_state_t *state) +{ + tor_free(state); +} + +/* Helper function: use by the free all function. */ +static void +cache_intro_state_free_(void *state) +{ + cache_intro_state_free(state); +} + +/* Return a newly allocated and initialized hs_cache_client_intro_state_t + * object. */ +static hs_cache_client_intro_state_t * +cache_client_intro_state_new(void) +{ + hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache)); + cache->intro_points = digest256map_new(); + return cache; +} + +/* Free a cache client intro state object. */ +static void +cache_client_intro_state_free(hs_cache_client_intro_state_t *cache) +{ + if (cache == NULL) { + return; + } + digest256map_free(cache->intro_points, cache_intro_state_free_); + tor_free(cache); +} + +/* Helper function: use by the free all function. */ +static void +cache_client_intro_state_free_(void *entry) +{ + cache_client_intro_state_free(entry); +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, lookup the intro state object. Return 1 if + * found and put it in entry if not NULL. Return 0 if not found and entry is + * untouched. */ +static int +cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **entry) +{ + hs_cache_intro_state_t *state; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the intro state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + goto not_found; + } + + /* From the cache we just found for the service, lookup in the introduction + * points map for the given authentication key. */ + state = digest256map_get(cache->intro_points, auth_key->pubkey); + if (state == NULL) { + goto not_found; + } + if (entry) { + *entry = state; + } + return 1; + not_found: + return 0; +} + +/* Note the given failure in state. */ +static void +cache_client_intro_state_note(hs_cache_intro_state_t *state, + rend_intro_point_failure_t failure) +{ + tor_assert(state); + switch (failure) { + case INTRO_POINT_FAILURE_GENERIC: + state->error = 1; + break; + case INTRO_POINT_FAILURE_TIMEOUT: + state->timed_out = 1; + break; + case INTRO_POINT_FAILURE_UNREACHABLE: + state->unreachable_count++; + break; + default: + tor_assert_nonfatal_unreached(); + return; + } +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, add an entry in the client intro state cache + * If no entry exists for the service, it will create one. If state is non + * NULL, it will point to the new intro state entry. */ +static void +cache_client_intro_state_add(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **state) +{ + hs_cache_intro_state_t *entry, *old_entry; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + cache = cache_client_intro_state_new(); + digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache); + } + + entry = cache_intro_state_new(); + old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry); + /* This should never happened because the code flow is to lookup the entry + * before adding it. But, just in case, non fatal assert and free it. */ + tor_assert_nonfatal(old_entry == NULL); + tor_free(old_entry); + + if (state) { + *state = entry; + } +} + +/* Remove every intro point state entry from cache that has been created + * before or at the cutoff. */ +static void +cache_client_intro_state_clean(time_t cutoff, + hs_cache_client_intro_state_t *cache) +{ + tor_assert(cache); + + DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key, + hs_cache_intro_state_t *, entry) { + if (entry->created_ts <= cutoff) { + cache_intro_state_free(entry); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Return true iff no intro points are in this cache. */ +static int +cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache) +{ + return digest256map_isempty(cache->intro_points); +} + /** Check whether client_desc is useful for us, and store it in the * client-side HS cache if so. The client_desc is freed if we already have a * fresher (higher revision counter count) in the cache. */ @@ -554,6 +725,59 @@ hs_cache_clean_as_client(time_t now) cache_clean_v3_as_client(now); } +/* For a given service identity public key and an introduction authentication + * key, note the given failure in the client intro state cache. */ +void +hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure) +{ + int found; + hs_cache_intro_state_t *entry; + + tor_assert(service_pk); + tor_assert(auth_key); + + found = cache_client_intro_state_lookup(service_pk, auth_key, &entry); + if (!found) { + /* Create a new entry and add it to the cache. */ + cache_client_intro_state_add(service_pk, auth_key, &entry); + } + /* Note down the entry. */ + cache_client_intro_state_note(entry, failure); +} + +/* For a given service identity public key and an introduction authentication + * key, return true iff it is present in the failure cache. */ +const hs_cache_intro_state_t * +hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key) +{ + hs_cache_intro_state_t *state = NULL; + cache_client_intro_state_lookup(service_pk, auth_key, &state); + return state; +} + +/* Cleanup the client introduction state cache. */ +void +hs_cache_client_intro_state_clean(time_t now) +{ + time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE; + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + /* Cleanup intro points failure. */ + cache_client_intro_state_clean(cutoff, cache); + + /* Is this cache empty for this service key? If yes, remove it from the + * cache. Else keep it. */ + if (cache_client_intro_state_is_empty(cache)) { + cache_client_intro_state_free(cache); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + /**************** Generics *********************************/ /* Do a round of OOM cleanup on all directory caches. Return the amount of @@ -629,6 +853,9 @@ hs_cache_init(void) tor_assert(!hs_cache_v3_client); hs_cache_v3_client = digest256map_new(); + + tor_assert(!hs_cache_client_intro_state); + hs_cache_client_intro_state = digest256map_new(); } /* Cleanup the hidden service cache subsystem. */ @@ -640,5 +867,9 @@ hs_cache_free_all(void) digest256map_free(hs_cache_v3_client, cache_client_desc_free_); hs_cache_v3_client = NULL; + + digest256map_free(hs_cache_client_intro_state, + cache_client_intro_state_free_); + hs_cache_client_intro_state = NULL; } diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index 79456f69c8..2a4d2dbb2f 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -15,8 +15,34 @@ #include "crypto_ed25519.h" #include "hs_common.h" #include "hs_descriptor.h" +#include "rendcommon.h" #include "torcert.h" +/* This is the maximum time an introduction point state object can stay in the + * client cache in seconds (2 mins or 120 seconds). */ +#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60) + +/* Introduction point state. */ +typedef struct hs_cache_intro_state_t { + /* When this entry was created and put in the cache. */ + time_t created_ts; + + /* Did it suffered a generic error? */ + unsigned int error : 1; + + /* Did it timed out? */ + unsigned int timed_out : 1; + + /* How many times we tried to reached it and it was unreachable. */ + uint32_t unreachable_count; +} hs_cache_intro_state_t; + +typedef struct hs_cache_client_intro_state_t { + /* Contains hs_cache_intro_state_t object indexed by introduction point + * authentication key. */ + digest256map_t *intro_points; +} hs_cache_client_intro_state_t; + /* Descriptor representation on the directory side which is a subset of * information that the HSDir can decode and serve it. */ typedef struct hs_cache_dir_descriptor_t { @@ -59,6 +85,15 @@ int hs_cache_store_as_client(const char *desc_str, const ed25519_public_key_t *identity_pk); void hs_cache_clean_as_client(time_t now); +/* Client failure cache. */ +void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure); +const hs_cache_intro_state_t *hs_cache_client_intro_state_find( + const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key); +void hs_cache_client_intro_state_clean(time_t now); + #ifdef HS_CACHE_PRIVATE /** Represents a locally cached HS descriptor on a hidden service client. */ diff --git a/src/or/main.c b/src/or/main.c index 3db0b5e901..20fec50093 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1847,6 +1847,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options) * clean it as soon as we can since we want to make sure the client waits * as little as possible for reachability reasons. */ rend_cache_failure_clean(now); + hs_cache_client_intro_state_clean(now); return 30; } From 14b858c4ced594ee5130a685ac4b143bf66da04a Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 27 Jul 2017 17:06:42 -0400 Subject: [PATCH 44/62] prop224: Use the intro point state cache This commit makes the client use the intro point state cache. It notes down when we get a NACK from the intro point and then uses that cache to decide if it should either close the circuits or re-extend to a new intro point. This also introduces a very useful function that checks if an intro point is usable that is query the state cache and checks a series of requirement. Signed-off-by: David Goulet --- src/or/hs_client.c | 122 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 68b33b1969..06cbcc4091 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -419,6 +419,51 @@ desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) return ei; } +/* Return true iff the intro point ip for the service service_pk is usable. + * This function checks if the intro point is in the client intro state cache + * and checks at the failures. It is considered usable if: + * - No error happened (INTRO_POINT_FAILURE_GENERIC) + * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT) + * - The unreachable count is lower than + * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE) + */ +static int +intro_point_is_usable(const ed25519_public_key_t *service_pk, + const hs_desc_intro_point_t *ip) +{ + const hs_cache_intro_state_t *state; + + tor_assert(service_pk); + tor_assert(ip); + + state = hs_cache_client_intro_state_find(service_pk, + &ip->auth_key_cert->signed_key); + if (state == NULL) { + /* This means we've never encountered any problem thus usable. */ + goto usable; + } + if (state->error) { + log_info(LD_REND, "Intro point with auth key %s had an error. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->timed_out) { + log_info(LD_REND, "Intro point with auth key %s timed out. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) { + log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + + usable: + return 1; + not_usable: + return 0; +} + /* Using a descriptor desc, return a newly allocated extend_info_t object of a * randomly picked introduction point from its list. Return NULL if none are * usable. */ @@ -454,6 +499,12 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) ip = smartlist_get(usable_ips, idx); smartlist_del(usable_ips, idx); + /* We need to make sure we have a usable intro points which is in a good + * state in our cache. */ + if (!intro_point_is_usable(service_pk, ip)) { + continue; + } + /* Generate an extend info object from the intro point object. */ ei = desc_intro_point_to_extend_info(ip); if (ei == NULL) { @@ -470,8 +521,6 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) ei_excluded = ei; continue; } - /* XXX: Intro point can time out or just be unsuable, we need to keep - * track of this and check against such cache. */ /* Good pick! Let's go with this. */ goto end; @@ -493,6 +542,62 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) return ei; } +/* For this introduction circuit, we'll look at if we have any usable + * introduction point left for this service. If so, we'll use the circuit to + * re-extend to a new intro point. Else, we'll close the circuit and its + * corresponding rendezvous circuit. Return 0 if we are re-extending else -1 + * if we are closing the circuits. + * + * This is called when getting an INTRODUCE_ACK cell with a NACK. */ +static int +close_or_reextend_intro_circ(origin_circuit_t *intro_circ) +{ + int ret = -1; + const hs_descriptor_t *desc; + origin_circuit_t *rend_circ; + + tor_assert(intro_circ); + + desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* We can't continue without a descriptor. */ + goto close; + } + /* We still have the descriptor, great! Let's try to see if we can + * re-extend by looking up if there are any usable intro points. */ + if (!hs_client_any_intro_points_usable(desc)) { + goto close; + } + /* Try to re-extend now. */ + if (hs_client_reextend_intro_circuit(intro_circ) < 0) { + goto close; + } + /* Success on re-extending. Don't return an error. */ + ret = 0; + goto end; + + close: + /* Change the intro circuit purpose before so we don't report an intro point + * failure again triggering an extra descriptor fetch. The circuit can + * already be closed on failure to re-extend. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + } + /* Close the related rendezvous circuit. */ + rend_circ = hs_circuitmap_get_rend_circ_client_side( + intro_circ->hs_ident->rendezvous_cookie); + /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was + * inflight so we can't expect one every time. */ + if (rend_circ) { + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED); + } + + end: + return ret; +} + /* Called when we get an INTRODUCE_ACK success status code. Do the appropriate * actions for the rendezvous point and finally close intro_circ. */ static void @@ -545,8 +650,11 @@ handle_introduce_ack_bad(origin_circuit_t *circ, int status) /* It's a NAK. The introduction point didn't relay our request. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); - /* XXX: Report this failure for the intro point failure cache. Depending on - * how many times we've tried this intro point, close it or reextend. */ + /* Note down this failure in the intro point failure cache. Depending on how + * many times we've tried this intro point, close it or reextend. */ + hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk, + &circ->hs_ident->intro_auth_pk, + INTRO_POINT_FAILURE_GENERIC); } /* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded @@ -570,11 +678,14 @@ handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, case HS_CELL_INTRO_ACK_SUCCESS: ret = 0; handle_introduce_ack_success(circ); - break; + goto end; case HS_CELL_INTRO_ACK_FAILURE: case HS_CELL_INTRO_ACK_BADFMT: case HS_CELL_INTRO_ACK_NORELAY: handle_introduce_ack_bad(circ, status); + /* We are going to see if we have to close the circuits (IP and RP) or we + * can re-extend to a new intro point. */ + ret = close_or_reextend_intro_circ(circ); break; default: log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s", @@ -583,6 +694,7 @@ handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, break; } + end: return ret; } From a64ef7d6c5f609229c4e6a25f1b18494a3ba6eea Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 27 Jul 2017 17:15:19 -0400 Subject: [PATCH 45/62] prop224: Implement hs_client_any_intro_points_usable() Signed-off-by: David Goulet --- src/or/connection_edge.c | 4 +++- src/or/hs_client.c | 34 ++++++++++++++++++++++++---------- src/or/hs_client.h | 3 ++- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 3dd08b6d2a..6409c5be81 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -1501,7 +1501,9 @@ connection_ap_handle_onion(entry_connection_t *conn, cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk); if (cached_desc) { rend_cache_lookup_result = 0; - descriptor_is_usable = hs_client_any_intro_points_usable(cached_desc); + descriptor_is_usable = + hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk, + cached_desc); log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.", (descriptor_is_usable) ? "usable" : "unusable", safe_str_client(onion_address), diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 06cbcc4091..e7e3eb72ae 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -242,7 +242,8 @@ send_introduce1(origin_circuit_t *intro_circ, /* 1) Get descriptor from our cache. */ const hs_descriptor_t *desc = hs_cache_lookup_as_client(service_identity_pk); - if (desc == NULL || !hs_client_any_intro_points_usable(desc)) { + if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk, + desc)) { log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.", safe_str_client(onion_address), (desc) ? "didn't have usable intro points" : @@ -479,7 +480,8 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) tor_assert(service_pk); desc = hs_cache_lookup_as_client(service_pk); - if (desc == NULL || !hs_client_any_intro_points_usable(desc)) { + if (desc == NULL || !hs_client_any_intro_points_usable(service_pk, + desc)) { log_info(LD_REND, "Unable to randomly select an introduction point " "because descriptor %s.", (desc) ? "doesn't have usable intro point" : "is missing"); @@ -565,7 +567,8 @@ close_or_reextend_intro_circ(origin_circuit_t *intro_circ) } /* We still have the descriptor, great! Let's try to see if we can * re-extend by looking up if there are any usable intro points. */ - if (!hs_client_any_intro_points_usable(desc)) { + if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk, + desc)) { goto close; } /* Try to re-extend now. */ @@ -824,14 +827,24 @@ hs_client_decode_descriptor(const char *desc_str, return -1; } -/** Return true if there are any usable intro points in the v3 HS descriptor - * desc. */ +/* Return true iff there are at least one usable intro point in the service + * descriptor desc. */ int -hs_client_any_intro_points_usable(const hs_descriptor_t *desc) +hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc) { - /* XXX stub waiting for more client-side work: - equivalent to v2 rend_client_any_intro_points_usable() */ + tor_assert(service_pk); tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (intro_point_is_usable(service_pk, ip)) { + goto usable; + } + } SMARTLIST_FOREACH_END(ip); + + return 0; + usable: return 1; } @@ -856,7 +869,8 @@ hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) { const hs_descriptor_t *cached_desc = NULL; cached_desc = hs_cache_lookup_as_client(identity_pk); - if (cached_desc && hs_client_any_intro_points_usable(cached_desc)) { + if (cached_desc && hs_client_any_intro_points_usable(identity_pk, + cached_desc)) { log_warn(LD_GENERAL, "We would fetch a v3 hidden service descriptor " "but we already have a useable descriprot."); return 0; @@ -989,7 +1003,7 @@ hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) goto end; } - if (!hs_client_any_intro_points_usable(desc)) { + if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) { log_info(LD_REND, "Hidden service descriptor is unusable. " "Closing streams."); connection_mark_unattached_ap(entry_conn, diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 4406987884..8ed0501c91 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -20,7 +20,8 @@ int hs_client_decode_descriptor( const char *desc_str, const ed25519_public_key_t *service_identity_pk, hs_descriptor_t **desc); -int hs_client_any_intro_points_usable(const hs_descriptor_t *desc); +int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc); int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); int hs_client_send_introduce1(origin_circuit_t *intro_circ, From 31da3898e72cc2b9e8c25f6b4e89d34bbb17737e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 9 Aug 2017 11:45:29 -0400 Subject: [PATCH 46/62] prop224: Add directory_request_fetch_set_hs_ident() Needed by the client when fetching a descriptor. This function checks the directory purpose and hard assert if it is not for fetching. Signed-off-by: David Goulet --- src/or/directory.c | 14 ++++++++++++++ src/or/directory.h | 2 ++ src/or/hs_client.c | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/or/directory.c b/src/or/directory.c index f3e76be62f..57dfdd9cac 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1292,6 +1292,20 @@ directory_request_upload_set_hs_ident(directory_request_t *req, } req->hs_ident = ident; } +/** + * Set an object containing HS connection identifier to be associated with + * this fetch request. Note that only an alias to ident is stored, so + * the ident object must outlive the request. + */ +void +directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC); + } + req->hs_ident = ident; +} /** Set a static circuit_guard_state_t object to affliate with the request in * req. This object will receive notification when the attempt to * connect to the guard either succeeds or fails. */ diff --git a/src/or/directory.h b/src/or/directory.h index 1acdff3460..fc71bf800b 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -75,6 +75,8 @@ 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_fetch_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_client.c b/src/or/hs_client.c index e7e3eb72ae..77348f23e2 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -102,7 +102,7 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, directory_request_set_routerstatus(req, hsdir); directory_request_set_indirection(req, DIRIND_ANONYMOUS); directory_request_set_resource(req, base64_blinded_pubkey); - directory_request_upload_set_hs_ident(req, &hs_conn_dir_ident); + directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident); directory_initiate_request(req); directory_request_free(req); From 520fcdf2f377909e980dfcc8baea4733476f096c Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 11 Aug 2017 15:41:23 -0400 Subject: [PATCH 47/62] test: Fix broken HS upload descriptor test We can't trigger a valid upload because it would require us to MOCK a long list of functions ultimately not really testing the upload because we aren't on a running network. Signed-off-by: David Goulet Conflicts: src/test/test_hs_service.c --- src/test/test_hs_service.c | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 7263e04579..c93197ac20 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1177,19 +1177,12 @@ test_build_update_descriptors(void *arg) UNMOCK(hs_overlap_mode_is_active); } -static int -mock_router_have_minimum_dir_info(void) -{ - return 1; -} - static void test_upload_descriptors(void *arg) { int ret; time_t now = time(NULL); hs_service_t *service; - hs_service_intro_point_t *ip; (void) arg; @@ -1227,29 +1220,10 @@ test_upload_descriptors(void *arg) /* If no upload happened, this should be untouched. */ tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000); - /* Set our upload time in the past so we trigger an upload. */ - service->desc_current->next_upload_time = now - 1000; - service->desc_next->next_upload_time = now - 1000; - ip = helper_create_service_ip(); - ip->circuit_established = 1; - service_intro_point_add(service->desc_current->intro_points.map, ip); - - MOCK(networkstatus_get_live_consensus, - mock_networkstatus_get_live_consensus); - MOCK(router_have_minimum_dir_info, - mock_router_have_minimum_dir_info); - - setup_full_capture_of_logs(LOG_WARN); - run_upload_descriptor_event(now); - teardown_capture_of_logs(); - tt_u64_op(service->desc_current->next_upload_time, OP_GE, - now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN); - tt_u64_op(service->desc_current->next_upload_time, OP_LE, - now + HS_SERVICE_NEXT_UPLOAD_TIME_MAX); - done: hs_free_all(); UNMOCK(hs_overlap_mode_is_active); + UNMOCK(get_or_state); } /** Test the functions that save and load HS revision counters to state. */ From 47672ec1c7202feb0422317b39f9bcefb231fe5b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 09:02:23 -0400 Subject: [PATCH 48/62] prop224: Check decoded descriptor matches the expected blinded key When a client decodes a descriptor, make sure it matches the expected blinded key which is derived from the hidden service identity key. Signed-off-by: David Goulet --- src/or/hs_client.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 77348f23e2..71186b2619 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -800,6 +800,7 @@ hs_client_decode_descriptor(const char *desc_str, { int ret; uint8_t subcredential[DIGEST256_LEN]; + ed25519_public_key_t blinded_pubkey; tor_assert(desc_str); tor_assert(service_identity_pk); @@ -807,7 +808,6 @@ hs_client_decode_descriptor(const char *desc_str, /* Create subcredential for this HS so that we can decrypt */ { - ed25519_public_key_t blinded_pubkey; uint64_t current_time_period = hs_get_time_period_num(approx_time()); hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); @@ -822,6 +822,16 @@ hs_client_decode_descriptor(const char *desc_str, goto err; } + /* Make sure the descriptor signing key cross certifies with the computed + * blinded key. Without this validation, anyone knowing the subcredential + * and onion address can forge a descriptor. */ + if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert, + &blinded_pubkey, approx_time()) < 0) { + log_warn(LD_GENERAL, "Descriptor signing key certificate signature " + "doesn't validate with computed blinded key."); + goto err; + } + return 0; err: return -1; From 68887fdc5d4ff94cb8e88480e160349007d8a6a5 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 11:27:47 -0400 Subject: [PATCH 49/62] prop224: Update OOM counters when freeing a client descriptor cache object When we free a client descriptor cache object, tell the OOM handler. Signed-off-by: David Goulet --- src/or/hs_cache.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 28cb4d1bd9..e0d89a70e1 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -335,6 +335,8 @@ remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) { tor_assert(desc); digest256map_remove(hs_cache_v3_client, desc->key.pubkey); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_decrement_allocation(cache_get_client_entry_size(desc)); } /* Store a given descriptor in our cache. */ @@ -608,7 +610,6 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) } /* Remove old entry. Make space for the new one! */ remove_v3_desc_as_client(cache_entry); - rend_cache_decrement_allocation(cache_get_client_entry_size(cache_entry)); cache_client_desc_free(cache_entry); } @@ -649,7 +650,8 @@ cache_clean_v3_as_client(time_t now) bytes_removed += entry_size; /* Entry is not in the cache anymore, destroy it. */ cache_client_desc_free(entry); - /* Update our cache entry allocation size for the OOM. */ + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ rend_cache_decrement_allocation(entry_size); /* Logging. */ { From e046e6fb4816e118f1bb32bd709b5dce5b729994 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 11:30:11 -0400 Subject: [PATCH 50/62] prop224: Update OOM counters when storing a client descriptor cache object Signed-off-by: David Goulet --- src/or/hs_cache.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index e0d89a70e1..6962c5ce44 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -329,6 +329,14 @@ static digest256map_t *hs_cache_v3_client; * objects all related to a specific service. */ static digest256map_t *hs_cache_client_intro_state; +/* Return the size of a client cache entry in bytes. */ +static size_t +cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) +{ + return sizeof(*entry) + + strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); +} + /* Remove a given descriptor from our cache. */ static void remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) @@ -345,6 +353,8 @@ store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) { tor_assert(desc); digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_increment_allocation(cache_get_client_entry_size(desc)); } /* Query our cache and return the entry or NULL if not found. */ @@ -355,14 +365,6 @@ lookup_v3_desc_as_client(const uint8_t *key) return digest256map_get(hs_cache_v3_client, key); } -/* Return the size of a client cache entry in bytes. */ -static size_t -cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) -{ - return sizeof(*entry) + - strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); -} - /* Parse the encoded descriptor in desc_str using * service_identity_pk to decrypt it first. * @@ -616,9 +618,6 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) /* Store descriptor in cache */ store_v3_desc_as_client(client_desc); - /* Update cache size with this entry for the OOM handler. */ - rend_cache_increment_allocation(cache_get_client_entry_size(client_desc)); - done: return 0; } From 61009d40d127e0435e9fd98a8daf81c50e6687f0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 14:10:30 -0400 Subject: [PATCH 51/62] hs: Stub link_specifier_t in hs_descriptor.h Avoid including a huge trunnel interface everytime we include hs_descriptor.h Signed-off-by: David Goulet --- src/or/hs_descriptor.c | 5 ++--- src/or/hs_descriptor.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 71f16fee1a..b9233c6d7c 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -55,11 +55,10 @@ /* For unit tests.*/ #define HS_DESCRIPTOR_PRIVATE -#include "hs_descriptor.h" - #include "or.h" -#include "circuitbuild.h" #include "ed25519_cert.h" /* Trunnel interface. */ +#include "hs_descriptor.h" +#include "circuitbuild.h" #include "parsecommon.h" #include "rendcache.h" #include "hs_cache.h" diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index b4cda7b845..eeeb3e38a4 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -19,7 +19,7 @@ #include "torcert.h" /* Trunnel */ -#include "ed25519_cert.h" +struct link_specifier_t; /* The earliest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 From cb462b95b7e864b71ea3dc0a6bfb76c30fb8d378 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 14:12:49 -0400 Subject: [PATCH 52/62] prop224: Rename hs_desc_encode_lspec() Give it a name that shows the transition from a descriptor link spec to a trunnel link spec. Signed-off-by: David Goulet --- src/or/hs_client.c | 2 +- src/or/hs_descriptor.c | 4 ++-- src/or/hs_descriptor.h | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 71186b2619..90124fb8e6 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -407,7 +407,7 @@ desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) * representation which is a trunnel object. */ SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, const hs_desc_link_specifier_t *, desc_lspec) { - link_specifier_t *lspec = hs_desc_encode_lspec(desc_lspec); + link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec); smartlist_add(lspecs, lspec); } SMARTLIST_FOREACH_END(desc_lspec); diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index b9233c6d7c..af443b9955 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -331,7 +331,7 @@ encode_link_specifiers(const smartlist_t *specs) SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, spec) { - link_specifier_t *ls = hs_desc_encode_lspec(spec); + link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec); if (ls) { link_specifier_list_add_spec(lslist, ls); } @@ -2538,7 +2538,7 @@ hs_descriptor_clear_intro_points(hs_descriptor_t *desc) * link specifier object that is the encoded representation of spec. Return * NULL on error. */ link_specifier_t * -hs_desc_encode_lspec(const hs_desc_link_specifier_t *spec) +hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec) { tor_assert(spec); diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index eeeb3e38a4..ce225d5217 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -233,7 +233,8 @@ 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); -link_specifier_t *hs_desc_encode_lspec(const hs_desc_link_specifier_t *spec); +link_specifier_t *hs_desc_lspec_to_trunnel( + const hs_desc_link_specifier_t *spec); #ifdef HS_DESCRIPTOR_PRIVATE From 7c543412fc4c77a58fb928c6eb322e48fae97137 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 14:37:33 -0400 Subject: [PATCH 53/62] prop224: Add a function to clear INTRO data New function named hs_cell_introduce1_data_clear() is introduced to clear off an hs_cell_introduce1_data_t object. Signed-off-by: David Goulet --- src/or/hs_cell.c | 15 +++++++++++++++ src/or/hs_cell.h | 3 +++ src/or/hs_circuit.c | 6 +----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 482fa04e92..5244cfa3dd 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -931,3 +931,18 @@ hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, return ret; } +/* Clear the given INTRODUCE1 data structure data. */ +void +hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) +{ + if (data == NULL) { + return; + } + /* Object in this list have been moved to the cell object when building it + * so they've been freed earlier. We do that in order to avoid duplicating + * them leading to more memory and CPU time being used for nothing. */ + smartlist_free(data->link_specifiers); + /* The data object has no ownership of any members. */ + memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 14dd31c9de..5136fce933 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -115,5 +115,8 @@ int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, uint8_t *handshake_info, size_t handshake_info_len); +/* Util API. */ +void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); + #endif /* TOR_HS_CELL_H */ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 819d254327..7d41a33c89 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -1079,11 +1079,7 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, goto done; done: - /* Object in this list have been moved to the cell object when building it - * so they've been freed earlier. We do that in order to avoid duplicating - * them leading to more memory and CPU time being used for nothing. */ - smartlist_free(intro1_data.link_specifiers); - memwipe(&intro1_data, 0, sizeof(intro1_data)); + hs_cell_introduce1_data_clear(&intro1_data); memwipe(payload, 0, sizeof(payload)); return ret; } From a16627db1f099e8eaa2ac899cead604fe611f117 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 14:51:27 -0400 Subject: [PATCH 54/62] prop224: Add a function to find an intro point by legacy ID The client needs to find the right intro point object from the circuit identity digest it is opened to. This new function does that. Signed-off-by: David Goulet --- src/or/hs_client.c | 64 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 90124fb8e6..cfbda23277 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -214,6 +214,42 @@ find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, return intro_point; } +/* Find a descriptor intro point object from the descriptor object desc that + * matches the given legacy identity digest in legacy_id. Return NULL if not + * found. */ +static hs_desc_intro_point_t * +find_desc_intro_point_by_legacy_id(const char *legacy_id, + const hs_descriptor_t *desc) +{ + hs_desc_intro_point_t *ret_ip = NULL; + + tor_assert(legacy_id); + tor_assert(desc); + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + /* Not all tor node have an ed25519 identity key so we still rely on the + * legacy identity digest. */ + if (lspec->type != LS_LEGACY_ID) { + continue; + } + if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) { + break; + } + /* Found it. */ + ret_ip = ip; + goto end; + } SMARTLIST_FOREACH_END(lspec); + } SMARTLIST_FOREACH_END(ip); + + end: + return ret_ip; +} + /* Send an INTRODUCE1 cell along the intro circuit and populate the rend * circuit identifier with the needed key material for the e2e encryption. * Return 0 on success, -1 if there is a transient error such that an action @@ -319,6 +355,7 @@ static void setup_intro_circ_auth_key(origin_circuit_t *circ) { const hs_descriptor_t *desc; + const hs_desc_intro_point_t *ip; tor_assert(circ); @@ -330,25 +367,14 @@ setup_intro_circ_auth_key(origin_circuit_t *circ) /* We will go over every intro point and try to find which one is linked to * that circuit. Those lists are small so it's not that expensive. */ - SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, - const hs_desc_intro_point_t *, ip) { - SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, - const hs_desc_link_specifier_t *, lspec) { - /* Not all tor node have an ed25519 identity key so we still rely on the - * legacy identity digest. */ - if (lspec->type != LS_LEGACY_ID) { - continue; - } - if (fast_memneq(circ->build_state->chosen_exit->identity_digest, - lspec->u.legacy_id, DIGEST_LEN)) { - break; - } - /* We got it, copy its authentication key to the identifier. */ - ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, - &ip->auth_key_cert->signed_key); - goto end; - } SMARTLIST_FOREACH_END(lspec); - } SMARTLIST_FOREACH_END(ip); + ip = find_desc_intro_point_by_legacy_id( + circ->build_state->chosen_exit->identity_digest, desc); + if (ip) { + /* We got it, copy its authentication key to the identifier. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_cert->signed_key); + goto end; + } /* Reaching this point means we didn't find any intro point for this circuit * which is not suppose to happen. */ From 98efb646fc2caac850684f35e2ff6fd9eeab1450 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 22 Aug 2017 15:03:19 -0400 Subject: [PATCH 55/62] hs: Remove unused rend_client_rendezvous_acked() This function has been replaced by hs_client_receive_rendezvous_acked(() doing the same exact thing for both v2 and v3 service. Signed-off-by: David Goulet --- src/or/rendclient.c | 38 -------------------------------------- src/or/rendclient.h | 3 --- 2 files changed, 41 deletions(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index d983dea7ed..0f430d1f88 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -850,44 +850,6 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, return 1; } -/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of - * the circuit to C_REND_READY. - */ -int -rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, - size_t request_len) -{ - (void) request; - (void) request_len; - /* we just got an ack for our establish-rendezvous. switch purposes. */ - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { - log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. " - "Closing circ."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for " - "rendezvous."); - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); - /* Set timestamp_dirty, because circuit_expire_building expects it - * to specify when a circuit entered the _C_REND_READY state. */ - circ->base_.timestamp_dirty = time(NULL); - - /* From a path bias point of view, this circuit is now successfully used. - * Waiting any longer opens us up to attacks from malicious hidden services. - * They could induce the client to attempt to connect to their hidden - * service and never reply to the client's rend requests */ - pathbias_mark_use_success(circ); - - /* XXXX++ This is a pretty brute-force approach. It'd be better to - * attach only the connections that are waiting on this circuit, rather - * than trying to attach them all. See comments bug 743. */ - /* If we already have the introduction circuit built, make sure we send - * the INTRODUCE cell _now_ */ - connection_ap_attach_pending(1); - return 0; -} - /** The service sent us a rendezvous cell; join the circuits. */ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, diff --git a/src/or/rendclient.h b/src/or/rendclient.h index 375bc973b7..ac0503bade 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -29,9 +29,6 @@ int rend_client_report_intro_point_failure(extend_info_t *failed_intro, rend_data_t *rend_data, unsigned int failure_type); -int rend_client_rendezvous_acked(origin_circuit_t *circ, - const uint8_t *request, - size_t request_len); int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len); From 5f94c4a0f17403060d5030fab3e81bde46693119 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 23 Aug 2017 10:48:48 -0400 Subject: [PATCH 56/62] prop224: Refactor how we use connection_ap_handle_onion Simply directly return its returned value. Signed-off-by: David Goulet --- src/or/connection_edge.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 6409c5be81..a98b32450b 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -2017,9 +2017,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, tor_assert(addresstype == ONION_V2_HOSTNAME || addresstype == ONION_V3_HOSTNAME); tor_assert(!automap); - if (connection_ap_handle_onion(conn, socks, circ, addresstype) < 0) { - return -1; - } + return connection_ap_handle_onion(conn, socks, circ, addresstype); } return 0; /* unreached but keeps the compiler happy */ From 954f663831622f281ece83519552f91cd0fdda72 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 23 Aug 2017 11:15:25 -0400 Subject: [PATCH 57/62] prop224: Try to recover from a bad intro circuit Changed the assert_intro_circ_ok() to an almost non fatal function so tor can recover properly. We keep the anonymity assert because if that is not right, we have much deeper problems and client should stop sending bytes to the network immediately. Signed-off-by: David Goulet --- src/or/hs_client.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index cfbda23277..57bd870798 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -179,16 +179,33 @@ fetch_v3_desc(const ed25519_public_key_t *onion_identity_pk) return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); } -/* Make sure that the given origin circuit circ is a valid correct - * introduction circuit. This asserts on validation failure. */ -static void -assert_intro_circ_ok(const origin_circuit_t *circ) +/* Make sure that the given v3 origin circuit circ is a valid correct + * introduction circuit. This will BUG() on any problems and hard assert if + * the anonymity of the circuit is not ok. Return 0 on success else -1 where + * the circuit should be mark for closed immediately. */ +static int +intro_circ_is_ok(const origin_circuit_t *circ) { + int ret = 0; + tor_assert(circ); - tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); - tor_assert(circ->hs_ident); - tor_assert(hs_ident_intro_circ_is_valid(circ->hs_ident)); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + ret = -1; + } + if (BUG(circ->hs_ident == NULL)) { + ret = -1; + } + if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) { + ret = -1; + } + + /* This can stop the tor daemon but we want that since if we don't have + * anonymity on this circuit, something went really wrong. */ assert_circ_anonymity_ok(circ, get_options()); + return ret; } /* Find a descriptor intro point object that matches the given ident in the @@ -264,8 +281,10 @@ send_introduce1(origin_circuit_t *intro_circ, const ed25519_public_key_t *service_identity_pk = NULL; const hs_desc_intro_point_t *ip; - assert_intro_circ_ok(intro_circ); tor_assert(rend_circ); + if (intro_circ_is_ok(intro_circ) < 0) { + goto perm_err; + } service_identity_pk = &intro_circ->hs_ident->identity_pk; /* For logging purposes. There will be a time where the hs_ident will have a From dca105d58853c7ba6f8ef594cca34230dbb87394 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 24 Aug 2017 08:49:24 -0400 Subject: [PATCH 58/62] prop224: Register RP circuit when it opens Only register the RP circuit when it opens and not when we send the INTRODUCE1 cell else, when re-extending to a new IP, we would register the same RP circuit with the same cookie twice leading to the circuit being closed. Signed-off-by: David Goulet --- src/or/hs_circuit.c | 4 ---- src/or/hs_circuitmap.c | 13 ++++++++++--- src/or/hs_client.c | 6 ++++++ src/test/test_circuitlist.c | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 7d41a33c89..2bfeac747c 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -1070,10 +1070,6 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, goto done; } - /* Register rend circuit in circuitmap */ - hs_circuitmap_register_rend_circ_client_side(rend_circ, - rend_circ->hs_ident->rendezvous_cookie); - /* Success. */ ret = 0; goto done; diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index f235ecc82d..09704d796c 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -408,7 +408,8 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) /* Public function: Return client-side rendezvous circuit with rendezvous * cookie. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY - * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED. + * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED and then + * finally tries for CIRCUIT_PURPOSE_C_ESTABLISH_REND. * * Return NULL if no such circuit is found in the circuitmap. */ origin_circuit_t * @@ -426,6 +427,13 @@ hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, REND_TOKEN_LEN, cookie, CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_ESTABLISH_REND); return circ; } @@ -472,8 +480,7 @@ hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, { circuit_t *circ = TO_CIRCUIT(or_circ); { /* Basic circ purpose sanity checking */ - tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_REND_READY || - circ->purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); } hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE, diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 57bd870798..3e25126a6a 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -434,6 +434,12 @@ client_rendezvous_circ_has_opened(origin_circuit_t *circ) /* Ignore returned value, nothing we can really do. On failure, the circuit * will be marked for close. */ hs_circ_send_establish_rendezvous(circ); + + /* Register rend circuit in circuitmap if it's still alive. */ + if (!TO_CIRCUIT(circ)->marked_for_close) { + hs_circuitmap_register_rend_circ_client_side(circ, + circ->hs_ident->rendezvous_cookie); + } } /* This is an helper function that convert a descriptor intro point object ip diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c index 6b8dae31f4..f622704ec5 100644 --- a/src/test/test_circuitlist.c +++ b/src/test/test_circuitlist.c @@ -267,7 +267,7 @@ test_rend_token_maps(void *arg) tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); /* Now let's do a check for the client-side rend circuitmap */ - c5->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY; + c5->base_.purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND; hs_circuitmap_register_rend_circ_client_side(c5, tok1); tt_ptr_op(c5, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok1)); From 1077e9894cd60d9b991e38d965e57be00fd83277 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 24 Aug 2017 09:00:18 -0400 Subject: [PATCH 59/62] circuit: Avoid needless log info in circuit_get_best() When looking for an introduction circuit in circuit_get_best(), we log an info message if we are about to launch a new intro circuit in parallel. However, the condition was considering marked for close circuit leading to the function triggering the log info even though there is actually no valid intro circuit. Signed-off-by: David Goulet --- src/or/circuituse.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 93bccf0eb8..2472ac5452 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -337,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn, /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && origin_circ->hs_circ_has_timed_out) { + !must_be_open && origin_circ->hs_circ_has_timed_out && + !circ->marked_for_close) { intro_going_on_but_too_old = 1; continue; } From a1125625449617ef427d8816f6e0174806e2d947 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 24 Aug 2017 09:12:57 -0400 Subject: [PATCH 60/62] hs: Update intro circuit timestamp when re-extending A client can re-extend up to 3 intro points on the same circuit. This happens when we get NACKed by the intro point for which we choose a new intro and re-extend the circuit to it. That process can be arbitrarly long so reset the dirty timestamp of the circuit everytime we choose to re-extend so we get a bit more time to actually do our introduction. This is a client circuit so it is short live once opened thus giving us a bit more time to complete the introduction is ok. Signed-off-by: David Goulet --- src/or/hs_client.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 3e25126a6a..c0e24ac85c 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -1195,6 +1195,13 @@ hs_client_reextend_intro_circuit(origin_circuit_t *circ) (unsigned int) TO_CIRCUIT(circ)->n_circ_id, safe_str_client(extend_info_describe(ei))); ret = circuit_extend_to_new_exit(circ, ei); + if (ret == 0) { + /* We were able to extend so update the timestamp so we avoid expiring + * this circuit too early. The intro circuit is short live so the + * linkability issue is minimized, we just need the circuit to hold a + * bit longer so we can introduce. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + } } else { log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).", (unsigned int) TO_CIRCUIT(circ)->n_circ_id); From e546ac6b32d5478f50644ca8f1eac0a66513fe8d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 24 Aug 2017 09:31:30 -0400 Subject: [PATCH 61/62] circuit: Consider v3 hidden service circuit in circuit_expire_building() For a ready v3 rendezvous circuit, consider it so we don't expire. Signed-off-by: David Goulet --- src/or/circuituse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 2472ac5452..570b05e572 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -651,6 +651,7 @@ circuit_expire_building(void) * because that's set when they switch purposes */ if (TO_ORIGIN_CIRCUIT(victim)->rend_data || + TO_ORIGIN_CIRCUIT(victim)->hs_ident || victim->timestamp_dirty > cutoff.tv_sec) continue; break; From dc0264f6593116da413dc5ca6c424dcff36fbbd2 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 24 Aug 2017 19:09:25 +0300 Subject: [PATCH 62/62] Silence some spammy log messages. --- src/or/hs_service.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 31776c8b5e..ae98e92f54 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2722,10 +2722,10 @@ service_desc_hsdirs_changed(const hs_service_t *service, /* Check whether the set of HSDirs changed */ if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) { - log_warn(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!"); + log_info(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!"); retval = 1; } else { - log_warn(LD_GENERAL, "No change in hsdir set!"); + log_debug(LD_GENERAL, "No change in hsdir set!"); } done: @@ -2759,7 +2759,7 @@ hs_hsdir_set_changed_consider_reupload(void) return; } - log_info(LD_GENERAL, "Received new descriptors. Set of HSdirs changed."); + log_info(LD_GENERAL, "Received new dirinfo: Checking hash ring for changes"); /* Go over all descriptors and check if the set of HSDirs changed for any of * them. Schedule reupload if so. */