Loading changes/ticket40363 0 → 100644 +9 −0 Original line number Original line Diff line number Diff line o Major features (Proposal 332, onion services, guard selection algorithm): - Clients and onion services now choose four long-lived "layer 2" guard relays for use as the middle hop in all onion circuits. These relays are kept in place for a randomized duration averaging 1 week each. This mitigates guard discovery attacks against clients and short-lived onion services such as OnionShare. Long-lived onion services that need high security should still use the Vanguards addon (https://github.com/mikeperry-tor/vanguards). Closes ticket 40363; implements proposal 332. src/core/mainloop/mainloop.c +20 −0 Original line number Original line Diff line number Diff line Loading @@ -1293,6 +1293,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); addressmap_clear_transient(); hs_client_purge_state(); hs_client_purge_state(); purge_vanguards_lite(); time_of_last_signewnym = now; time_of_last_signewnym = now; signewnym_is_pending = 0; signewnym_is_pending = 0; Loading Loading @@ -1370,6 +1371,7 @@ CALLBACK(save_state); CALLBACK(write_stats_file); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); CALLBACK(control_per_second_events); CALLBACK(second_elapsed); CALLBACK(second_elapsed); CALLBACK(manage_vglite); #undef CALLBACK #undef CALLBACK Loading @@ -1392,6 +1394,9 @@ STATIC periodic_event_item_t mainloop_periodic_events[] = { CALLBACK(second_elapsed, NET_PARTICIPANT, CALLBACK(second_elapsed, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), FL(RUN_ON_DISABLE)), /* Update vanguards-lite once per hour, if we have networking */ CALLBACK(manage_vglite, NET_PARTICIPANT, FL(NEED_NET)), /* XXXX Do we have a reason to do this on a callback? Does it do any good at /* XXXX Do we have a reason to do this on a callback? Does it do any good at * all? For now, if we're dormant, we can let our listeners decay. */ * all? For now, if we're dormant, we can let our listeners decay. */ CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)), CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)), Loading Loading @@ -1662,6 +1667,21 @@ mainloop_schedule_shutdown(int delay_sec) mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); } } /** * Update vanguards-lite layer2 nodes, once per hour */ static int manage_vglite_callback(time_t now, const or_options_t *options) { (void)now; (void)options; #define VANGUARDS_LITE_INTERVAL (60*60) maintain_layer2_guards(); return VANGUARDS_LITE_INTERVAL; } /** Perform regular maintenance tasks. This function gets run once per /** Perform regular maintenance tasks. This function gets run once per * second. * second. */ */ Loading src/feature/client/entrynodes.c +202 −0 Original line number Original line Diff line number Diff line Loading @@ -3930,6 +3930,197 @@ guard_selection_free_(guard_selection_t *gs) tor_free(gs); tor_free(gs); } } /**********************************************************************/ /** Layer2 guard subsystem used for client-side onion service circuits. */ /** A simple representation of a layer2 guard. We just need its identity so * that we feed it into a routerset, and a sampled timestamp to do expiration * checks. */ typedef struct layer2_guard_t { /** Identity of the guard */ char identity[DIGEST_LEN]; /** When does this guard expire? (randomized timestamp) */ time_t expire_on_date; } layer2_guard_t; /** Global list and routerset of L2 guards. They are both synced and they get * updated periodically. We need both the list and the routerset: we use the * smartlist to keep track of expiration times and the routerset is what we * return to the users of this subsystem. */ static smartlist_t *layer2_guards = NULL; static routerset_t *layer2_routerset = NULL; /** Number of L2 guards */ #define NUMBER_SECOND_GUARDS 4 /** Lifetime of L2 guards: * 1 to 12 days, for an average of a week using the max(x,x) distribution */ #define MIN_SECOND_GUARD_LIFETIME (3600*24) #define MAX_SECOND_GUARD_LIFETIME (3600*24*12) /** Return the number of guards our L2 guardset should have */ static int get_number_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-number", NUMBER_SECOND_GUARDS, 1, INT32_MAX); } /** Return the minimum lifetime of L2 guards */ static int get_min_lifetime_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-lifetime-min", MIN_SECOND_GUARD_LIFETIME, 1, INT32_MAX); } /** Return the maximum lifetime of L2 guards */ static int get_max_lifetime_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-lifetime-max", MAX_SECOND_GUARD_LIFETIME, 1, INT32_MAX); } /** * Sample and return a lifetime for an L2 guard. * * Lifetime randomized uniformly between min and max consensus params. */ static int get_layer2_hs_guard_lifetime(void) { return crypto_rand_int_range(get_min_lifetime_of_layer2_hs_guards(), get_max_lifetime_of_layer2_hs_guards()); } /** Maintain the L2 guard list. Make sure the list contains enough guards, do * expirations as necessary, and keep all the data structures of this * subsystem synchronized */ void maintain_layer2_guards(void) { if (!router_have_minimum_dir_info()) { return; } /* Create the list if it doesn't exist */ if (!layer2_guards) { layer2_guards = smartlist_new(); } /* Go through the list and perform any needed expirations */ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { /* Expire based on expiration date */ if (g->expire_on_date <= approx_time()) { log_info(LD_GENERAL, "Removing expired Layer2 guard %s", safe_str_client(hex_str(g->identity, DIGEST_LEN))); // Nickname may be gone from consensus and doesn't matter anyway control_event_guard("None", g->identity, "BAD_L2"); tor_free(g); SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); continue; } /* Expire if relay has left consensus */ if (router_get_consensus_status_by_id(g->identity) == NULL) { log_info(LD_GENERAL, "Removing missing Layer2 guard %s", safe_str_client(hex_str(g->identity, DIGEST_LEN))); // Nickname may be gone from consensus and doesn't matter anyway control_event_guard("None", g->identity, "BAD_L2"); tor_free(g); SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); continue; } } SMARTLIST_FOREACH_END(g); /* Find out how many guards we need to add */ int new_guards_needed_n = get_number_of_layer2_hs_guards() - smartlist_len(layer2_guards); if (new_guards_needed_n <= 0) { return; } log_info(LD_GENERAL, "Adding %d guards to Layer2 routerset", new_guards_needed_n); /* Add required guards to the list */ for (int i = 0; i < new_guards_needed_n; i++) { const node_t *choice = NULL; const or_options_t *options = get_options(); /* Pick Stable nodes */ router_crn_flags_t flags = CRN_NEED_DESC|CRN_NEED_UPTIME; choice = router_choose_random_node(NULL, options->ExcludeNodes, flags); if (choice) { /* We found our node: create an L2 guard out of it */ layer2_guard_t *layer2_guard = tor_malloc_zero(sizeof(layer2_guard_t)); memcpy(layer2_guard->identity, choice->identity, DIGEST_LEN); layer2_guard->expire_on_date = approx_time() + get_layer2_hs_guard_lifetime(); smartlist_add(layer2_guards, layer2_guard); log_info(LD_GENERAL, "Adding Layer2 guard %s", safe_str_client(hex_str(layer2_guard->identity, DIGEST_LEN))); // Nickname can also be None here because it is looked up later control_event_guard("None", layer2_guard->identity, "GOOD_L2"); } } /* Now that the list is up to date, synchronize the routerset */ routerset_free(layer2_routerset); layer2_routerset = routerset_new(); SMARTLIST_FOREACH_BEGIN (layer2_guards, layer2_guard_t *, g) { routerset_parse(layer2_routerset, hex_str(g->identity, DIGEST_LEN), "l2 guards"); } SMARTLIST_FOREACH_END(g); } /** * Reset vanguards-lite list(s). * * Used for SIGNAL NEWNYM. */ void purge_vanguards_lite(void) { if (!layer2_guards) return; /* Go through the list and perform any needed expirations */ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { tor_free(g); } SMARTLIST_FOREACH_END(g); smartlist_clear(layer2_guards); /* Pick new l2 guards */ maintain_layer2_guards(); } /** Return a routerset containing the L2 guards or NULL if it's not yet * initialized. Callers must not free the routerset. Designed for use in * pick_vanguard_middle_node() and should not be used anywhere else (because * the routerset pointer can dangle under your feet) */ routerset_t * get_layer2_guards(void) { if (!layer2_guards) { maintain_layer2_guards(); } return layer2_routerset; } /*****************************************************************************/ /** Release all storage held by the list of entry guards and related /** Release all storage held by the list of entry guards and related * memory structs. */ * memory structs. */ void void Loading @@ -3946,4 +4137,15 @@ entry_guards_free_all(void) guard_contexts = NULL; guard_contexts = NULL; } } circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); if (!layer2_guards) { return; } SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { tor_free(g); } SMARTLIST_FOREACH_END(g); smartlist_free(layer2_guards); routerset_free(layer2_routerset); } } src/feature/client/entrynodes.h +4 −0 Original line number Original line Diff line number Diff line Loading @@ -651,4 +651,8 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, int orig_bandwidth, uint32_t guardfraction_percentage); uint32_t guardfraction_percentage); routerset_t *get_layer2_guards(void); void maintain_layer2_guards(void); void purge_vanguards_lite(void); #endif /* !defined(TOR_ENTRYNODES_H) */ #endif /* !defined(TOR_ENTRYNODES_H) */ src/test/test_entrynodes.c +25 −0 Original line number Original line Diff line number Diff line Loading @@ -92,6 +92,12 @@ bfn_mock_node_get_by_id(const char *id) return NULL; return NULL; } } static int mock_router_have_minimum_dir_info(void) { return 1; } /* Helper function to free a test node. */ /* Helper function to free a test node. */ static void static void test_node_free(node_t *n) test_node_free(node_t *n) Loading Loading @@ -3087,6 +3093,23 @@ test_entry_guard_vanguard_path_selection(void *arg) circuit_free_(circ); circuit_free_(circ); } } static void test_entry_guard_layer2_guards(void *arg) { (void) arg; MOCK(router_have_minimum_dir_info, mock_router_have_minimum_dir_info); /* Create the guardset */ maintain_layer2_guards(); routerset_t *l2_guards = get_layer2_guards(); tt_assert(l2_guards); tt_int_op(routerset_len(l2_guards), OP_EQ, 4); done: UNMOCK(router_have_minimum_dir_info); } static const struct testcase_setup_t big_fake_network = { static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup big_fake_network_setup, big_fake_network_cleanup }; }; Loading Loading @@ -3152,6 +3175,8 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(manage_primary), BFN_TEST(manage_primary), BFN_TEST(correct_cascading_order), BFN_TEST(correct_cascading_order), BFN_TEST(layer2_guards), EN_TEST_FORK(guard_preferred), EN_TEST_FORK(guard_preferred), BFN_TEST(select_for_circuit_no_confirmed), BFN_TEST(select_for_circuit_no_confirmed), Loading Loading
changes/ticket40363 0 → 100644 +9 −0 Original line number Original line Diff line number Diff line o Major features (Proposal 332, onion services, guard selection algorithm): - Clients and onion services now choose four long-lived "layer 2" guard relays for use as the middle hop in all onion circuits. These relays are kept in place for a randomized duration averaging 1 week each. This mitigates guard discovery attacks against clients and short-lived onion services such as OnionShare. Long-lived onion services that need high security should still use the Vanguards addon (https://github.com/mikeperry-tor/vanguards). Closes ticket 40363; implements proposal 332.
src/core/mainloop/mainloop.c +20 −0 Original line number Original line Diff line number Diff line Loading @@ -1293,6 +1293,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); addressmap_clear_transient(); hs_client_purge_state(); hs_client_purge_state(); purge_vanguards_lite(); time_of_last_signewnym = now; time_of_last_signewnym = now; signewnym_is_pending = 0; signewnym_is_pending = 0; Loading Loading @@ -1370,6 +1371,7 @@ CALLBACK(save_state); CALLBACK(write_stats_file); CALLBACK(write_stats_file); CALLBACK(control_per_second_events); CALLBACK(control_per_second_events); CALLBACK(second_elapsed); CALLBACK(second_elapsed); CALLBACK(manage_vglite); #undef CALLBACK #undef CALLBACK Loading @@ -1392,6 +1394,9 @@ STATIC periodic_event_item_t mainloop_periodic_events[] = { CALLBACK(second_elapsed, NET_PARTICIPANT, CALLBACK(second_elapsed, NET_PARTICIPANT, FL(RUN_ON_DISABLE)), FL(RUN_ON_DISABLE)), /* Update vanguards-lite once per hour, if we have networking */ CALLBACK(manage_vglite, NET_PARTICIPANT, FL(NEED_NET)), /* XXXX Do we have a reason to do this on a callback? Does it do any good at /* XXXX Do we have a reason to do this on a callback? Does it do any good at * all? For now, if we're dormant, we can let our listeners decay. */ * all? For now, if we're dormant, we can let our listeners decay. */ CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)), CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)), Loading Loading @@ -1662,6 +1667,21 @@ mainloop_schedule_shutdown(int delay_sec) mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); } } /** * Update vanguards-lite layer2 nodes, once per hour */ static int manage_vglite_callback(time_t now, const or_options_t *options) { (void)now; (void)options; #define VANGUARDS_LITE_INTERVAL (60*60) maintain_layer2_guards(); return VANGUARDS_LITE_INTERVAL; } /** Perform regular maintenance tasks. This function gets run once per /** Perform regular maintenance tasks. This function gets run once per * second. * second. */ */ Loading
src/feature/client/entrynodes.c +202 −0 Original line number Original line Diff line number Diff line Loading @@ -3930,6 +3930,197 @@ guard_selection_free_(guard_selection_t *gs) tor_free(gs); tor_free(gs); } } /**********************************************************************/ /** Layer2 guard subsystem used for client-side onion service circuits. */ /** A simple representation of a layer2 guard. We just need its identity so * that we feed it into a routerset, and a sampled timestamp to do expiration * checks. */ typedef struct layer2_guard_t { /** Identity of the guard */ char identity[DIGEST_LEN]; /** When does this guard expire? (randomized timestamp) */ time_t expire_on_date; } layer2_guard_t; /** Global list and routerset of L2 guards. They are both synced and they get * updated periodically. We need both the list and the routerset: we use the * smartlist to keep track of expiration times and the routerset is what we * return to the users of this subsystem. */ static smartlist_t *layer2_guards = NULL; static routerset_t *layer2_routerset = NULL; /** Number of L2 guards */ #define NUMBER_SECOND_GUARDS 4 /** Lifetime of L2 guards: * 1 to 12 days, for an average of a week using the max(x,x) distribution */ #define MIN_SECOND_GUARD_LIFETIME (3600*24) #define MAX_SECOND_GUARD_LIFETIME (3600*24*12) /** Return the number of guards our L2 guardset should have */ static int get_number_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-number", NUMBER_SECOND_GUARDS, 1, INT32_MAX); } /** Return the minimum lifetime of L2 guards */ static int get_min_lifetime_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-lifetime-min", MIN_SECOND_GUARD_LIFETIME, 1, INT32_MAX); } /** Return the maximum lifetime of L2 guards */ static int get_max_lifetime_of_layer2_hs_guards(void) { return (int) networkstatus_get_param(NULL, "guard-hs-l2-lifetime-max", MAX_SECOND_GUARD_LIFETIME, 1, INT32_MAX); } /** * Sample and return a lifetime for an L2 guard. * * Lifetime randomized uniformly between min and max consensus params. */ static int get_layer2_hs_guard_lifetime(void) { return crypto_rand_int_range(get_min_lifetime_of_layer2_hs_guards(), get_max_lifetime_of_layer2_hs_guards()); } /** Maintain the L2 guard list. Make sure the list contains enough guards, do * expirations as necessary, and keep all the data structures of this * subsystem synchronized */ void maintain_layer2_guards(void) { if (!router_have_minimum_dir_info()) { return; } /* Create the list if it doesn't exist */ if (!layer2_guards) { layer2_guards = smartlist_new(); } /* Go through the list and perform any needed expirations */ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { /* Expire based on expiration date */ if (g->expire_on_date <= approx_time()) { log_info(LD_GENERAL, "Removing expired Layer2 guard %s", safe_str_client(hex_str(g->identity, DIGEST_LEN))); // Nickname may be gone from consensus and doesn't matter anyway control_event_guard("None", g->identity, "BAD_L2"); tor_free(g); SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); continue; } /* Expire if relay has left consensus */ if (router_get_consensus_status_by_id(g->identity) == NULL) { log_info(LD_GENERAL, "Removing missing Layer2 guard %s", safe_str_client(hex_str(g->identity, DIGEST_LEN))); // Nickname may be gone from consensus and doesn't matter anyway control_event_guard("None", g->identity, "BAD_L2"); tor_free(g); SMARTLIST_DEL_CURRENT_KEEPORDER(layer2_guards, g); continue; } } SMARTLIST_FOREACH_END(g); /* Find out how many guards we need to add */ int new_guards_needed_n = get_number_of_layer2_hs_guards() - smartlist_len(layer2_guards); if (new_guards_needed_n <= 0) { return; } log_info(LD_GENERAL, "Adding %d guards to Layer2 routerset", new_guards_needed_n); /* Add required guards to the list */ for (int i = 0; i < new_guards_needed_n; i++) { const node_t *choice = NULL; const or_options_t *options = get_options(); /* Pick Stable nodes */ router_crn_flags_t flags = CRN_NEED_DESC|CRN_NEED_UPTIME; choice = router_choose_random_node(NULL, options->ExcludeNodes, flags); if (choice) { /* We found our node: create an L2 guard out of it */ layer2_guard_t *layer2_guard = tor_malloc_zero(sizeof(layer2_guard_t)); memcpy(layer2_guard->identity, choice->identity, DIGEST_LEN); layer2_guard->expire_on_date = approx_time() + get_layer2_hs_guard_lifetime(); smartlist_add(layer2_guards, layer2_guard); log_info(LD_GENERAL, "Adding Layer2 guard %s", safe_str_client(hex_str(layer2_guard->identity, DIGEST_LEN))); // Nickname can also be None here because it is looked up later control_event_guard("None", layer2_guard->identity, "GOOD_L2"); } } /* Now that the list is up to date, synchronize the routerset */ routerset_free(layer2_routerset); layer2_routerset = routerset_new(); SMARTLIST_FOREACH_BEGIN (layer2_guards, layer2_guard_t *, g) { routerset_parse(layer2_routerset, hex_str(g->identity, DIGEST_LEN), "l2 guards"); } SMARTLIST_FOREACH_END(g); } /** * Reset vanguards-lite list(s). * * Used for SIGNAL NEWNYM. */ void purge_vanguards_lite(void) { if (!layer2_guards) return; /* Go through the list and perform any needed expirations */ SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { tor_free(g); } SMARTLIST_FOREACH_END(g); smartlist_clear(layer2_guards); /* Pick new l2 guards */ maintain_layer2_guards(); } /** Return a routerset containing the L2 guards or NULL if it's not yet * initialized. Callers must not free the routerset. Designed for use in * pick_vanguard_middle_node() and should not be used anywhere else (because * the routerset pointer can dangle under your feet) */ routerset_t * get_layer2_guards(void) { if (!layer2_guards) { maintain_layer2_guards(); } return layer2_routerset; } /*****************************************************************************/ /** Release all storage held by the list of entry guards and related /** Release all storage held by the list of entry guards and related * memory structs. */ * memory structs. */ void void Loading @@ -3946,4 +4137,15 @@ entry_guards_free_all(void) guard_contexts = NULL; guard_contexts = NULL; } } circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); circuit_build_times_free_timeouts(get_circuit_build_times_mutable()); if (!layer2_guards) { return; } SMARTLIST_FOREACH_BEGIN(layer2_guards, layer2_guard_t *, g) { tor_free(g); } SMARTLIST_FOREACH_END(g); smartlist_free(layer2_guards); routerset_free(layer2_routerset); } }
src/feature/client/entrynodes.h +4 −0 Original line number Original line Diff line number Diff line Loading @@ -651,4 +651,8 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, int orig_bandwidth, uint32_t guardfraction_percentage); uint32_t guardfraction_percentage); routerset_t *get_layer2_guards(void); void maintain_layer2_guards(void); void purge_vanguards_lite(void); #endif /* !defined(TOR_ENTRYNODES_H) */ #endif /* !defined(TOR_ENTRYNODES_H) */
src/test/test_entrynodes.c +25 −0 Original line number Original line Diff line number Diff line Loading @@ -92,6 +92,12 @@ bfn_mock_node_get_by_id(const char *id) return NULL; return NULL; } } static int mock_router_have_minimum_dir_info(void) { return 1; } /* Helper function to free a test node. */ /* Helper function to free a test node. */ static void static void test_node_free(node_t *n) test_node_free(node_t *n) Loading Loading @@ -3087,6 +3093,23 @@ test_entry_guard_vanguard_path_selection(void *arg) circuit_free_(circ); circuit_free_(circ); } } static void test_entry_guard_layer2_guards(void *arg) { (void) arg; MOCK(router_have_minimum_dir_info, mock_router_have_minimum_dir_info); /* Create the guardset */ maintain_layer2_guards(); routerset_t *l2_guards = get_layer2_guards(); tt_assert(l2_guards); tt_int_op(routerset_len(l2_guards), OP_EQ, 4); done: UNMOCK(router_have_minimum_dir_info); } static const struct testcase_setup_t big_fake_network = { static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup big_fake_network_setup, big_fake_network_cleanup }; }; Loading Loading @@ -3152,6 +3175,8 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(manage_primary), BFN_TEST(manage_primary), BFN_TEST(correct_cascading_order), BFN_TEST(correct_cascading_order), BFN_TEST(layer2_guards), EN_TEST_FORK(guard_preferred), EN_TEST_FORK(guard_preferred), BFN_TEST(select_for_circuit_no_confirmed), BFN_TEST(select_for_circuit_no_confirmed), Loading