#define HS_CLIENT_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "core/crypto/hs_ntor.h"
#include "core/mainloop/connection.h"
#include "core/or/circuitbuild.h"
#include "core/or/circuitlist.h"
#include "core/or/circuituse.h"
#include "core/or/connection_edge.h"
#include "core/or/extendinfo.h"
#include "core/or/reasons.h"
#include "feature/client/circpathbias.h"
#include "feature/dirclient/dirclient.h"
#include "feature/dircommon/directory.h"
#include "feature/hs/hs_cache.h"
#include "feature/hs/hs_cell.h"
#include "feature/hs/hs_circuit.h"
#include "feature/hs/hs_circuitmap.h"
#include "feature/hs/hs_client.h"
#include "feature/hs/hs_control.h"
#include "feature/hs/hs_descriptor.h"
#include "feature/hs/hs_ident.h"
#include "feature/nodelist/describe.h"
#include "feature/nodelist/microdesc.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/nodelist/nodelist.h"
#include "feature/nodelist/routerset.h"
#include "lib/crypt_ops/crypto_format.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "lib/crypt_ops/crypto_util.h"
#include "core/or/cpath_build_state_st.h"
#include "feature/dircommon/dir_connection_st.h"
#include "core/or/entry_connection_st.h"
#include "core/or/extend_info_st.h"
#include "core/or/origin_circuit_st.h"
#include "core/or/socks_request_st.h"
static digest256map_t *client_auths = NULL;
#include "trunnel/hs/cell_introduce1.h"
static const char *
fetch_status_to_string(hs_client_fetch_status_t status)
{
switch (status) {
case HS_CLIENT_FETCH_ERROR:
return "Internal error";
case HS_CLIENT_FETCH_LAUNCHED:
return "Descriptor fetch launched";
case HS_CLIENT_FETCH_HAVE_DESC:
return "Already have descriptor";
case HS_CLIENT_FETCH_NO_HSDIRS:
return "No more HSDir available to query";
case HS_CLIENT_FETCH_NOT_ALLOWED:
return "Fetching descriptors is not allowed";
case HS_CLIENT_FETCH_MISSING_INFO:
return "Missing directory information";
case HS_CLIENT_FETCH_PENDING:
return "Pending descriptor fetch";
default:
return "(Unknown client fetch status code)";
}
}
static int
fetch_status_should_close_socks(hs_client_fetch_status_t status)
{
switch (status) {
case HS_CLIENT_FETCH_NO_HSDIRS:
case HS_CLIENT_FETCH_ERROR:
case HS_CLIENT_FETCH_NOT_ALLOWED:
goto close;
case HS_CLIENT_FETCH_MISSING_INFO:
case HS_CLIENT_FETCH_HAVE_DESC:
case HS_CLIENT_FETCH_PENDING:
case HS_CLIENT_FETCH_LAUNCHED:
goto no_close;
}
no_close:
return 0;
close:
return 1;
}
static smartlist_t *
find_entry_conns(const ed25519_public_key_t *service_identity_pk)
{
time_t now = time(NULL);
smartlist_t *conns = NULL, *entry_conns = NULL;
entry_conns = smartlist_new();
conns = connection_list_by_type_state(CONN_TYPE_AP,
AP_CONN_STATE_RENDDESC_WAIT);
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) {
entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn);
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
if (!edge_conn->hs_ident ||
(service_identity_pk &&
!ed25519_pubkey_eq(service_identity_pk,
&edge_conn->hs_ident->identity_pk))) {
continue;
}
assert_connection_ok(base_conn, now);
smartlist_add(entry_conns, entry_conn);
} SMARTLIST_FOREACH_END(base_conn);
smartlist_free(conns);
return entry_conns;
}
static void
cancel_descriptor_fetches(void)
{
smartlist_t *conns =
connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC);
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident;
if (BUG(ident == NULL)) {
continue;
}
log_debug(LD_REND, "Marking for close a directory connection fetching "
"a hidden service descriptor for service %s.",
safe_str_client(ed25519_fmt(&ident->identity_pk)));
connection_mark_for_close(conn);
} SMARTLIST_FOREACH_END(conn);
smartlist_free(conns);
log_info(LD_REND, "Hidden service client descriptor fetches cancelled.");
}
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_waiting_for_renddesc(TO_ENTRY_CONN(conn));
}
} SMARTLIST_FOREACH_END(conn);
smartlist_free(conns);
}
static void
purge_hid_serv_request(const ed25519_public_key_t *identity_pk)
{
char base64_blinded_pk[ED25519_BASE64_LEN + 1];
ed25519_public_key_t blinded_pk;
tor_assert(identity_pk);
hs_build_blinded_pubkey(identity_pk, NULL, 0,
hs_get_time_period_num(0), &blinded_pk);
ed25519_public_to_base64(base64_blinded_pk, &blinded_pk);
hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk);
}
static int
directory_request_is_pending(const ed25519_public_key_t *identity_pk)
{
int ret = 0;
smartlist_t *conns =
connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC);
SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident;
if (BUG(ident == NULL)) {
continue;
}
if (!ed25519_pubkey_eq(identity_pk, &ident->identity_pk)) {
continue;
}
ret = 1;
break;
} SMARTLIST_FOREACH_END(conn);
smartlist_free(conns);
return ret;
}
static void
mark_conn_as_waiting_for_circuit(connection_t *conn, time_t now)
{
tor_assert(conn);
conn->timestamp_created = now;
conn->timestamp_last_read_allowed = now;
conn->timestamp_last_write_allowed = now;
conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn));
}
static void
close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk,
hs_client_fetch_status_t status,
int reason)
{
unsigned int count = 0;
smartlist_t *entry_conns = find_entry_conns(identity_pk);
SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) {
connection_mark_unattached_ap(entry_conn, reason);
count++;
} SMARTLIST_FOREACH_END(entry_conn);
if (count > 0) {
char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
hs_build_address(identity_pk, HS_VERSION_THREE, onion_address);
log_notice(LD_REND, "Closed %u streams for service %s.onion "
"for reason %s. Fetch status: %s.",
count, safe_str_client(onion_address),
stream_end_reason_to_string(reason),
fetch_status_to_string(status));
}
smartlist_free(entry_conns);
}
STATIC void
retry_all_socks_conn_waiting_for_desc(void)
{
smartlist_t *entry_conns = find_entry_conns(NULL);
SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) {
hs_client_fetch_status_t status;
edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
connection_t *base_conn = &edge_conn->base_;
if (edge_conn->hs_ident == NULL) {
continue;
}
if (base_conn->marked_for_close) {
continue;
}
status = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
if (status == HS_CLIENT_FETCH_HAVE_DESC) {
mark_conn_as_waiting_for_circuit(base_conn, approx_time());
continue;
}
} SMARTLIST_FOREACH_END(entry_conn);
smartlist_free(entry_conns);
}
static void
note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
{
tor_assert(hs_conn_ident);
purge_hid_serv_request(&hs_conn_ident->identity_pk);
}
static hs_client_fetch_status_t
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(0);
ed25519_public_key_t blinded_pubkey;
char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
hs_ident_dir_conn_t hs_conn_dir_ident;
tor_assert(hsdir);
tor_assert(onion_identity_pk);
hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
current_time_period, &blinded_pubkey);
ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey,
&hs_conn_dir_ident);
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_fetch_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)));
hs_control_desc_event_requested(onion_identity_pk, base64_blinded_pubkey,
hsdir);
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 HS_CLIENT_FETCH_LAUNCHED;
}
STATIC routerstatus_t *
pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk)
{
char base64_blinded_pubkey[ED25519_BASE64_LEN + 1];
uint64_t current_time_period = hs_get_time_period_num(0);
smartlist_t *responsible_hsdirs = NULL;
ed25519_public_key_t blinded_pubkey;
routerstatus_t *hsdir_rs = NULL;
tor_assert(onion_identity_pk);
hs_build_blinded_pubkey(onion_identity_pk, NULL, 0,
current_time_period, &blinded_pubkey);
ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey);
responsible_hsdirs = smartlist_new();
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));
hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey, NULL);
return hsdir_rs;
}
MOCK_IMPL(STATIC hs_client_fetch_status_t,
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 HS_CLIENT_FETCH_NO_HSDIRS;
}
return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs);
}
void
hs_client_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk,
const smartlist_t *hsdirs)
{
tor_assert(onion_identity_pk);
if (hsdirs != NULL) {
SMARTLIST_FOREACH_BEGIN(hsdirs, const routerstatus_t *, hsdir) {
directory_launch_v3_desc_fetch(onion_identity_pk, hsdir);
} SMARTLIST_FOREACH_END(hsdir);
} else {
fetch_v3_desc(onion_identity_pk);
}
}
static int
intro_circ_is_ok(const origin_circuit_t *circ)
{
int ret = 0;
tor_assert(circ);
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;
}
assert_circ_anonymity_ok(circ, get_options());
return ret;
}
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;
}
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);
SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
hs_desc_intro_point_t *, ip) {
SMARTLIST_FOREACH_BEGIN(ip->link_specifiers,
const link_specifier_t *, lspec) {
if (link_specifier_get_ls_type(lspec) != LS_LEGACY_ID) {
continue;
}
if (fast_memneq(legacy_id,
link_specifier_getconstarray_un_legacy_id(lspec),
DIGEST_LEN)) {
break;
}
ret_ip = ip;
goto end;
} SMARTLIST_FOREACH_END(lspec);
} SMARTLIST_FOREACH_END(ip);
end:
return ret_ip;
}
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;
tor_assert(rend_circ);
if (intro_circ_is_ok(intro_circ) < 0) {
goto perm_err;
}
service_identity_pk = &intro_circ->hs_ident->identity_pk;
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);
const hs_descriptor_t *desc =
hs_cache_lookup_as_client(service_identity_pk);
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" :
"didn't have a descriptor");
hs_client_refetch_hsdesc(service_identity_pk);
flag_all_conn_wait_desc(service_identity_pk);
goto tran_err;
}
ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc);
if (ip == NULL) {
log_info(LD_REND, "Unable to find introduction point for service %s "
"while trying to send an INTRODUCE1 cell.",
safe_str_client(onion_address));
goto perm_err;
}
if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
&desc->subcredential) < 0) {
if (TO_CIRCUIT(intro_circ)->marked_for_close) {
hs_cache_client_intro_state_note(service_identity_pk,
&intro_circ->hs_ident->intro_auth_pk,
INTRO_POINT_FAILURE_GENERIC);
}
goto tran_err;
}
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);
circuit_change_purpose(TO_CIRCUIT(intro_circ),
CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT);
TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL);
pathbias_count_use_attempt(intro_circ);
status = 0;
goto end;
perm_err:
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;
}
static int
setup_intro_circ_auth_key(origin_circuit_t *circ)
{
const hs_descriptor_t *desc;
const hs_desc_intro_point_t *ip;
tor_assert(circ);
desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk);
if (desc == NULL) {
goto err;
}
ip = find_desc_intro_point_by_legacy_id(
circ->build_state->chosen_exit->identity_digest, desc);
if (!ip) {
log_info(LD_REND,"Could not match opened intro circuit with intro point.");
goto err;
}
ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk,
&ip->auth_key_cert->signed_key);
return 0;
err:
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return -1;
}
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);
if (setup_intro_circ_auth_key(circ) < 0) {
return;
}
connection_ap_attach_pending(1);
}
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);
const extend_info_t *rp_ei = circ->build_state->chosen_exit;
if (rp_ei) {
const node_t *rp_node = node_get_by_id(rp_ei->identity_digest);
if (rp_node && !node_supports_v3_rendezvous_point(rp_node)) {
log_info(LD_REND, "Rendezvous node %s did not support v3 after circuit "
"has opened.", safe_str_client(extend_info_describe(rp_ei)));
return;
}
}
log_info(LD_REND, "Rendezvous circuit has opened to %s.",
safe_str_client(extend_info_describe(rp_ei)));
hs_circ_send_establish_rendezvous(circ);
if (!TO_CIRCUIT(circ)->marked_for_close) {
hs_circuitmap_register_rend_circ_client_side(circ,
circ->hs_ident->rendezvous_cookie);
}
}
STATIC extend_info_t *
desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip)
{
extend_info_t *ei;
tor_assert(ip);
ei = hs_get_extend_info_from_lspecs(ip->link_specifiers, &ip->onion_key, 0);
return ei;
}
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) {
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;
}
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();
char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
tor_assert(service_pk);
desc = hs_cache_lookup_as_client(service_pk);
hs_build_address(service_pk,
desc ? desc->plaintext_data.version : HS_VERSION_THREE,
onion_address);
if (desc == NULL || !hs_client_any_intro_points_usable(service_pk,
desc)) {
log_info(LD_REND, "Unable to randomly select an introduction point "
"for service %s because descriptor %s. We can't connect.",
safe_str_client(onion_address),
(desc) ? "doesn't have any usable intro points"
: "is missing (assuming v3 onion address)");
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;
idx = crypto_rand_int(smartlist_len(usable_ips));
ip = smartlist_get(usable_ips, idx);
smartlist_del(usable_ips, idx);
if (!intro_point_is_usable(service_pk, ip)) {
continue;
}
ei = desc_intro_point_to_extend_info(ip);
if (ei == NULL) {
log_info(LD_REND, "Unable to select introduction point with auth key %s "
"for service %s, because we could not extend to it.",
safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)),
safe_str_client(onion_address));
continue;
}
if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) {
if (ei_excluded) {
extend_info_free(ei_excluded);
}
ei_excluded = ei;
continue;
}
goto end;
}
ei = ei_excluded;
if (options->StrictNodes) {
log_warn(LD_REND, "Every introduction point for service %s is in the "
"ExcludeNodes set and StrictNodes is set. We can't connect.",
safe_str_client(onion_address));
extend_info_free(ei);
ei = NULL;
} else {
log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service "
"%s is unusable or we can't extend to it. We can't connect.",
safe_str_client(onion_address));
}
end:
smartlist_free(usable_ips);
memwipe(onion_address, 0, sizeof(onion_address));
return ei;
}
static bool
intro_points_all_timed_out(const ed25519_public_key_t *service_pk)
{
bool ret = false;
tor_assert(service_pk);
const hs_descriptor_t *desc = hs_cache_lookup_as_client(service_pk);
if (BUG(!desc)) {
goto end;
}
SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
const hs_desc_intro_point_t *, ip) {
const hs_cache_intro_state_t *state =
hs_cache_client_intro_state_find(service_pk,
&ip->auth_key_cert->signed_key);
if (!state || !state->timed_out) {
goto end;
}
} SMARTLIST_FOREACH_END(ip);
ret = true;
end:
return ret;
}
static void
socks_mark_rend_circuit_timed_out(const origin_circuit_t *rend_circ)
{
tor_assert(rend_circ);
for (edge_connection_t *edge = rend_circ->p_streams; edge;
edge = edge->next_stream) {
entry_connection_t *entry = EDGE_TO_ENTRY_CONN(edge);
if (entry->socks_request) {
entry->socks_request->socks_extended_error_code =
SOCKS5_HS_REND_FAILED;
}
}
}
static void
socks_mark_introduction_failed(entry_connection_t *conn,
const ed25519_public_key_t *identity_pk)
{
socks5_reply_status_t code = SOCKS5_HS_INTRO_FAILED;
tor_assert(conn);
tor_assert(conn->socks_request);
tor_assert(identity_pk);
if (intro_points_all_timed_out(identity_pk)) {
code = SOCKS5_HS_INTRO_TIMEDOUT;
}
conn->socks_request->socks_extended_error_code = code;
}
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 (desc == NULL) {
goto close;
}
if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk,
desc)) {
goto close;
}
if (hs_client_reextend_intro_circuit(intro_circ) < 0) {
goto close;
}
ret = 0;
goto end;
close:
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);
}
rend_circ = hs_circuitmap_get_rend_circ_client_side(
intro_circ->hs_ident->rendezvous_cookie);
if (rend_circ) {
circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED);
}
end:
return ret;
}
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");
uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie;
rend_circ =
hs_circuitmap_get_established_rend_circ_client_side(rendezvous_cookie);
if (rend_circ == NULL) {
log_info(LD_REND, "Can't find any rendezvous circuit. Stopping");
goto end;
}
assert_circ_anonymity_ok(rend_circ, get_options());
if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) {
goto end;
}
circuit_change_purpose(TO_CIRCUIT(rend_circ),
CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED);
TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL);
end:
circuit_change_purpose(TO_CIRCUIT(intro_circ),
CIRCUIT_PURPOSE_C_INTRODUCE_ACKED);
circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED);
return;
}
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);
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk,
&circ->hs_ident->intro_auth_pk,
INTRO_POINT_FAILURE_GENERIC);
}
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 TRUNNEL_HS_INTRO_ACK_STATUS_SUCCESS:
ret = 0;
handle_introduce_ack_success(circ);
goto end;
case TRUNNEL_HS_INTRO_ACK_STATUS_UNKNOWN_ID:
case TRUNNEL_HS_INTRO_ACK_STATUS_BAD_FORMAT:
default:
handle_introduce_ack_bad(circ, status);
ret = close_or_reextend_intro_circ(circ);
break;
}
end:
return ret;
}
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);
ident = circ->hs_ident;
tor_assert(ident);
if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info,
sizeof(handshake_info)) < 0) {
goto err;
}
memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN);
memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac));
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;
}
if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) {
log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell.");
goto err;
}
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;
}
ret = 0;
goto end;
err:
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
end:
memwipe(&keys, 0, sizeof(keys));
return ret;
}
static unsigned int
can_client_refetch_desc(const ed25519_public_key_t *identity_pk,
hs_client_fetch_status_t *status_out)
{
hs_client_fetch_status_t status;
tor_assert(identity_pk);
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.");
status = HS_CLIENT_FETCH_NOT_ALLOWED;
goto cannot;
}
if (!networkstatus_get_reasonably_live_consensus(approx_time(),
usable_consensus_flavor())) {
log_info(LD_REND, "Can't fetch descriptor for service %s because we "
"are missing a live consensus. Stalling connection.",
safe_str_client(ed25519_fmt(identity_pk)));
status = HS_CLIENT_FETCH_MISSING_INFO;
goto cannot;
}
if (!router_have_minimum_dir_info()) {
log_info(LD_REND, "Can't fetch descriptor for service %s because we "
"dont have enough descriptors. Stalling connection.",
safe_str_client(ed25519_fmt(identity_pk)));
status = HS_CLIENT_FETCH_MISSING_INFO;
goto cannot;
}
{
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(identity_pk,
cached_desc)) {
log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor "
"but we already have a usable descriptor.");
status = HS_CLIENT_FETCH_HAVE_DESC;
goto cannot;
}
}
if (directory_request_is_pending(identity_pk)) {
log_info(LD_REND, "Already a pending directory request. Waiting on it.");
status = HS_CLIENT_FETCH_PENDING;
goto cannot;
}
return 1;
cannot:
if (status_out) {
*status_out = status;
}
return 0;
}
STATIC void
purge_ephemeral_client_auth(void)
{
DIGEST256MAP_FOREACH_MODIFY(client_auths, key,
hs_client_service_authorization_t *, auth) {
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.");
}
static hs_client_service_authorization_t *
find_client_auth(const ed25519_public_key_t *service_identity_pk)
{
if (!client_auths) {
return NULL;
}
return digest256map_get(client_auths, service_identity_pk->pubkey);
}
static void
client_desc_has_arrived(const smartlist_t *entry_conns)
{
time_t now = time(NULL);
tor_assert(entry_conns);
SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) {
const hs_descriptor_t *desc;
edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn);
const ed25519_public_key_t *identity_pk =
&edge_conn->hs_ident->identity_pk;
desc = hs_cache_lookup_as_client(identity_pk);
if (BUG(desc == NULL)) {
goto end;
}
if (!hs_client_any_intro_points_usable(identity_pk, desc)) {
log_info(LD_REND, "Hidden service descriptor is unusable. "
"Closing streams.");
socks_mark_introduction_failed(entry_conn, identity_pk);
connection_mark_unattached_ap(entry_conn,
END_STREAM_REASON_RESOLVEFAILED);
note_connection_attempt_succeeded(edge_conn->hs_ident);
continue;
}
log_info(LD_REND, "Descriptor has arrived. Launching circuits.");
mark_conn_as_waiting_for_circuit(&edge_conn->base_, now);
} SMARTLIST_FOREACH_END(entry_conn);
end:
return;
}
static void
client_desc_missing_bad_client_auth(const smartlist_t *entry_conns,
hs_desc_decode_status_t status)
{
tor_assert(entry_conns);
SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) {
socks5_reply_status_t code;
if (status == HS_DESC_DECODE_BAD_CLIENT_AUTH) {
code = SOCKS5_HS_BAD_CLIENT_AUTH;
} else if (status == HS_DESC_DECODE_NEED_CLIENT_AUTH) {
code = SOCKS5_HS_MISSING_CLIENT_AUTH;
} else {
tor_assert_nonfatal_unreached();
code = SOCKS5_GENERAL_ERROR;
}
entry_conn->socks_request->socks_extended_error_code = code;
connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_MISC);
} SMARTLIST_FOREACH_END(entry_conn);
}
static void
client_dir_fetch_200(dir_connection_t *dir_conn,
const smartlist_t *entry_conns, const char *body)
{
hs_desc_decode_status_t decode_status;
tor_assert(dir_conn);
tor_assert(entry_conns);
tor_assert(body);
decode_status = hs_cache_store_as_client(body,
&dir_conn->hs_ident->identity_pk);
switch (decode_status) {
case HS_DESC_DECODE_OK:
case HS_DESC_DECODE_NEED_CLIENT_AUTH:
case HS_DESC_DECODE_BAD_CLIENT_AUTH:
log_info(LD_REND, "Stored hidden service descriptor successfully.");
TO_CONN(dir_conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC;
if (decode_status == HS_DESC_DECODE_OK) {
client_desc_has_arrived(entry_conns);
} else {
client_desc_missing_bad_client_auth(entry_conns, decode_status);
log_info(LD_REND, "Stored hidden service descriptor requires "
"%s client authorization.",
decode_status == HS_DESC_DECODE_NEED_CLIENT_AUTH ? "missing"
: "new");
}
hs_control_desc_event_received(dir_conn->hs_ident,
dir_conn->identity_digest);
hs_control_desc_event_content(dir_conn->hs_ident,
dir_conn->identity_digest, body);
break;
case HS_DESC_DECODE_ENCRYPTED_ERROR:
case HS_DESC_DECODE_SUPERENC_ERROR:
case HS_DESC_DECODE_PLAINTEXT_ERROR:
case HS_DESC_DECODE_GENERIC_ERROR:
default:
log_info(LD_REND, "Failed to store hidden service descriptor. "
"Descriptor decoding status: %d", decode_status);
hs_control_desc_event_failed(dir_conn->hs_ident,
dir_conn->identity_digest, "BAD_DESC");
hs_control_desc_event_content(dir_conn->hs_ident,
dir_conn->identity_digest, NULL);
break;
}
}
static void
client_dir_fetch_404(dir_connection_t *dir_conn,
const smartlist_t *entry_conns)
{
tor_assert(entry_conns);
log_info(LD_REND, "Fetching hidden service v3 descriptor not found: "
"Retrying at another directory.");
hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest,
"NOT_FOUND");
hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest,
NULL);
SMARTLIST_FOREACH_BEGIN(entry_conns, entry_connection_t *, entry_conn) {
entry_conn->socks_request->socks_extended_error_code =
SOCKS5_HS_NOT_FOUND;
} SMARTLIST_FOREACH_END(entry_conn);
}
static void
client_dir_fetch_400(dir_connection_t *dir_conn, const char *reason)
{
tor_assert(dir_conn);
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));
hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest,
"QUERY_REJECTED");
hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest,
NULL);
}
static void
client_dir_fetch_unexpected(dir_connection_t *dir_conn, const char *reason,
const int status_code)
{
tor_assert(dir_conn);
log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: "
"http status %d (%s) response unexpected from HSDir "
"server %s'. Retrying at another directory.",
status_code, escaped(reason),
connection_describe_peer(TO_CONN(dir_conn)));
hs_control_desc_event_failed(dir_conn->hs_ident, dir_conn->identity_digest,
"UNEXPECTED");
hs_control_desc_event_content(dir_conn->hs_ident, dir_conn->identity_digest,
NULL);
}
static char *
get_client_auth_creds_filename(const char *onion_address,
const char *dir)
{
char *full_fname = NULL;
char *fname;
tor_asprintf(&fname, "%s.auth_private", onion_address);
full_fname = hs_path_from_filename(dir, fname);
tor_free(fname);
return full_fname;
}
static int
store_permanent_client_auth_credentials(
const hs_client_service_authorization_t *creds)
{
const or_options_t *options = get_options();
char *full_fname = NULL;
char *file_contents = NULL;
char priv_key_b32[BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)+1];
int retval = -1;
tor_assert(creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT);
if (!options->ClientOnionAuthDir) {
log_warn(LD_GENERAL, "Can't register permanent client auth credentials "
"for %s without ClientOnionAuthDir option. Discarding.",
creds->onion_address);
goto err;
}
if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) {
goto err;
}
full_fname = get_client_auth_creds_filename(creds->onion_address,
options->ClientOnionAuthDir);
base32_encode(priv_key_b32, sizeof(priv_key_b32),
(char*)creds->enc_seckey.secret_key,
sizeof(creds->enc_seckey.secret_key));
tor_asprintf(&file_contents, "%s:descriptor:x25519:%s",
creds->onion_address, priv_key_b32);
if (write_str_to_file(full_fname, file_contents, 0) < 0) {
log_warn(LD_GENERAL, "Failed to write client auth creds file for %s!",
creds->onion_address);
goto err;
}
retval = 0;
err:
tor_free(file_contents);
tor_free(full_fname);
return retval;
}
hs_client_register_auth_status_t
hs_client_register_auth_credentials(hs_client_service_authorization_t *creds)
{
ed25519_public_key_t service_identity_pk;
hs_client_service_authorization_t *old_creds = NULL;
hs_client_register_auth_status_t retval = REGISTER_SUCCESS;
tor_assert(creds);
if (!client_auths) {
client_auths = digest256map_new();
}
if (hs_parse_address(creds->onion_address, &service_identity_pk,
NULL, NULL) < 0) {
client_service_authorization_free(creds);
return REGISTER_FAIL_BAD_ADDRESS;
}
if (creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) {
if (store_permanent_client_auth_credentials(creds) < 0) {
client_service_authorization_free(creds);
return REGISTER_FAIL_PERMANENT_STORAGE;
}
}
old_creds = digest256map_get(client_auths, service_identity_pk.pubkey);
if (old_creds) {
digest256map_remove(client_auths, service_identity_pk.pubkey);
client_service_authorization_free(old_creds);
retval = REGISTER_SUCCESS_ALREADY_EXISTS;
}
digest256map_set(client_auths, service_identity_pk.pubkey, creds);
if (hs_cache_client_new_auth_parse(&service_identity_pk)) {
retval = REGISTER_SUCCESS_AND_DECRYPTED;
}
return retval;
}
static hs_client_service_authorization_t *
get_creds_from_client_auth_filename(const char *filename,
const or_options_t *options)
{
hs_client_service_authorization_t *auth = NULL;
char *client_key_file_path = NULL;
char *client_key_str = NULL;
log_info(LD_REND, "Loading a client authorization key file %s...",
filename);
if (!auth_key_filename_is_valid(filename)) {
log_notice(LD_REND, "Client authorization unrecognized filename %s. "
"File must end in .auth_private. Ignoring.",
filename);
goto err;
}
client_key_file_path = hs_path_from_filename(options->ClientOnionAuthDir,
filename);
client_key_str = read_file_to_str(client_key_file_path, 0, NULL);
if (!client_key_str) {
log_warn(LD_REND, "The file %s cannot be read.", filename);
goto err;
}
auth = parse_auth_file_content(client_key_str);
if (!auth) {
goto err;
}
err:
tor_free(client_key_str);
tor_free(client_key_file_path);
return auth;
}
static void
remove_client_auth_creds_file(const char *filename)
{
char *creds_file_path = NULL;
const or_options_t *options = get_options();
creds_file_path = hs_path_from_filename(options->ClientOnionAuthDir,
filename);
if (tor_unlink(creds_file_path) != 0) {
log_warn(LD_REND, "Failed to remove client auth file (%s).",
creds_file_path);
goto end;
}
log_warn(LD_REND, "Successfully removed client auth file (%s).",
creds_file_path);
end:
tor_free(creds_file_path);
}
static void
find_and_remove_client_auth_creds_file(
const hs_client_service_authorization_t *cred)
{
smartlist_t *file_list = NULL;
const or_options_t *options = get_options();
tor_assert(cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT);
if (!options->ClientOnionAuthDir) {
log_warn(LD_REND, "Found permanent credential but no ClientOnionAuthDir "
"configured. There is no file to be removed.");
goto end;
}
file_list = tor_listdir(options->ClientOnionAuthDir);
if (file_list == NULL) {
log_warn(LD_REND, "Client authorization key directory %s can't be listed.",
options->ClientOnionAuthDir);
goto end;
}
SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) {
hs_client_service_authorization_t *tmp_cred = NULL;
tmp_cred = get_creds_from_client_auth_filename(filename, options);
if (!tmp_cred) {
continue;
}
if (!strcmp(tmp_cred->onion_address, cred->onion_address)) {
remove_client_auth_creds_file(filename);
client_service_authorization_free(tmp_cred);
break;
}
client_service_authorization_free(tmp_cred);
} SMARTLIST_FOREACH_END(filename);
end:
if (file_list) {
SMARTLIST_FOREACH(file_list, char *, s, tor_free(s));
smartlist_free(file_list);
}
}
hs_client_removal_auth_status_t
hs_client_remove_auth_credentials(const char *hsaddress)
{
ed25519_public_key_t service_identity_pk;
if (!client_auths) {
return REMOVAL_SUCCESS_NOT_FOUND;
}
if (hs_parse_address(hsaddress, &service_identity_pk, NULL, NULL) < 0) {
return REMOVAL_BAD_ADDRESS;
}
hs_client_service_authorization_t *cred = NULL;
cred = digest256map_remove(client_auths, service_identity_pk.pubkey);
if (cred) {
if (cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) {
find_and_remove_client_auth_creds_file(cred);
}
hs_cache_remove_as_client(&service_identity_pk);
client_service_authorization_free(cred);
return REMOVAL_SUCCESS;
}
return REMOVAL_SUCCESS_NOT_FOUND;
}
digest256map_t *
get_hs_client_auths_map(void)
{
return client_auths;
}
void
hs_client_circuit_cleanup_on_close(const circuit_t *circ)
{
bool has_timed_out;
tor_assert(circ);
tor_assert(CIRCUIT_IS_ORIGIN(circ));
has_timed_out =
(circ->marked_for_close_orig_reason == END_CIRC_REASON_TIMEOUT);
switch (circ->purpose) {
case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
case CIRCUIT_PURPOSE_C_REND_READY:
case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
case CIRCUIT_PURPOSE_C_REND_JOINED:
if (has_timed_out) {
socks_mark_rend_circuit_timed_out(CONST_TO_ORIGIN_CIRCUIT(circ));
}
break;
default:
break;
}
}
void
hs_client_circuit_cleanup_on_free(const circuit_t *circ)
{
bool has_timed_out;
rend_intro_point_failure_t failure = INTRO_POINT_FAILURE_GENERIC;
const origin_circuit_t *orig_circ = NULL;
tor_assert(circ);
tor_assert(CIRCUIT_IS_ORIGIN(circ));
orig_circ = CONST_TO_ORIGIN_CIRCUIT(circ);
tor_assert(orig_circ->hs_ident);
has_timed_out =
(circ->marked_for_close_orig_reason == END_CIRC_REASON_TIMEOUT);
if (has_timed_out) {
failure = INTRO_POINT_FAILURE_TIMEOUT;
}
switch (circ->purpose) {
case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
log_info(LD_REND, "Failed v3 intro circ for service %s to intro point %s "
"(awaiting ACK). Failure code: %d",
safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)),
safe_str_client(build_state_get_exit_nickname(orig_circ->build_state)),
failure);
hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk,
&orig_circ->hs_ident->intro_auth_pk,
failure);
break;
case CIRCUIT_PURPOSE_C_INTRODUCING:
if (has_timed_out || !orig_circ->build_state) {
break;
}
failure = INTRO_POINT_FAILURE_UNREACHABLE;
log_info(LD_REND, "Failed v3 intro circ for service %s to intro point %s "
"(while building circuit). Marking as unreachable.",
safe_str_client(ed25519_fmt(&orig_circ->hs_ident->identity_pk)),
safe_str_client(build_state_get_exit_nickname(orig_circ->build_state)));
hs_cache_client_intro_state_note(&orig_circ->hs_ident->identity_pk,
&orig_circ->hs_ident->intro_auth_pk,
failure);
break;
default:
break;
}
}
void
hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
{
tor_assert(connection_edge_is_rendezvous_stream(conn));
if (conn->hs_ident) {
note_connection_attempt_succeeded(conn->hs_ident);
return;
}
}
hs_desc_decode_status_t
hs_client_decode_descriptor(const char *desc_str,
const ed25519_public_key_t *service_identity_pk,
hs_descriptor_t **desc)
{
hs_desc_decode_status_t ret;
hs_subcredential_t subcredential;
ed25519_public_key_t blinded_pubkey;
hs_client_service_authorization_t *client_auth = NULL;
curve25519_secret_key_t *client_auth_sk = NULL;
tor_assert(desc_str);
tor_assert(service_identity_pk);
tor_assert(desc);
client_auth = find_client_auth(service_identity_pk);
if (client_auth) {
client_auth_sk = &client_auth->enc_seckey;
}
{
uint64_t current_time_period = hs_get_time_period_num(0);
hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
&blinded_pubkey);
hs_get_subcredential(service_identity_pk, &blinded_pubkey, &subcredential);
}
ret = hs_desc_decode_descriptor(desc_str, &subcredential,
client_auth_sk, desc);
memwipe(&subcredential, 0, sizeof(subcredential));
if (ret != HS_DESC_DECODE_OK) {
goto err;
}
tor_cert_t *cert = (*desc)->plaintext_data.signing_key_cert;
if (tor_cert_checksig(cert,
&blinded_pubkey, approx_time()) < 0) {
log_warn(LD_GENERAL, "Descriptor signing key certificate signature "
"doesn't validate with computed blinded key: %s",
tor_cert_describe_signature_status(cert));
ret = HS_DESC_DECODE_GENERIC_ERROR;
goto err;
}
return HS_DESC_DECODE_OK;
err:
return ret;
}
int
hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk,
const hs_descriptor_t *desc)
{
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;
}
int
hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk)
{
hs_client_fetch_status_t status;
tor_assert(identity_pk);
if (!can_client_refetch_desc(identity_pk, &status)) {
return status;
}
status = fetch_v3_desc(identity_pk);
if (fetch_status_should_close_socks(status)) {
close_all_socks_conns_waiting_for_desc(identity_pk, status,
END_STREAM_REASON_RESOLVEFAILED);
purge_hid_serv_request(identity_pk);
}
return status;
}
int
hs_client_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ)
{
return send_introduce1(intro_circ, rend_circ);
}
void
hs_client_circuit_has_opened(origin_circuit_t *circ)
{
tor_assert(circ);
switch (TO_CIRCUIT(circ)->purpose) {
case CIRCUIT_PURPOSE_C_INTRODUCING:
if (circ->hs_ident) {
client_intro_circ_has_opened(circ);
}
break;
case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
if (circ->hs_ident) {
client_rendezvous_circ_has_opened(circ);
}
break;
default:
tor_assert_nonfatal_unreached();
}
}
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);
TO_CIRCUIT(circ)->timestamp_dirty = time(NULL);
pathbias_mark_use_success(circ);
connection_ap_attach_pending(1);
return 0;
err:
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
return -1;
}
void
client_service_authorization_free_(hs_client_service_authorization_t *auth)
{
if (!auth) {
return;
}
tor_free(auth->client_name);
memwipe(auth, 0, sizeof(*auth));
tor_free(auth);
}
static void
client_service_authorization_free_void(void *auth)
{
client_service_authorization_free_(auth);
}
static void
client_service_authorization_free_all(void)
{
if (!client_auths) {
return;
}
digest256map_free(client_auths, client_service_authorization_free_void);
}
STATIC int
auth_key_filename_is_valid(const char *filename)
{
int ret = 1;
const char *valid_extension = ".auth_private";
tor_assert(filename);
if (!strcmpend(filename, valid_extension) &&
strlen(filename) != strlen(valid_extension)) {
ret = 1;
} else {
ret = 0;
}
return ret;
}
STATIC hs_client_service_authorization_t *
parse_auth_file_content(const char *client_key_str)
{
char *onion_address = NULL;
char *auth_type = NULL;
char *key_type = NULL;
char *seckey_b32 = NULL;
hs_client_service_authorization_t *auth = NULL;
smartlist_t *fields = smartlist_new();
tor_assert(client_key_str);
smartlist_split_string(fields, client_key_str, ":",
SPLIT_SKIP_SPACE, 0);
if (smartlist_len(fields) != 4) {
goto err;
}
onion_address = smartlist_get(fields, 0);
auth_type = smartlist_get(fields, 1);
key_type = smartlist_get(fields, 2);
seckey_b32 = smartlist_get(fields, 3);
if (strcmp(auth_type, "descriptor") || strcmp(key_type, "x25519")) {
goto err;
}
if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_SECKEY_LEN)) {
log_warn(LD_REND, "Client authorization encoded base32 private key "
"length is invalid: %s", seckey_b32);
goto err;
}
auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t));
if (base32_decode((char *) auth->enc_seckey.secret_key,
sizeof(auth->enc_seckey.secret_key),
seckey_b32, strlen(seckey_b32)) !=
sizeof(auth->enc_seckey.secret_key)) {
log_warn(LD_REND, "Client authorization encoded base32 private key "
"can't be decoded: %s", seckey_b32);
goto err;
}
if (fast_mem_is_zero((const char*)auth->enc_seckey.secret_key,
sizeof(auth->enc_seckey.secret_key))) {
log_warn(LD_REND, "Client authorization private key can't be all-zeroes");
goto err;
}
strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32);
auth->flags |= CLIENT_AUTH_FLAG_IS_PERMANENT;
goto done;
err:
client_service_authorization_free(auth);
done:
if (seckey_b32) {
memwipe(seckey_b32, 0, strlen(seckey_b32));
}
tor_assert(fields);
SMARTLIST_FOREACH(fields, char *, s, tor_free(s));
smartlist_free(fields);
return auth;
}
int
hs_config_client_authorization(const or_options_t *options,
int validate_only)
{
int ret = -1;
digest256map_t *auths = digest256map_new();
smartlist_t *file_list = NULL;
tor_assert(options);
if (!options->ClientOnionAuthDir) {
ret = 0;
goto end;
}
if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) {
goto end;
}
file_list = tor_listdir(options->ClientOnionAuthDir);
if (file_list == NULL) {
log_warn(LD_REND, "Client authorization key directory %s can't be listed.",
options->ClientOnionAuthDir);
goto end;
}
SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) {
hs_client_service_authorization_t *auth = NULL;
ed25519_public_key_t identity_pk;
auth = get_creds_from_client_auth_filename(filename, options);
if (!auth) {
continue;
}
if (hs_parse_address(auth->onion_address, &identity_pk,
NULL, NULL) < 0) {
log_warn(LD_REND, "The onion address \"%s\" is invalid in "
"file %s", filename, auth->onion_address);
client_service_authorization_free(auth);
continue;
}
if (digest256map_get(auths, identity_pk.pubkey)) {
log_warn(LD_REND, "Duplicate authorization for the same hidden "
"service address %s.",
safe_str_client_opts(options, auth->onion_address));
client_service_authorization_free(auth);
goto end;
}
digest256map_set(auths, identity_pk.pubkey, auth);
log_info(LD_REND, "Loaded a client authorization key file %s.",
filename);
} SMARTLIST_FOREACH_END(filename);
ret = 0;
end:
if (file_list) {
SMARTLIST_FOREACH(file_list, char *, s, tor_free(s));
smartlist_free(file_list);
}
if (!validate_only && ret == 0) {
client_service_authorization_free_all();
client_auths = auths;
} else {
digest256map_free(auths, client_service_authorization_free_void);
}
return ret;
}
void
hs_client_dir_fetch_done(dir_connection_t *dir_conn, const char *reason,
const char *body, const int status_code)
{
smartlist_t *entry_conns;
tor_assert(dir_conn);
tor_assert(body);
entry_conns = find_entry_conns(&dir_conn->hs_ident->identity_pk);
switch (status_code) {
case 200:
client_dir_fetch_200(dir_conn, entry_conns, body);
break;
case 404:
client_dir_fetch_404(dir_conn, entry_conns);
break;
case 400:
client_dir_fetch_400(dir_conn, reason);
break;
default:
client_dir_fetch_unexpected(dir_conn, reason, status_code);
break;
}
smartlist_free(entry_conns);
}
extend_info_t *
hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn)
{
tor_assert(edge_conn);
return client_get_random_intro(&edge_conn->hs_ident->identity_pk);
}
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 = handle_introduce_ack(circ, payload, payload_len);
pathbias_mark_use_success(circ);
end:
return ret;
}
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);
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 = handle_rendezvous2(circ, payload, payload_len);
end:
return ret;
}
int
hs_client_reextend_intro_circuit(origin_circuit_t *circ)
{
int ret = -1;
extend_info_t *ei;
tor_assert(circ);
ei = client_get_random_intro(&circ->hs_ident->identity_pk);
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);
if (ret == 0) {
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);
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
ret = 0;
}
end:
extend_info_free(ei);
return ret;
}
void
hs_client_close_intro_circuits_from_desc(const hs_descriptor_t *desc)
{
origin_circuit_t *ocirc = NULL;
tor_assert(desc);
while ((ocirc = circuit_get_next_intro_circ(ocirc, true))) {
if (ocirc->hs_ident == NULL) {
continue;
}
if (find_desc_intro_point_by_ident(ocirc->hs_ident, desc) == NULL) {
continue;
}
circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
}
}
void
hs_client_free_all(void)
{
hs_purge_last_hid_serv_requests();
client_service_authorization_free_all();
}
void
hs_client_purge_state(void)
{
cancel_descriptor_fetches();
hs_cache_client_intro_state_purge();
hs_cache_purge_as_client();
hs_purge_last_hid_serv_requests();
purge_ephemeral_client_auth();
log_info(LD_REND, "Hidden service client state has been purged.");
}
void
hs_client_dir_info_changed(void)
{
retry_all_socks_conn_waiting_for_desc();
}
#ifdef TOR_UNIT_TESTS
STATIC void
set_hs_client_auths_map(digest256map_t *map)
{
client_auths = map;
}
#endif