Commit 08f31e40 authored by David Goulet's avatar David Goulet 🐼
Browse files

hs-v3: Purge ephemeral client auth on NEWNYM



Fixes #33139.

Signed-off-by: David Goulet's avatarDavid Goulet <dgoulet@torproject.org>
parent 1bf377f4
Loading
Loading
Loading
Loading

changes/ticket33139

0 → 100644
+3 −0
Original line number Diff line number Diff line
  o Minor bugfixes (Onion service client, authorization):
    - On a NEWNYM signal, purge the ephemeral client authorization cache. The
      permanent ones are kept. Fixes bug 33139; bugfix on 0.4.3.1-alpha.
+22 −0
Original line number Diff line number Diff line
@@ -1249,6 +1249,26 @@ can_client_refetch_desc(const ed25519_public_key_t *identity_pk,
  return 0;
}

/** Purge the client authorization cache of all ephemeral entries that is the
 * entries that are not flagged with CLIENT_AUTH_FLAG_IS_PERMANENT.
 *
 * This is called from the hs_client_purge_state() used by a SIGNEWNYM. */
STATIC void
purge_ephemeral_client_auth(void)
{
  DIGEST256MAP_FOREACH_MODIFY(client_auths, key,
                              hs_client_service_authorization_t *, auth) {
    /* Cleanup every entry that are _NOT_ permanent that is ephemeral. */
    if (!(auth->flags & CLIENT_AUTH_FLAG_IS_PERMANENT)) {
      MAP_DEL_CURRENT(key);
      client_service_authorization_free(auth);
    }
  } DIGESTMAP_FOREACH_END;

  log_info(LD_REND, "Client onion service ephemeral authorization "
                    "cache has been purged.");
}

/** Return the client auth in the map using the service identity public key.
 * Return NULL if it does not exist in the map. */
static hs_client_service_authorization_t *
@@ -2433,6 +2453,8 @@ hs_client_purge_state(void)
  hs_cache_purge_as_client();
  /* Purge the last hidden service request cache. */
  hs_purge_last_hid_serv_requests();
  /* Purge ephemeral client authorization. */
  purge_ephemeral_client_auth();

  log_info(LD_REND, "Hidden service client state has been purged.");
}
+2 −0
Original line number Diff line number Diff line
@@ -162,6 +162,8 @@ MOCK_DECL(STATIC hs_client_fetch_status_t,

STATIC void retry_all_socks_conn_waiting_for_desc(void);

STATIC void purge_ephemeral_client_auth(void);

#ifdef TOR_UNIT_TESTS

STATIC void set_hs_client_auths_map(digest256map_t *map);
+100 −0
Original line number Diff line number Diff line
@@ -79,6 +79,23 @@ mock_networkstatus_get_live_consensus(time_t now)
  return &mock_ns;
}

static int
mock_write_str_to_file(const char *path, const char *str, int bin)
{
  (void) bin;
  (void) path;
  (void) str;
  return 0;
}

static or_options_t mocked_options;

static const or_options_t *
mock_get_options(void)
{
  return &mocked_options;
}

static int
helper_config_client(const char *conf, int validate_only)
{
@@ -1330,6 +1347,85 @@ test_close_intro_circuit_failure(void *arg)
  hs_free_all();
}

static void
test_purge_ephemeral_client_auth(void *arg)
{
  ed25519_keypair_t service_kp;
  hs_client_service_authorization_t *auth = NULL;
  hs_client_register_auth_status_t status;

  (void) arg;

  /* We will try to write on disk client credentials. */
  MOCK(check_private_dir, mock_check_private_dir);
  MOCK(get_options, mock_get_options);
  MOCK(write_str_to_file, mock_write_str_to_file);

  /* Boggus directory so when we try to write the permanent client
   * authorization data to disk, we don't fail. See
   * store_permanent_client_auth_credentials() for more details. */
  mocked_options.ClientOnionAuthDir = tor_strdup("auth_dir");

  hs_init();

  /* Generate service keypair */
  tt_int_op(0, OP_EQ, ed25519_keypair_generate(&service_kp, 0));

  /* Generate a client authorization object. */
  auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t));

  /* Set it up. No flags meaning it is ephemeral. */
  curve25519_secret_key_generate(&auth->enc_seckey, 0);
  hs_build_address(&service_kp.pubkey, HS_VERSION_THREE, auth->onion_address);
  auth->flags = 0;

  /* Confirm that there is nothing in the client auth map. It is unallocated
   * until we add the first entry. */
  tt_assert(!get_hs_client_auths_map());

  /* Add an entry to the client auth list. We loose ownership of the auth
   * object so nullify it. */
  status = hs_client_register_auth_credentials(auth);
  auth = NULL;
  tt_int_op(status, OP_EQ, REGISTER_SUCCESS);

  /* We should have the entry now. */
  digest256map_t *client_auths = get_hs_client_auths_map();
  tt_assert(client_auths);
  tt_int_op(digest256map_size(client_auths), OP_EQ, 1);

  /* Purge the cache that should remove all ephemeral values. */
  purge_ephemeral_client_auth();
  tt_int_op(digest256map_size(client_auths), OP_EQ, 0);

  /* Now add a new authorization object but permanent. */
  /* Generate a client authorization object. */
  auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t));
  curve25519_secret_key_generate(&auth->enc_seckey, 0);
  hs_build_address(&service_kp.pubkey, HS_VERSION_THREE, auth->onion_address);
  auth->flags = CLIENT_AUTH_FLAG_IS_PERMANENT;

  /* Add an entry to the client auth list. We loose ownership of the auth
   * object so nullify it. */
  status = hs_client_register_auth_credentials(auth);
  auth = NULL;
  tt_int_op(status, OP_EQ, REGISTER_SUCCESS);
  tt_int_op(digest256map_size(client_auths), OP_EQ, 1);

  /* Purge again, the entry should still be there. */
  purge_ephemeral_client_auth();
  tt_int_op(digest256map_size(client_auths), OP_EQ, 1);

 done:
  client_service_authorization_free(auth);
  hs_free_all();
  tor_free(mocked_options.ClientOnionAuthDir);

  UNMOCK(check_private_dir);
  UNMOCK(get_options);
  UNMOCK(write_str_to_file);
}

struct testcase_t hs_client_tests[] = {
  { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy,
    TT_FORK, NULL, NULL },
@@ -1357,5 +1453,9 @@ struct testcase_t hs_client_tests[] = {
  /* SOCKS5 Extended Error Code. */
  { "socks_hs_errors", test_socks_hs_errors, TT_FORK, NULL, NULL },

  /* Client authorization. */
  { "purge_ephemeral_client_auth", test_purge_ephemeral_client_auth, TT_FORK,
    NULL, NULL },

  END_OF_TESTCASES
};