Commit 314a6b42 authored by George Kadianakis's avatar George Kadianakis
Browse files

Introduce vanguards-lite subsystem and some of its entry points

parent e71db3a4
Loading
Loading
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.
+20 −0
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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


@@ -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)),
@@ -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.
 */
 */
+202 −0
Original line number Original line Diff line number Diff line
@@ -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
@@ -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);
}
}
+4 −0
Original line number Original line Diff line number Diff line
@@ -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) */
+25 −0
Original line number Original line Diff line number Diff line
@@ -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)
@@ -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
};
};
@@ -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),