mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-10 21:23:58 +01:00
Merge branch 'ticket20657_nickm_bugfixes_squashed'
This commit is contained in:
commit
34e4122025
@ -245,13 +245,7 @@ ed25519_donna_sign(unsigned char *sig, const unsigned char *m, size_t mlen,
|
||||
static void
|
||||
ed25519_donna_gettweak(unsigned char *out, const unsigned char *param)
|
||||
{
|
||||
static const char str[] = "Derive temporary signing key";
|
||||
ed25519_hash_context ctx;
|
||||
|
||||
ed25519_hash_init(&ctx);
|
||||
ed25519_hash_update(&ctx, (const unsigned char*)str, strlen(str));
|
||||
ed25519_hash_update(&ctx, param, 32);
|
||||
ed25519_hash_final(&ctx, out);
|
||||
memcpy(out, param, 32);
|
||||
|
||||
out[0] &= 248; /* Is this necessary ? */
|
||||
out[31] &= 63;
|
||||
|
@ -12,8 +12,8 @@
|
||||
static void
|
||||
ed25519_ref10_gettweak(unsigned char *out, const unsigned char *param)
|
||||
{
|
||||
const char str[] = "Derive temporary signing key";
|
||||
crypto_hash_sha512_2(out, (const unsigned char*)str, strlen(str), param, 32);
|
||||
memcpy(out, param, 32);
|
||||
|
||||
out[0] &= 248; /* Is this necessary necessary ? */
|
||||
out[31] &= 63;
|
||||
out[31] |= 64;
|
||||
|
@ -65,6 +65,7 @@
|
||||
#include "control.h"
|
||||
#include "entrynodes.h"
|
||||
#include "main.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_circuitmap.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_ident.h"
|
||||
@ -1532,6 +1533,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Return the first service rendezvous circuit originating from the global
|
||||
* circuit list after <b>start</b> or at the start of the list if <b>start</b>
|
||||
* is NULL. Return NULL if no circuit is found.
|
||||
*
|
||||
* A service rendezvous point circuit has a purpose of either
|
||||
* CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does
|
||||
* not return a circuit marked for close and its state must be open. */
|
||||
origin_circuit_t *
|
||||
circuit_get_next_service_rp_circ(origin_circuit_t *start)
|
||||
{
|
||||
int idx = 0;
|
||||
smartlist_t *lst = circuit_get_global_list();
|
||||
|
||||
if (start) {
|
||||
idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1;
|
||||
}
|
||||
|
||||
for ( ; idx < smartlist_len(lst); ++idx) {
|
||||
circuit_t *circ = smartlist_get(lst, idx);
|
||||
|
||||
/* Ignore a marked for close circuit or purpose not matching a service
|
||||
* intro point or if the state is not open. */
|
||||
if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN ||
|
||||
(circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND &&
|
||||
circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) {
|
||||
continue;
|
||||
}
|
||||
/* The purposes we are looking for are only for origin circuits so the
|
||||
* following is valid. */
|
||||
return TO_ORIGIN_CIRCUIT(circ);
|
||||
}
|
||||
/* Not found. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Return the first circuit originating here in global_circuitlist after
|
||||
* <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if
|
||||
* set) matches the private key digest of the rend data associated with the
|
||||
@ -1913,6 +1949,13 @@ circuit_about_to_free(circuit_t *circ)
|
||||
orig_reason);
|
||||
}
|
||||
|
||||
/* Notify the HS subsystem for any intro point circuit closing so it can be
|
||||
* dealt with cleanly. */
|
||||
if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
|
||||
circ->purpose == CIRCUIT_PURPOSE_S_INTRO) {
|
||||
hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ));
|
||||
}
|
||||
|
||||
if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
|
||||
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
|
||||
int timed_out = (reason == END_CIRC_REASON_TIMEOUT);
|
||||
|
@ -48,6 +48,7 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data(
|
||||
origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
|
||||
const uint8_t *digest, uint8_t purpose);
|
||||
origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start);
|
||||
origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start);
|
||||
origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start);
|
||||
origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose,
|
||||
extend_info_t *info, int flags);
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "entrynodes.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_client.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_ident.h"
|
||||
#include "nodelist.h"
|
||||
#include "networkstatus.h"
|
||||
@ -782,7 +783,7 @@ circuit_expire_building(void)
|
||||
victim->state, circuit_state_to_string(victim->state),
|
||||
victim->purpose);
|
||||
TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
|
||||
rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim));
|
||||
hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1113,11 +1114,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
|
||||
/* Return true if we need any more hidden service server circuits.
|
||||
* HS servers only need an internal circuit. */
|
||||
STATIC int
|
||||
needs_hs_server_circuits(int num_uptime_internal)
|
||||
needs_hs_server_circuits(time_t now, int num_uptime_internal)
|
||||
{
|
||||
return (num_rend_services() &&
|
||||
num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS &&
|
||||
router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
|
||||
if (!rend_num_services() && !hs_service_get_num_services()) {
|
||||
/* No services, we don't need anything. */
|
||||
goto no_need;
|
||||
}
|
||||
|
||||
if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) {
|
||||
/* We have sufficient amount of internal circuit. */
|
||||
goto no_need;
|
||||
}
|
||||
|
||||
if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
|
||||
/* Consensus hasn't been checked or might be invalid so requesting
|
||||
* internal circuits is not wise. */
|
||||
goto no_need;
|
||||
}
|
||||
|
||||
/* At this point, we need a certain amount of circuits and we will most
|
||||
* likely use them for rendezvous so we note down the use of internal
|
||||
* circuit for our prediction for circuit needing uptime and capacity. */
|
||||
rep_hist_note_used_internal(now, 1, 1);
|
||||
|
||||
return 1;
|
||||
no_need:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need at least this many internal circuits for hidden service clients */
|
||||
@ -1216,7 +1238,7 @@ circuit_predict_and_launch_new(void)
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_hs_server_circuits(num_uptime_internal)) {
|
||||
if (needs_hs_server_circuits(now, num_uptime_internal)) {
|
||||
flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
|
||||
CIRCLAUNCH_IS_INTERNAL);
|
||||
|
||||
@ -1280,11 +1302,6 @@ circuit_build_needed_circs(time_t now)
|
||||
if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
|
||||
connection_ap_rescan_and_attach_pending();
|
||||
|
||||
/* make sure any hidden services have enough intro points
|
||||
* HS intro point streams only require an internal circuit */
|
||||
if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
|
||||
rend_consider_services_intro_points();
|
||||
|
||||
circuit_expire_old_circs_as_needed(now);
|
||||
|
||||
if (!options->DisablePredictedCircuits)
|
||||
@ -1366,8 +1383,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
|
||||
* number of streams on the circuit associated with the rend service.
|
||||
*/
|
||||
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
|
||||
tor_assert(origin_circ->rend_data);
|
||||
origin_circ->rend_data->nr_streams--;
|
||||
hs_dec_rdv_stream_counter(origin_circ);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1641,11 +1657,11 @@ circuit_has_opened(origin_circuit_t *circ)
|
||||
break;
|
||||
case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
|
||||
/* at the service, waiting for introductions */
|
||||
rend_service_intro_has_opened(circ);
|
||||
hs_service_circuit_has_opened(circ);
|
||||
break;
|
||||
case CIRCUIT_PURPOSE_S_CONNECT_REND:
|
||||
/* at the service, connecting to rend point */
|
||||
rend_service_rendezvous_has_opened(circ);
|
||||
hs_service_circuit_has_opened(circ);
|
||||
break;
|
||||
case CIRCUIT_PURPOSE_TESTING:
|
||||
circuit_testing_opened(circ);
|
||||
@ -1795,7 +1811,7 @@ circuit_build_failed(origin_circuit_t *circ)
|
||||
"(%s hop failed).",
|
||||
escaped(build_state_get_exit_nickname(circ->build_state)),
|
||||
failed_at_last_hop?"last":"non-last");
|
||||
rend_service_relaunch_rendezvous(circ);
|
||||
hs_circ_retry_service_rendezvous_point(circ);
|
||||
break;
|
||||
/* default:
|
||||
* This won't happen in normal operation, but might happen if the
|
||||
|
@ -68,7 +68,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ);
|
||||
STATIC int needs_exit_circuits(time_t now,
|
||||
int *port_needs_uptime,
|
||||
int *port_needs_capacity);
|
||||
STATIC int needs_hs_server_circuits(int num_uptime_internal);
|
||||
STATIC int needs_hs_server_circuits(time_t now,
|
||||
int num_uptime_internal);
|
||||
|
||||
STATIC int needs_hs_client_circuits(time_t now,
|
||||
int *needs_uptime,
|
||||
|
@ -76,6 +76,7 @@
|
||||
#include "dirserv.h"
|
||||
#include "hibernate.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "main.h"
|
||||
#include "nodelist.h"
|
||||
#include "policies.h"
|
||||
@ -3066,6 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the
|
||||
* connection, attach it to the circ and connect it. Return 0 on success
|
||||
* or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port
|
||||
* where the caller should close the circuit. */
|
||||
static int
|
||||
handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
|
||||
{
|
||||
int ret;
|
||||
origin_circuit_t *origin_circ;
|
||||
|
||||
assert_circuit_ok(circ);
|
||||
tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
|
||||
tor_assert(conn);
|
||||
|
||||
log_debug(LD_REND, "Connecting the hidden service rendezvous circuit "
|
||||
"to the service destination.");
|
||||
|
||||
origin_circ = TO_ORIGIN_CIRCUIT(circ);
|
||||
conn->base_.address = tor_strdup("(rendezvous)");
|
||||
conn->base_.state = EXIT_CONN_STATE_CONNECTING;
|
||||
|
||||
/* The circuit either has an hs identifier for v3+ or a rend_data for legacy
|
||||
* service. */
|
||||
if (origin_circ->rend_data) {
|
||||
conn->rend_data = rend_data_dup(origin_circ->rend_data);
|
||||
tor_assert(connection_edge_is_rendezvous_stream(conn));
|
||||
ret = rend_service_set_connection_addr_port(conn, origin_circ);
|
||||
} else if (origin_circ->hs_ident) {
|
||||
/* Setup the identifier to be the one for the circuit service. */
|
||||
conn->hs_ident =
|
||||
hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk);
|
||||
tor_assert(connection_edge_is_rendezvous_stream(conn));
|
||||
ret = hs_service_set_conn_addr_port(origin_circ, conn);
|
||||
} else {
|
||||
/* We should never get here if the circuit's purpose is rendezvous. */
|
||||
tor_assert_nonfatal_unreached();
|
||||
return -1;
|
||||
}
|
||||
if (ret < 0) {
|
||||
log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)",
|
||||
fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port);
|
||||
/* Send back reason DONE because we want to make hidden service port
|
||||
* scanning harder thus instead of returning that the exit policy
|
||||
* didn't match, which makes it obvious that the port is closed,
|
||||
* return DONE and kill the circuit. That way, a user (malicious or
|
||||
* not) needs one circuit per bad port unless it matches the policy of
|
||||
* the hidden service. */
|
||||
relay_send_end_cell_from_edge(conn->stream_id, circ,
|
||||
END_STREAM_REASON_DONE,
|
||||
origin_circ->cpath->prev);
|
||||
connection_free(TO_CONN(conn));
|
||||
|
||||
/* Drop the circuit here since it might be someone deliberately
|
||||
* scanning the hidden service ports. Note that this mitigates port
|
||||
* scanning by adding more work on the attacker side to successfully
|
||||
* scan but does not fully solve it. */
|
||||
if (ret < -1) {
|
||||
return END_CIRC_AT_ORIGIN;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Link the circuit and the connection crypt path. */
|
||||
conn->cpath_layer = origin_circ->cpath->prev;
|
||||
|
||||
/* Add it into the linked list of p_streams on this circuit */
|
||||
conn->next_stream = origin_circ->p_streams;
|
||||
origin_circ->p_streams = conn;
|
||||
conn->on_circuit = circ;
|
||||
assert_circuit_ok(circ);
|
||||
|
||||
hs_inc_rdv_stream_counter(origin_circ);
|
||||
|
||||
/* Connect tor to the hidden service destination. */
|
||||
connection_exit_connect(conn);
|
||||
|
||||
/* For path bias: This circuit was used successfully */
|
||||
pathbias_mark_use_success(origin_circ);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** A relay 'begin' or 'begin_dir' cell has arrived, and either we are
|
||||
* an exit hop for the circuit, or we are the origin and it is a
|
||||
* rendezvous begin.
|
||||
@ -3217,58 +3300,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
|
||||
n_stream->deliver_window = STREAMWINDOW_START;
|
||||
|
||||
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
|
||||
tor_assert(origin_circ);
|
||||
log_info(LD_REND,"begin is for rendezvous. configuring stream.");
|
||||
n_stream->base_.address = tor_strdup("(rendezvous)");
|
||||
n_stream->base_.state = EXIT_CONN_STATE_CONNECTING;
|
||||
n_stream->rend_data = rend_data_dup(origin_circ->rend_data);
|
||||
tor_assert(connection_edge_is_rendezvous_stream(n_stream));
|
||||
assert_circuit_ok(circ);
|
||||
|
||||
const int r = rend_service_set_connection_addr_port(n_stream, origin_circ);
|
||||
if (r < 0) {
|
||||
log_info(LD_REND,"Didn't find rendezvous service (port %d)",
|
||||
n_stream->base_.port);
|
||||
/* Send back reason DONE because we want to make hidden service port
|
||||
* scanning harder thus instead of returning that the exit policy
|
||||
* didn't match, which makes it obvious that the port is closed,
|
||||
* return DONE and kill the circuit. That way, a user (malicious or
|
||||
* not) needs one circuit per bad port unless it matches the policy of
|
||||
* the hidden service. */
|
||||
relay_send_end_cell_from_edge(rh.stream_id, circ,
|
||||
END_STREAM_REASON_DONE,
|
||||
layer_hint);
|
||||
connection_free(TO_CONN(n_stream));
|
||||
tor_free(address);
|
||||
|
||||
/* Drop the circuit here since it might be someone deliberately
|
||||
* scanning the hidden service ports. Note that this mitigates port
|
||||
* scanning by adding more work on the attacker side to successfully
|
||||
* scan but does not fully solve it. */
|
||||
if (r < -1)
|
||||
return END_CIRC_AT_ORIGIN;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
assert_circuit_ok(circ);
|
||||
log_debug(LD_REND,"Finished assigning addr/port");
|
||||
n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */
|
||||
|
||||
/* add it into the linked list of p_streams on this circuit */
|
||||
n_stream->next_stream = origin_circ->p_streams;
|
||||
n_stream->on_circuit = circ;
|
||||
origin_circ->p_streams = n_stream;
|
||||
assert_circuit_ok(circ);
|
||||
|
||||
origin_circ->rend_data->nr_streams++;
|
||||
|
||||
connection_exit_connect(n_stream);
|
||||
|
||||
/* For path bias: This circuit was used successfully */
|
||||
pathbias_mark_use_success(origin_circ);
|
||||
|
||||
tor_free(address);
|
||||
return 0;
|
||||
/* We handle this circuit and stream in this function for all supported
|
||||
* hidden service version. */
|
||||
return handle_hs_exit_conn(circ, n_stream);
|
||||
}
|
||||
tor_strlower(address);
|
||||
n_stream->base_.address = address;
|
||||
|
@ -186,6 +186,8 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
|
||||
case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
|
||||
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
|
||||
case DIR_PURPOSE_FETCH_RENDDESC_V2:
|
||||
case DIR_PURPOSE_FETCH_HSDESC:
|
||||
case DIR_PURPOSE_UPLOAD_HSDESC:
|
||||
return 1;
|
||||
case DIR_PURPOSE_SERVER:
|
||||
default:
|
||||
@ -244,6 +246,10 @@ dir_conn_purpose_to_string(int purpose)
|
||||
return "hidden-service v2 descriptor fetch";
|
||||
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
|
||||
return "hidden-service v2 descriptor upload";
|
||||
case DIR_PURPOSE_FETCH_HSDESC:
|
||||
return "hidden-service descriptor fetch";
|
||||
case DIR_PURPOSE_UPLOAD_HSDESC:
|
||||
return "hidden-service descriptor upload";
|
||||
case DIR_PURPOSE_FETCH_MICRODESC:
|
||||
return "microdescriptor fetch";
|
||||
}
|
||||
@ -1034,11 +1040,12 @@ struct directory_request_t {
|
||||
size_t payload_len;
|
||||
/** Value to send in an if-modified-since header, or 0 for none. */
|
||||
time_t if_modified_since;
|
||||
/** Hidden-service-specific information */
|
||||
/** Hidden-service-specific information v2. */
|
||||
const rend_data_t *rend_query;
|
||||
/** Extra headers to append to the request */
|
||||
config_line_t *additional_headers;
|
||||
/** */
|
||||
/** Hidden-service-specific information for v3+. */
|
||||
const hs_ident_dir_conn_t *hs_ident;
|
||||
/** Used internally to directory.c: gets informed when the attempt to
|
||||
* connect to the directory succeeds or fails, if that attempt bears on the
|
||||
* directory's usability as a directory guard. */
|
||||
@ -1268,6 +1275,20 @@ directory_request_set_rend_query(directory_request_t *req,
|
||||
}
|
||||
req->rend_query = query;
|
||||
}
|
||||
/**
|
||||
* Set an object containing HS connection identifier to be associated with
|
||||
* this request. Note that only an alias to <b>ident</b> is stored, so the
|
||||
* <b>ident</b> object must outlive the request.
|
||||
*/
|
||||
void
|
||||
directory_request_upload_set_hs_ident(directory_request_t *req,
|
||||
const hs_ident_dir_conn_t *ident)
|
||||
{
|
||||
if (ident) {
|
||||
tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC);
|
||||
}
|
||||
req->hs_ident = ident;
|
||||
}
|
||||
/** Set a static circuit_guard_state_t object to affliate with the request in
|
||||
* <b>req</b>. This object will receive notification when the attempt to
|
||||
* connect to the guard either succeeds or fails. */
|
||||
@ -1389,6 +1410,7 @@ directory_initiate_request,(directory_request_t *request))
|
||||
const dir_indirection_t indirection = request->indirection;
|
||||
const char *resource = request->resource;
|
||||
const rend_data_t *rend_query = request->rend_query;
|
||||
const hs_ident_dir_conn_t *hs_ident = request->hs_ident;
|
||||
circuit_guard_state_t *guard_state = request->guard_state;
|
||||
|
||||
tor_assert(or_addr_port->port || dir_addr_port->port);
|
||||
@ -1476,8 +1498,16 @@ directory_initiate_request,(directory_request_t *request))
|
||||
conn->dirconn_direct = !anonymized_connection;
|
||||
|
||||
/* copy rendezvous data, if any */
|
||||
if (rend_query)
|
||||
if (rend_query) {
|
||||
/* We can't have both v2 and v3+ identifier. */
|
||||
tor_assert_nonfatal(!hs_ident);
|
||||
conn->rend_data = rend_data_dup(rend_query);
|
||||
}
|
||||
if (hs_ident) {
|
||||
/* We can't have both v2 and v3+ identifier. */
|
||||
tor_assert_nonfatal(!rend_query);
|
||||
conn->hs_ident = hs_ident_dir_conn_dup(hs_ident);
|
||||
}
|
||||
|
||||
if (!anonymized_connection && !use_begindir) {
|
||||
/* then we want to connect to dirport directly */
|
||||
@ -1835,6 +1865,12 @@ directory_send_command(dir_connection_t *conn,
|
||||
httpcommand = "POST";
|
||||
url = tor_strdup("/tor/rendezvous2/publish");
|
||||
break;
|
||||
case DIR_PURPOSE_UPLOAD_HSDESC:
|
||||
tor_assert(resource);
|
||||
tor_assert(payload);
|
||||
httpcommand = "POST";
|
||||
tor_asprintf(&url, "/tor/hs/%s/publish", resource);
|
||||
break;
|
||||
default:
|
||||
tor_assert(0);
|
||||
return;
|
||||
@ -2189,6 +2225,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *,
|
||||
const response_handler_args_t *);
|
||||
static int handle_response_upload_renddesc_v2(dir_connection_t *,
|
||||
const response_handler_args_t *);
|
||||
static int handle_response_upload_hsdesc(dir_connection_t *,
|
||||
const response_handler_args_t *);
|
||||
|
||||
static int
|
||||
dir_client_decompress_response_body(char **bodyp, size_t *bodylenp,
|
||||
@ -2489,6 +2527,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
|
||||
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
|
||||
rv = handle_response_upload_renddesc_v2(conn, &args);
|
||||
break;
|
||||
case DIR_PURPOSE_UPLOAD_HSDESC:
|
||||
rv = handle_response_upload_hsdesc(conn, &args);
|
||||
break;
|
||||
default:
|
||||
tor_assert_nonfatal_unreached();
|
||||
rv = -1;
|
||||
@ -3180,6 +3221,52 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function: processes a response to a POST request to upload an
|
||||
* hidden service descriptor.
|
||||
**/
|
||||
static int
|
||||
handle_response_upload_hsdesc(dir_connection_t *conn,
|
||||
const response_handler_args_t *args)
|
||||
{
|
||||
const int status_code = args->status_code;
|
||||
const char *reason = args->reason;
|
||||
|
||||
tor_assert(conn);
|
||||
tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC);
|
||||
|
||||
log_info(LD_REND, "Uploaded hidden service descriptor (status %d "
|
||||
"(%s))",
|
||||
status_code, escaped(reason));
|
||||
/* For this directory response, it MUST have an hidden service identifier on
|
||||
* this connection. */
|
||||
tor_assert(conn->hs_ident);
|
||||
switch (status_code) {
|
||||
case 200:
|
||||
log_info(LD_REND, "Uploading hidden service descriptor: "
|
||||
"finished with status 200 (%s)", escaped(reason));
|
||||
/* XXX: Trigger control event. */
|
||||
break;
|
||||
case 400:
|
||||
log_warn(LD_REND, "Uploading hidden service descriptor: http "
|
||||
"status 400 (%s) response from dirserver "
|
||||
"'%s:%d'. Malformed hidden service descriptor?",
|
||||
escaped(reason), conn->base_.address, conn->base_.port);
|
||||
/* XXX: Trigger control event. */
|
||||
break;
|
||||
default:
|
||||
log_warn(LD_REND, "Uploading hidden service descriptor: http "
|
||||
"status %d (%s) response unexpected (server "
|
||||
"'%s:%d').",
|
||||
status_code, escaped(reason), conn->base_.address,
|
||||
conn->base_.port);
|
||||
/* XXX: Trigger control event. */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Called when a directory connection reaches EOF. */
|
||||
int
|
||||
connection_dir_reached_eof(dir_connection_t *conn)
|
||||
|
@ -12,6 +12,8 @@
|
||||
#ifndef TOR_DIRECTORY_H
|
||||
#define TOR_DIRECTORY_H
|
||||
|
||||
#include "hs_ident.h"
|
||||
|
||||
int directories_have_accepted_server_descriptor(void);
|
||||
void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
|
||||
dirinfo_type_t type, const char *payload,
|
||||
@ -71,6 +73,8 @@ void directory_request_set_if_modified_since(directory_request_t *req,
|
||||
time_t if_modified_since);
|
||||
void directory_request_set_rend_query(directory_request_t *req,
|
||||
const rend_data_t *query);
|
||||
void directory_request_upload_set_hs_ident(directory_request_t *req,
|
||||
const hs_ident_dir_conn_t *ident);
|
||||
|
||||
void directory_request_set_routerstatus(directory_request_t *req,
|
||||
const routerstatus_t *rs);
|
||||
|
@ -124,8 +124,10 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
|
||||
if (cache_entry->plaintext_data->revision_counter >=
|
||||
desc->plaintext_data->revision_counter) {
|
||||
log_info(LD_REND, "Descriptor revision counter in our cache is "
|
||||
"greater or equal than the one we received. "
|
||||
"Rejecting!");
|
||||
"greater or equal than the one we received (%d/%d). "
|
||||
"Rejecting!",
|
||||
(int)cache_entry->plaintext_data->revision_counter,
|
||||
(int)desc->plaintext_data->revision_counter);
|
||||
goto err;
|
||||
}
|
||||
/* We now know that the descriptor we just received is a new one so
|
||||
|
584
src/or/hs_cell.c
Normal file
584
src/or/hs_cell.c
Normal file
@ -0,0 +1,584 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_cell.c
|
||||
* \brief Hidden service API for cell creation and handling.
|
||||
**/
|
||||
|
||||
#include "or.h"
|
||||
#include "config.h"
|
||||
#include "rendservice.h"
|
||||
#include "replaycache.h"
|
||||
|
||||
#include "hs_cell.h"
|
||||
#include "hs_ntor.h"
|
||||
|
||||
/* Trunnel. */
|
||||
#include "ed25519_cert.h"
|
||||
#include "hs/cell_common.h"
|
||||
#include "hs/cell_establish_intro.h"
|
||||
#include "hs/cell_introduce1.h"
|
||||
#include "hs/cell_rendezvous.h"
|
||||
|
||||
/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is
|
||||
* the cell content up to the ENCRYPTED section of length encoded_cell_len.
|
||||
* The encrypted param is the start of the ENCRYPTED section of length
|
||||
* encrypted_len. The mac_key is the key needed for the computation of the MAC
|
||||
* derived from the ntor handshake of length mac_key_len.
|
||||
*
|
||||
* The length mac_out_len must be at least DIGEST256_LEN. */
|
||||
static void
|
||||
compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
|
||||
const uint8_t *encrypted, size_t encrypted_len,
|
||||
const uint8_t *mac_key, size_t mac_key_len,
|
||||
uint8_t *mac_out, size_t mac_out_len)
|
||||
{
|
||||
size_t offset = 0;
|
||||
size_t mac_msg_len;
|
||||
uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0};
|
||||
|
||||
tor_assert(encoded_cell);
|
||||
tor_assert(encrypted);
|
||||
tor_assert(mac_key);
|
||||
tor_assert(mac_out);
|
||||
tor_assert(mac_out_len >= DIGEST256_LEN);
|
||||
|
||||
/* Compute the size of the message which is basically the entire cell until
|
||||
* the MAC field of course. */
|
||||
mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN);
|
||||
tor_assert(mac_msg_len <= sizeof(mac_msg));
|
||||
|
||||
/* First, put the encoded cell in the msg. */
|
||||
memcpy(mac_msg, encoded_cell, encoded_cell_len);
|
||||
offset += encoded_cell_len;
|
||||
/* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which
|
||||
* is junk at this point). */
|
||||
memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN));
|
||||
offset += (encrypted_len - DIGEST256_LEN);
|
||||
tor_assert(offset == mac_msg_len);
|
||||
|
||||
crypto_mac_sha3_256(mac_out, mac_out_len,
|
||||
mac_key, mac_key_len,
|
||||
mac_msg, mac_msg_len);
|
||||
memwipe(mac_msg, 0, sizeof(mac_msg));
|
||||
}
|
||||
|
||||
/* From a set of keys, subcredential and the ENCRYPTED section of an
|
||||
* INTRODUCE2 cell, return a newly allocated intro cell keys structure.
|
||||
* Finally, the client public key is copied in client_pk. On error, return
|
||||
* NULL. */
|
||||
static hs_ntor_intro_cell_keys_t *
|
||||
get_introduce2_key_material(const ed25519_public_key_t *auth_key,
|
||||
const curve25519_keypair_t *enc_key,
|
||||
const uint8_t *subcredential,
|
||||
const uint8_t *encrypted_section,
|
||||
curve25519_public_key_t *client_pk)
|
||||
{
|
||||
hs_ntor_intro_cell_keys_t *keys;
|
||||
|
||||
tor_assert(auth_key);
|
||||
tor_assert(enc_key);
|
||||
tor_assert(subcredential);
|
||||
tor_assert(encrypted_section);
|
||||
tor_assert(client_pk);
|
||||
|
||||
keys = tor_malloc_zero(sizeof(*keys));
|
||||
|
||||
/* First bytes of the ENCRYPTED section are the client public key. */
|
||||
memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);
|
||||
|
||||
if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk,
|
||||
subcredential, keys) < 0) {
|
||||
/* Don't rely on the caller to wipe this on error. */
|
||||
memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
|
||||
tor_free(keys);
|
||||
keys = NULL;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Using the given encryption key, decrypt the encrypted_section of length
|
||||
* encrypted_section_len of an INTRODUCE2 cell and return a newly allocated
|
||||
* buffer containing the decrypted data. On decryption failure, NULL is
|
||||
* returned. */
|
||||
static uint8_t *
|
||||
decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section,
|
||||
size_t encrypted_section_len)
|
||||
{
|
||||
uint8_t *decrypted = NULL;
|
||||
crypto_cipher_t *cipher = NULL;
|
||||
|
||||
tor_assert(enc_key);
|
||||
tor_assert(encrypted_section);
|
||||
|
||||
/* Decrypt ENCRYPTED section. */
|
||||
cipher = crypto_cipher_new_with_bits((char *) enc_key,
|
||||
CURVE25519_PUBKEY_LEN * 8);
|
||||
tor_assert(cipher);
|
||||
|
||||
/* This is symmetric encryption so can't be bigger than the encrypted
|
||||
* section length. */
|
||||
decrypted = tor_malloc_zero(encrypted_section_len);
|
||||
if (crypto_cipher_decrypt(cipher, (char *) decrypted,
|
||||
(const char *) encrypted_section,
|
||||
encrypted_section_len) < 0) {
|
||||
tor_free(decrypted);
|
||||
decrypted = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
crypto_cipher_free(cipher);
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/* Given a pointer to the decrypted data of the ENCRYPTED section of an
|
||||
* INTRODUCE2 cell of length decrypted_len, parse and validate the cell
|
||||
* content. Return a newly allocated cell structure or NULL on error. The
|
||||
* circuit and service object are only used for logging purposes. */
|
||||
static trn_cell_introduce_encrypted_t *
|
||||
parse_introduce2_encrypted(const uint8_t *decrypted_data,
|
||||
size_t decrypted_len, const origin_circuit_t *circ,
|
||||
const hs_service_t *service)
|
||||
{
|
||||
trn_cell_introduce_encrypted_t *enc_cell = NULL;
|
||||
|
||||
tor_assert(decrypted_data);
|
||||
tor_assert(circ);
|
||||
tor_assert(service);
|
||||
|
||||
if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data,
|
||||
decrypted_len) < 0) {
|
||||
log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of "
|
||||
"the INTRODUCE2 cell on circuit %u for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) !=
|
||||
HS_CELL_ONION_KEY_TYPE_NTOR) {
|
||||
log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but "
|
||||
"expected %u on circuit %u for service %s",
|
||||
trn_cell_introduce_encrypted_get_onion_key_type(enc_cell),
|
||||
HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) !=
|
||||
CURVE25519_PUBKEY_LEN) {
|
||||
log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but "
|
||||
"expected %d on circuit %u for service %s",
|
||||
(unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell),
|
||||
CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto err;
|
||||
}
|
||||
/* XXX: Validate NSPEC field as well. */
|
||||
|
||||
return enc_cell;
|
||||
err:
|
||||
trn_cell_introduce_encrypted_free(enc_cell);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA
|
||||
* encryption key. The encoded cell is put in cell_out that MUST at least be
|
||||
* of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on
|
||||
* success else a negative value and cell_out is untouched. */
|
||||
static ssize_t
|
||||
build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key,
|
||||
uint8_t *cell_out)
|
||||
{
|
||||
ssize_t cell_len;
|
||||
|
||||
tor_assert(circ_nonce);
|
||||
tor_assert(enc_key);
|
||||
tor_assert(cell_out);
|
||||
|
||||
memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE);
|
||||
|
||||
cell_len = rend_service_encode_establish_intro_cell((char*)cell_out,
|
||||
RELAY_PAYLOAD_SIZE,
|
||||
enc_key, circ_nonce);
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
/* Parse an INTRODUCE2 cell from payload of size payload_len for the given
|
||||
* service and circuit which are used only for logging purposes. The resulting
|
||||
* parsed cell is put in cell_ptr_out.
|
||||
*
|
||||
* This function only parses prop224 INTRODUCE2 cells even when the intro point
|
||||
* is a legacy intro point. That's because intro points don't actually care
|
||||
* about the contents of the introduce cell. Legacy INTRODUCE cells are only
|
||||
* used by the legacy system now.
|
||||
*
|
||||
* Return 0 on success else a negative value and cell_ptr_out is untouched. */
|
||||
static int
|
||||
parse_introduce2_cell(const hs_service_t *service,
|
||||
const origin_circuit_t *circ, const uint8_t *payload,
|
||||
size_t payload_len,
|
||||
trn_cell_introduce1_t **cell_ptr_out)
|
||||
{
|
||||
trn_cell_introduce1_t *cell = NULL;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(circ);
|
||||
tor_assert(payload);
|
||||
tor_assert(cell_ptr_out);
|
||||
|
||||
/* Parse the cell so we can start cell validation. */
|
||||
if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) {
|
||||
log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u "
|
||||
"for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
*cell_ptr_out = cell;
|
||||
return 0;
|
||||
err:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
|
||||
/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point
|
||||
* object. The encoded cell is put in cell_out that MUST at least be of the
|
||||
* size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else
|
||||
* a negative value and cell_out is untouched. This function also supports
|
||||
* legacy cell creation. */
|
||||
ssize_t
|
||||
hs_cell_build_establish_intro(const char *circ_nonce,
|
||||
const hs_service_intro_point_t *ip,
|
||||
uint8_t *cell_out)
|
||||
{
|
||||
ssize_t cell_len = -1;
|
||||
uint16_t sig_len = ED25519_SIG_LEN;
|
||||
trn_cell_extension_t *ext;
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
|
||||
tor_assert(circ_nonce);
|
||||
tor_assert(ip);
|
||||
|
||||
/* Quickly handle the legacy IP. */
|
||||
if (ip->base.is_only_legacy) {
|
||||
tor_assert(ip->legacy_key);
|
||||
cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key,
|
||||
cell_out);
|
||||
tor_assert(cell_len <= RELAY_PAYLOAD_SIZE);
|
||||
/* Success or not we are done here. */
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Set extension data. None used here. */
|
||||
ext = trn_cell_extension_new();
|
||||
trn_cell_extension_set_num(ext, 0);
|
||||
cell = trn_cell_establish_intro_new();
|
||||
trn_cell_establish_intro_set_extensions(cell, ext);
|
||||
/* Set signature size. Array is then allocated in the cell. We need to do
|
||||
* this early so we can use trunnel API to get the signature length. */
|
||||
trn_cell_establish_intro_set_sig_len(cell, sig_len);
|
||||
trn_cell_establish_intro_setlen_sig(cell, sig_len);
|
||||
|
||||
/* Set AUTH_KEY_TYPE: 2 means ed25519 */
|
||||
trn_cell_establish_intro_set_auth_key_type(cell,
|
||||
HS_INTRO_AUTH_KEY_TYPE_ED25519);
|
||||
|
||||
/* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of
|
||||
* AUTH_KEY to match */
|
||||
{
|
||||
uint16_t auth_key_len = ED25519_PUBKEY_LEN;
|
||||
trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
|
||||
trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
|
||||
/* We do this call _after_ setting the length because it's reallocated at
|
||||
* that point only. */
|
||||
uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell);
|
||||
memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len);
|
||||
}
|
||||
|
||||
/* Calculate HANDSHAKE_AUTH field (MAC). */
|
||||
{
|
||||
ssize_t tmp_cell_enc_len = 0;
|
||||
ssize_t tmp_cell_mac_offset =
|
||||
sig_len + sizeof(cell->sig_len) +
|
||||
trn_cell_establish_intro_getlen_handshake_mac(cell);
|
||||
uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0};
|
||||
uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr;
|
||||
|
||||
/* We first encode the current fields we have in the cell so we can
|
||||
* compute the MAC using the raw bytes. */
|
||||
tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
|
||||
sizeof(tmp_cell_enc),
|
||||
cell);
|
||||
if (BUG(tmp_cell_enc_len < 0)) {
|
||||
goto done;
|
||||
}
|
||||
/* Sanity check. */
|
||||
tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset);
|
||||
|
||||
/* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */
|
||||
crypto_mac_sha3_256(mac, sizeof(mac),
|
||||
(uint8_t *) circ_nonce, DIGEST_LEN,
|
||||
tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset);
|
||||
handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell);
|
||||
memcpy(handshake_ptr, mac, sizeof(mac));
|
||||
|
||||
memwipe(mac, 0, sizeof(mac));
|
||||
memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
|
||||
}
|
||||
|
||||
/* Calculate the cell signature SIG. */
|
||||
{
|
||||
ssize_t tmp_cell_enc_len = 0;
|
||||
ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len));
|
||||
uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr;
|
||||
ed25519_signature_t sig;
|
||||
|
||||
/* We first encode the current fields we have in the cell so we can
|
||||
* compute the signature from the raw bytes of the cell. */
|
||||
tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc,
|
||||
sizeof(tmp_cell_enc),
|
||||
cell);
|
||||
if (BUG(tmp_cell_enc_len < 0)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ed25519_sign_prefixed(&sig, tmp_cell_enc,
|
||||
tmp_cell_enc_len - tmp_cell_sig_offset,
|
||||
ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) {
|
||||
log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell.");
|
||||
goto done;
|
||||
}
|
||||
/* Copy the signature into the cell. */
|
||||
sig_ptr = trn_cell_establish_intro_getarray_sig(cell);
|
||||
memcpy(sig_ptr, sig.sig, sig_len);
|
||||
|
||||
memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
|
||||
}
|
||||
|
||||
/* Encode the cell. Can't be bigger than a standard cell. */
|
||||
cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE,
|
||||
cell);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(cell);
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we
|
||||
* are successful at parsing it, return the length of the parsed cell else a
|
||||
* negative value on error. */
|
||||
ssize_t
|
||||
hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
ssize_t ret;
|
||||
trn_cell_intro_established_t *cell = NULL;
|
||||
|
||||
tor_assert(payload);
|
||||
|
||||
/* Try to parse the payload into a cell making sure we do actually have a
|
||||
* valid cell. */
|
||||
ret = trn_cell_intro_established_parse(&cell, payload, payload_len);
|
||||
if (ret >= 0) {
|
||||
/* On success, we do not keep the cell, we just notify the caller that it
|
||||
* was successfully parsed. */
|
||||
trn_cell_intro_established_free(cell);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parsse the INTRODUCE2 cell using data which contains everything we need to
|
||||
* do so and contains the destination buffers of information we extract and
|
||||
* compute from the cell. Return 0 on success else a negative value. The
|
||||
* service and circ are only used for logging purposes. */
|
||||
ssize_t
|
||||
hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
|
||||
const origin_circuit_t *circ,
|
||||
const hs_service_t *service)
|
||||
{
|
||||
int ret = -1;
|
||||
time_t elapsed;
|
||||
uint8_t *decrypted = NULL;
|
||||
size_t encrypted_section_len;
|
||||
const uint8_t *encrypted_section;
|
||||
trn_cell_introduce1_t *cell = NULL;
|
||||
trn_cell_introduce_encrypted_t *enc_cell = NULL;
|
||||
hs_ntor_intro_cell_keys_t *intro_keys = NULL;
|
||||
|
||||
tor_assert(data);
|
||||
tor_assert(circ);
|
||||
tor_assert(service);
|
||||
|
||||
/* Parse the cell into a decoded data structure pointed by cell_ptr. */
|
||||
if (parse_introduce2_cell(service, circ, data->payload, data->payload_len,
|
||||
&cell) < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u "
|
||||
"for service %s. Decoding encrypted section...",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
|
||||
encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell);
|
||||
encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell);
|
||||
|
||||
/* Encrypted section must at least contain the CLIENT_PK and MAC which is
|
||||
* defined in section 3.3.2 of the specification. */
|
||||
if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) {
|
||||
log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length "
|
||||
"for service %s. Dropping cell.",
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check our replay cache for this introduction point. */
|
||||
if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
|
||||
encrypted_section_len, &elapsed)) {
|
||||
log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the"
|
||||
"same ENCRYPTED section was seen %ld seconds ago. "
|
||||
"Dropping cell.", elapsed);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Build the key material out of the key material found in the cell. */
|
||||
intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
|
||||
data->subcredential,
|
||||
encrypted_section,
|
||||
&data->client_pk);
|
||||
if (intro_keys == NULL) {
|
||||
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
|
||||
"compute key material on circuit %u for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Validate MAC from the cell and our computed key material. The MAC field
|
||||
* in the cell is at the end of the encrypted section. */
|
||||
{
|
||||
uint8_t mac[DIGEST256_LEN];
|
||||
/* The MAC field is at the very end of the ENCRYPTED section. */
|
||||
size_t mac_offset = encrypted_section_len - sizeof(mac);
|
||||
/* Compute the MAC. Use the entire encoded payload with a length up to the
|
||||
* ENCRYPTED section. */
|
||||
compute_introduce_mac(data->payload,
|
||||
data->payload_len - encrypted_section_len,
|
||||
encrypted_section, encrypted_section_len,
|
||||
intro_keys->mac_key, sizeof(intro_keys->mac_key),
|
||||
mac, sizeof(mac));
|
||||
if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) {
|
||||
log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on "
|
||||
"circuit %u for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
|
||||
const uint8_t *encrypted_data =
|
||||
encrypted_section + sizeof(data->client_pk);
|
||||
/* It's symmetric encryption so it's correct to use the ENCRYPTED length
|
||||
* for decryption. Computes the length of ENCRYPTED_DATA meaning removing
|
||||
* the CLIENT_PK and MAC length. */
|
||||
size_t encrypted_data_len =
|
||||
encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN);
|
||||
|
||||
/* This decrypts the ENCRYPTED_DATA section of the cell. */
|
||||
decrypted = decrypt_introduce2(intro_keys->enc_key,
|
||||
encrypted_data, encrypted_data_len);
|
||||
if (decrypted == NULL) {
|
||||
log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an "
|
||||
"INTRODUCE2 cell on circuit %u for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Parse this blob into an encrypted cell structure so we can then extract
|
||||
* the data we need out of it. */
|
||||
enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len,
|
||||
circ, service);
|
||||
memwipe(decrypted, 0, encrypted_data_len);
|
||||
if (enc_cell == NULL) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* XXX: Implement client authorization checks. */
|
||||
|
||||
/* Extract onion key and rendezvous cookie from the cell used for the
|
||||
* rendezvous point circuit e2e encryption. */
|
||||
memcpy(data->onion_pk.public_key,
|
||||
trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell),
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
memcpy(data->rendezvous_cookie,
|
||||
trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell),
|
||||
sizeof(data->rendezvous_cookie));
|
||||
|
||||
/* Extract rendezvous link specifiers. */
|
||||
for (size_t idx = 0;
|
||||
idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) {
|
||||
link_specifier_t *lspec =
|
||||
trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx);
|
||||
smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec));
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit.");
|
||||
|
||||
done:
|
||||
if (intro_keys) {
|
||||
memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t));
|
||||
tor_free(intro_keys);
|
||||
}
|
||||
tor_free(decrypted);
|
||||
trn_cell_introduce_encrypted_free(enc_cell);
|
||||
trn_cell_introduce1_free(cell);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake
|
||||
* info. The encoded cell is put in cell_out and the length of the data is
|
||||
* returned. This can't fail. */
|
||||
ssize_t
|
||||
hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
|
||||
size_t rendezvous_cookie_len,
|
||||
const uint8_t *rendezvous_handshake_info,
|
||||
size_t rendezvous_handshake_info_len,
|
||||
uint8_t *cell_out)
|
||||
{
|
||||
ssize_t cell_len;
|
||||
trn_cell_rendezvous1_t *cell;
|
||||
|
||||
tor_assert(rendezvous_cookie);
|
||||
tor_assert(rendezvous_handshake_info);
|
||||
tor_assert(cell_out);
|
||||
|
||||
cell = trn_cell_rendezvous1_new();
|
||||
/* Set the RENDEZVOUS_COOKIE. */
|
||||
memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell),
|
||||
rendezvous_cookie, rendezvous_cookie_len);
|
||||
/* Set the HANDSHAKE_INFO. */
|
||||
trn_cell_rendezvous1_setlen_handshake_info(cell,
|
||||
rendezvous_handshake_info_len);
|
||||
memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell),
|
||||
rendezvous_handshake_info, rendezvous_handshake_info_len);
|
||||
/* Encoding. */
|
||||
cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell);
|
||||
tor_assert(cell_len > 0);
|
||||
|
||||
trn_cell_rendezvous1_free(cell);
|
||||
return cell_len;
|
||||
}
|
||||
|
75
src/or/hs_cell.h
Normal file
75
src/or/hs_cell.h
Normal file
@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_cell.h
|
||||
* \brief Header file containing cell data for the whole HS subsytem.
|
||||
**/
|
||||
|
||||
#ifndef TOR_HS_CELL_H
|
||||
#define TOR_HS_CELL_H
|
||||
|
||||
#include "or.h"
|
||||
#include "hs_service.h"
|
||||
|
||||
/* Onion key type found in the INTRODUCE1 cell. */
|
||||
typedef enum {
|
||||
HS_CELL_ONION_KEY_TYPE_NTOR = 1,
|
||||
} hs_cell_onion_key_type_t;
|
||||
|
||||
/* This data structure contains data that we need to parse an INTRODUCE2 cell
|
||||
* which is used by the INTRODUCE2 cell parsing function. On a successful
|
||||
* parsing, the onion_pk and rendezvous_cookie will be populated with the
|
||||
* computed key material from the cell data. This structure is only used during
|
||||
* INTRO2 parsing and discarded after that. */
|
||||
typedef struct hs_cell_introduce2_data_t {
|
||||
/*** Immutable Section: Set on structure init. ***/
|
||||
|
||||
/* Introduction point authentication public key. Pointer owned by the
|
||||
introduction point object through which we received the INTRO2 cell. */
|
||||
const ed25519_public_key_t *auth_pk;
|
||||
/* Introduction point encryption keypair for the ntor handshake. Pointer
|
||||
owned by the introduction point object through which we received the
|
||||
INTRO2 cell*/
|
||||
const curve25519_keypair_t *enc_kp;
|
||||
/* Subcredentials of the service. Pointer owned by the descriptor that owns
|
||||
the introduction point through which we received the INTRO2 cell. */
|
||||
const uint8_t *subcredential;
|
||||
/* Payload of the received encoded cell. */
|
||||
const uint8_t *payload;
|
||||
/* Size of the payload of the received encoded cell. */
|
||||
size_t payload_len;
|
||||
|
||||
/*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
|
||||
|
||||
/* Onion public key computed using the INTRODUCE2 encrypted section. */
|
||||
curve25519_public_key_t onion_pk;
|
||||
/* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */
|
||||
uint8_t rendezvous_cookie[REND_COOKIE_LEN];
|
||||
/* Client public key from the INTRODUCE2 encrypted section. */
|
||||
curve25519_public_key_t client_pk;
|
||||
/* Link specifiers of the rendezvous point. Contains link_specifier_t. */
|
||||
smartlist_t *link_specifiers;
|
||||
/* Replay cache of the introduction point. */
|
||||
replaycache_t *replay_cache;
|
||||
} hs_cell_introduce2_data_t;
|
||||
|
||||
/* Build cell API. */
|
||||
ssize_t hs_cell_build_establish_intro(const char *circ_nonce,
|
||||
const hs_service_intro_point_t *ip,
|
||||
uint8_t *cell_out);
|
||||
ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie,
|
||||
size_t rendezvous_cookie_len,
|
||||
const uint8_t *rendezvous_handshake_info,
|
||||
size_t rendezvous_handshake_info_len,
|
||||
uint8_t *cell_out);
|
||||
|
||||
/* Parse cell API. */
|
||||
ssize_t hs_cell_parse_intro_established(const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
|
||||
const origin_circuit_t *circ,
|
||||
const hs_service_t *service);
|
||||
|
||||
#endif /* TOR_HS_CELL_H */
|
||||
|
@ -6,14 +6,27 @@
|
||||
**/
|
||||
|
||||
#include "or.h"
|
||||
#include "circpathbias.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "config.h"
|
||||
#include "policies.h"
|
||||
#include "relay.h"
|
||||
#include "rendservice.h"
|
||||
#include "rephist.h"
|
||||
#include "router.h"
|
||||
|
||||
#include "hs_cell.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_ntor.h"
|
||||
#include "hs_service.h"
|
||||
|
||||
/* Trunnel. */
|
||||
#include "ed25519_cert.h"
|
||||
#include "hs/cell_common.h"
|
||||
#include "hs/cell_establish_intro.h"
|
||||
|
||||
/* A circuit is about to become an e2e rendezvous circuit. Check
|
||||
* <b>circ_purpose</b> and ensure that it's properly set. Return true iff
|
||||
@ -167,6 +180,825 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
|
||||
}
|
||||
}
|
||||
|
||||
/* For a given circuit and a service introduction point object, register the
|
||||
* intro circuit to the circuitmap. This supports legacy intro point. */
|
||||
static void
|
||||
register_intro_circ(const hs_service_intro_point_t *ip,
|
||||
origin_circuit_t *circ)
|
||||
{
|
||||
tor_assert(ip);
|
||||
tor_assert(circ);
|
||||
|
||||
if (ip->base.is_only_legacy) {
|
||||
uint8_t digest[DIGEST_LEN];
|
||||
if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
|
||||
return;
|
||||
}
|
||||
hs_circuitmap_register_intro_circ_v2_service_side(circ, digest);
|
||||
} else {
|
||||
hs_circuitmap_register_intro_circ_v3_service_side(circ,
|
||||
&ip->auth_key_kp.pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the number of opened introduction circuit for the given circuit that
|
||||
* is matching its identity key. */
|
||||
static unsigned int
|
||||
count_opened_desc_intro_point_circuits(const hs_service_t *service,
|
||||
const hs_service_descriptor_t *desc)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(desc);
|
||||
|
||||
DIGEST256MAP_FOREACH(desc->intro_points.map, key,
|
||||
const hs_service_intro_point_t *, ip) {
|
||||
const circuit_t *circ;
|
||||
const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
|
||||
if (ocirc == NULL) {
|
||||
continue;
|
||||
}
|
||||
circ = TO_CIRCUIT(ocirc);
|
||||
tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
|
||||
circ->purpose == CIRCUIT_PURPOSE_S_INTRO);
|
||||
/* Having a circuit not for the requested service is really bad. */
|
||||
tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk,
|
||||
ô->hs_ident->identity_pk));
|
||||
/* Only count opened circuit and skip circuit that will be closed. */
|
||||
if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) {
|
||||
count++;
|
||||
}
|
||||
} DIGEST256MAP_FOREACH_END;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* From a given service, rendezvous cookie and handshake info, create a
|
||||
* rendezvous point circuit identifier. This can't fail. */
|
||||
static hs_ident_circuit_t *
|
||||
create_rp_circuit_identifier(const hs_service_t *service,
|
||||
const uint8_t *rendezvous_cookie,
|
||||
const curve25519_public_key_t *server_pk,
|
||||
const hs_ntor_rend_cell_keys_t *keys)
|
||||
{
|
||||
hs_ident_circuit_t *ident;
|
||||
uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(rendezvous_cookie);
|
||||
tor_assert(server_pk);
|
||||
tor_assert(keys);
|
||||
|
||||
ident = hs_ident_circuit_new(&service->keys.identity_pk,
|
||||
HS_IDENT_CIRCUIT_RENDEZVOUS);
|
||||
/* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */
|
||||
memcpy(ident->rendezvous_cookie, rendezvous_cookie,
|
||||
sizeof(ident->rendezvous_cookie));
|
||||
/* Build the HANDSHAKE_INFO which looks like this:
|
||||
* SERVER_PK [32 bytes]
|
||||
* AUTH_INPUT_MAC [32 bytes]
|
||||
*/
|
||||
memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN);
|
||||
memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac,
|
||||
DIGEST256_LEN);
|
||||
tor_assert(sizeof(ident->rendezvous_handshake_info) ==
|
||||
sizeof(handshake_info));
|
||||
memcpy(ident->rendezvous_handshake_info, handshake_info,
|
||||
sizeof(ident->rendezvous_handshake_info));
|
||||
/* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */
|
||||
tor_assert(sizeof(ident->rendezvous_ntor_key_seed) ==
|
||||
sizeof(keys->ntor_key_seed));
|
||||
memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed,
|
||||
sizeof(ident->rendezvous_ntor_key_seed));
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* From a given service and service intro point, create an introduction point
|
||||
* circuit identifier. This can't fail. */
|
||||
static hs_ident_circuit_t *
|
||||
create_intro_circuit_identifier(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip)
|
||||
{
|
||||
hs_ident_circuit_t *ident;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
|
||||
ident = hs_ident_circuit_new(&service->keys.identity_pk,
|
||||
HS_IDENT_CIRCUIT_INTRO);
|
||||
ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey);
|
||||
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* For a given introduction point and an introduction circuit, send the
|
||||
* ESTABLISH_INTRO cell. The service object is used for logging. This can fail
|
||||
* and if so, the circuit is closed and the intro point object is flagged
|
||||
* that the circuit is not established anymore which is important for the
|
||||
* retry mechanism. */
|
||||
static void
|
||||
send_establish_intro(const hs_service_t *service,
|
||||
hs_service_intro_point_t *ip, origin_circuit_t *circ)
|
||||
{
|
||||
ssize_t cell_len;
|
||||
uint8_t payload[RELAY_PAYLOAD_SIZE];
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
tor_assert(circ);
|
||||
|
||||
/* Encode establish intro cell. */
|
||||
cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce,
|
||||
ip, payload);
|
||||
if (cell_len < 0) {
|
||||
log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s "
|
||||
"on circuit %u. Closing circuit.",
|
||||
safe_str_client(service->onion_address),
|
||||
TO_CIRCUIT(circ)->n_circ_id);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Send the cell on the circuit. */
|
||||
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
|
||||
RELAY_COMMAND_ESTABLISH_INTRO,
|
||||
(char *) payload, cell_len,
|
||||
circ->cpath->prev) < 0) {
|
||||
log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s "
|
||||
"on circuit %u.",
|
||||
safe_str_client(service->onion_address),
|
||||
TO_CIRCUIT(circ)->n_circ_id);
|
||||
/* On error, the circuit has been closed. */
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Record the attempt to use this circuit. */
|
||||
pathbias_count_use_attempt(circ);
|
||||
goto done;
|
||||
|
||||
err:
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
|
||||
done:
|
||||
memwipe(payload, 0, sizeof(payload));
|
||||
}
|
||||
|
||||
/* From a list of link specifier, an onion key and if we are requesting a
|
||||
* direct connection (ex: single onion service), return a newly allocated
|
||||
* extend_info_t object. This function checks the firewall policies and if we
|
||||
* are allowed to extend to the chosen address.
|
||||
*
|
||||
* if either IPv4 or legacy ID is missing, error.
|
||||
* if not direct_conn, IPv4 is prefered.
|
||||
* if direct_conn, IPv6 is prefered if we have one available.
|
||||
* if firewall does not allow the chosen address, error.
|
||||
*
|
||||
* Return NULL if we can't fulfill the conditions. */
|
||||
static extend_info_t *
|
||||
get_rp_extend_info(const smartlist_t *link_specifiers,
|
||||
const curve25519_public_key_t *onion_key, int direct_conn)
|
||||
{
|
||||
int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
|
||||
char legacy_id[DIGEST_LEN] = {0};
|
||||
uint16_t port_v4 = 0, port_v6 = 0, port = 0;
|
||||
tor_addr_t addr_v4, addr_v6, *addr = NULL;
|
||||
ed25519_public_key_t ed25519_pk;
|
||||
extend_info_t *info = NULL;
|
||||
|
||||
tor_assert(link_specifiers);
|
||||
tor_assert(onion_key);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) {
|
||||
switch (link_specifier_get_ls_type(ls)) {
|
||||
case LS_IPV4:
|
||||
/* Skip if we already seen a v4. */
|
||||
if (have_v4) continue;
|
||||
tor_addr_from_ipv4h(&addr_v4,
|
||||
link_specifier_get_un_ipv4_addr(ls));
|
||||
port_v4 = link_specifier_get_un_ipv4_port(ls);
|
||||
have_v4 = 1;
|
||||
break;
|
||||
case LS_IPV6:
|
||||
/* Skip if we already seen a v6. */
|
||||
if (have_v6) continue;
|
||||
tor_addr_from_ipv6_bytes(&addr_v6,
|
||||
(const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
|
||||
port_v6 = link_specifier_get_un_ipv6_port(ls);
|
||||
have_v6 = 1;
|
||||
break;
|
||||
case LS_LEGACY_ID:
|
||||
/* Make sure we do have enough bytes for the legacy ID. */
|
||||
if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
|
||||
break;
|
||||
}
|
||||
memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls),
|
||||
sizeof(legacy_id));
|
||||
have_legacy_id = 1;
|
||||
break;
|
||||
case LS_ED25519_ID:
|
||||
memcpy(ed25519_pk.pubkey,
|
||||
link_specifier_getconstarray_un_ed25519_id(ls),
|
||||
ED25519_PUBKEY_LEN);
|
||||
have_ed25519_id = 1;
|
||||
break;
|
||||
default:
|
||||
/* Ignore unknown. */
|
||||
break;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(ls);
|
||||
|
||||
/* IPv4, legacy ID are mandatory for rend points.
|
||||
* ed25519 keys and ipv6 are optional for rend points */
|
||||
if (!have_v4 || !have_legacy_id) {
|
||||
goto done;
|
||||
}
|
||||
/* By default, we pick IPv4 but this might change to v6 if certain
|
||||
* conditions are met. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
|
||||
/* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
|
||||
* circuit so we can't extend in IPv6. And at this point, we do have an IPv4
|
||||
* address available so go to validation. */
|
||||
if (!direct_conn) {
|
||||
goto validate;
|
||||
}
|
||||
|
||||
/* From this point on, we have a request for a direct connection to the
|
||||
* rendezvous point so make sure we can actually connect through our
|
||||
* firewall. We'll prefer IPv6. */
|
||||
|
||||
/* IPv6 test. */
|
||||
if (have_v6 &&
|
||||
fascist_firewall_allows_address_addr(&addr_v6, port_v6,
|
||||
FIREWALL_OR_CONNECTION, 1, 1)) {
|
||||
/* Direct connection and we can reach it in IPv6 so go for it. */
|
||||
addr = &addr_v6; port = port_v6;
|
||||
goto validate;
|
||||
}
|
||||
/* IPv4 test and we are sure we have a v4 because of the check above. */
|
||||
if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
|
||||
FIREWALL_OR_CONNECTION, 0, 0)) {
|
||||
/* Direct connection and we can reach it in IPv4 so go for it. */
|
||||
addr = &addr_v4; port = port_v4;
|
||||
goto validate;
|
||||
}
|
||||
|
||||
validate:
|
||||
/* We'll validate now that the address we've picked isn't a private one. If
|
||||
* it is, are we allowing to extend to private address? */
|
||||
if (!extend_info_addr_is_allowed(addr)) {
|
||||
log_warn(LD_REND, "Rendezvous point address is private and it is not "
|
||||
"allowed to extend to it: %s:%u",
|
||||
fmt_addr(&addr_v4), port_v4);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* We do have everything for which we think we can connect successfully. */
|
||||
info = extend_info_new(NULL, legacy_id,
|
||||
have_ed25519_id ? &ed25519_pk : NULL,
|
||||
NULL, onion_key,
|
||||
addr, port);
|
||||
done:
|
||||
return info;
|
||||
}
|
||||
|
||||
/* For a given service, the ntor onion key and a rendezvous cookie, launch a
|
||||
* circuit to the rendezvous point specified by the link specifiers. On
|
||||
* success, a circuit identifier is attached to the circuit with the needed
|
||||
* data. This function will try to open a circuit for a maximum value of
|
||||
* MAX_REND_FAILURES then it will give up. */
|
||||
static void
|
||||
launch_rendezvous_point_circuit(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
const hs_cell_introduce2_data_t *data)
|
||||
{
|
||||
int circ_needs_uptime;
|
||||
time_t now = time(NULL);
|
||||
extend_info_t *info = NULL;
|
||||
origin_circuit_t *circ;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
tor_assert(data);
|
||||
|
||||
circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports);
|
||||
|
||||
/* Get the extend info data structure for the chosen rendezvous point
|
||||
* specified by the given link specifiers. */
|
||||
info = get_rp_extend_info(data->link_specifiers, &data->onion_pk,
|
||||
service->config.is_single_onion);
|
||||
if (info == NULL) {
|
||||
/* We are done here, we can't extend to the rendezvous point. */
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_REND_FAILURES; i++) {
|
||||
int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
|
||||
if (circ_needs_uptime) {
|
||||
circ_flags |= CIRCLAUNCH_NEED_UPTIME;
|
||||
}
|
||||
/* Firewall and policies are checked when getting the extend info. */
|
||||
if (service->config.is_single_onion) {
|
||||
circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
|
||||
}
|
||||
|
||||
circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info,
|
||||
circ_flags);
|
||||
if (circ != NULL) {
|
||||
/* Stop retrying, we have a circuit! */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (circ == NULL) {
|
||||
log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s "
|
||||
"for service %s",
|
||||
safe_str_client(extend_info_describe(info)),
|
||||
safe_str_client(service->onion_address));
|
||||
goto end;
|
||||
}
|
||||
log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
|
||||
"for service %s",
|
||||
safe_str_client(extend_info_describe(info)),
|
||||
safe_str_client(hex_str((const char *) data->rendezvous_cookie,
|
||||
REND_COOKIE_LEN)),
|
||||
safe_str_client(service->onion_address));
|
||||
tor_assert(circ->build_state);
|
||||
/* Rendezvous circuit have a specific timeout for the time spent on trying
|
||||
* to connect to the rendezvous point. */
|
||||
circ->build_state->expiry_time = now + MAX_REND_TIMEOUT;
|
||||
|
||||
/* Create circuit identifier and key material. */
|
||||
{
|
||||
hs_ntor_rend_cell_keys_t keys;
|
||||
curve25519_keypair_t ephemeral_kp;
|
||||
/* No need for extra strong, this is only for this circuit life time. This
|
||||
* key will be used for the RENDEZVOUS1 cell that will be sent on the
|
||||
* circuit once opened. */
|
||||
curve25519_keypair_generate(&ephemeral_kp, 0);
|
||||
if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey,
|
||||
&ip->enc_key_kp,
|
||||
&ephemeral_kp, &data->client_pk,
|
||||
&keys) < 0) {
|
||||
/* This should not really happened but just in case, don't make tor
|
||||
* freak out, close the circuit and move on. */
|
||||
log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for "
|
||||
"service %s",
|
||||
safe_str_client(service->onion_address));
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
|
||||
goto end;
|
||||
}
|
||||
circ->hs_ident = create_rp_circuit_identifier(service,
|
||||
data->rendezvous_cookie,
|
||||
&ephemeral_kp.pubkey, &keys);
|
||||
memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp));
|
||||
memwipe(&keys, 0, sizeof(keys));
|
||||
tor_assert(circ->hs_ident);
|
||||
}
|
||||
|
||||
end:
|
||||
extend_info_free(info);
|
||||
}
|
||||
|
||||
/* Return true iff the given service rendezvous circuit circ is allowed for a
|
||||
* relaunch to the rendezvous point. */
|
||||
static int
|
||||
can_relaunch_service_rendezvous_point(const origin_circuit_t *circ)
|
||||
{
|
||||
tor_assert(circ);
|
||||
/* This is initialized when allocating an origin circuit. */
|
||||
tor_assert(circ->build_state);
|
||||
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
|
||||
|
||||
/* XXX: Retrying under certain condition. This is related to #22455. */
|
||||
|
||||
/* Avoid to relaunch twice a circuit to the same rendezvous point at the
|
||||
* same time. */
|
||||
if (circ->hs_service_side_rend_circ_has_been_relaunched) {
|
||||
log_info(LD_REND, "Rendezvous circuit to %s has already been retried. "
|
||||
"Skipping retry.",
|
||||
safe_str_client(
|
||||
extend_info_describe(circ->build_state->chosen_exit)));
|
||||
goto disallow;
|
||||
}
|
||||
|
||||
/* A failure count that has reached maximum allowed or circuit that expired,
|
||||
* we skip relaunching. */
|
||||
if (circ->build_state->failure_count > MAX_REND_FAILURES ||
|
||||
circ->build_state->expiry_time <= time(NULL)) {
|
||||
log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has "
|
||||
"failed with %d attempts and expiry time %ld. "
|
||||
"Giving up building.",
|
||||
safe_str_client(
|
||||
extend_info_describe(circ->build_state->chosen_exit)),
|
||||
circ->build_state->failure_count,
|
||||
circ->build_state->expiry_time);
|
||||
goto disallow;
|
||||
}
|
||||
|
||||
/* Allowed to relaunch. */
|
||||
return 1;
|
||||
disallow:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Retry the rendezvous point of circ by launching a new circuit to it. */
|
||||
static void
|
||||
retry_service_rendezvous_point(const origin_circuit_t *circ)
|
||||
{
|
||||
int flags = 0;
|
||||
origin_circuit_t *new_circ;
|
||||
cpath_build_state_t *bstate;
|
||||
|
||||
tor_assert(circ);
|
||||
/* This is initialized when allocating an origin circuit. */
|
||||
tor_assert(circ->build_state);
|
||||
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
|
||||
|
||||
/* Ease our life. */
|
||||
bstate = circ->build_state;
|
||||
|
||||
log_info(LD_REND, "Retrying rendezvous point circuit to %s",
|
||||
safe_str_client(extend_info_describe(bstate->chosen_exit)));
|
||||
|
||||
/* Get the current build state flags for the next circuit. */
|
||||
flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0;
|
||||
flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0;
|
||||
flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0;
|
||||
|
||||
/* We do NOT add the onehop tunnel flag even though it might be a single
|
||||
* onion service. The reason is that if we failed once to connect to the RP
|
||||
* with a direct connection, we consider that chances are that we will fail
|
||||
* again so try a 3-hop circuit and hope for the best. Because the service
|
||||
* has no anonymity (single onion), this change of behavior won't affect
|
||||
* security directly. */
|
||||
|
||||
new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND,
|
||||
bstate->chosen_exit, flags);
|
||||
if (new_circ == NULL) {
|
||||
log_warn(LD_REND, "Failed to launch rendezvous circuit to %s",
|
||||
safe_str_client(extend_info_describe(bstate->chosen_exit)));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Transfer build state information to the new circuit state in part to
|
||||
* catch any other failures. */
|
||||
new_circ->build_state->failure_count = bstate->failure_count++;
|
||||
new_circ->build_state->expiry_time = bstate->expiry_time;
|
||||
new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident);
|
||||
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
/* ========== */
|
||||
/* Public API */
|
||||
/* ========== */
|
||||
|
||||
/* Return an introduction point circuit matching the given intro point object.
|
||||
* NULL is returned is no such circuit can be found. */
|
||||
origin_circuit_t *
|
||||
hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip)
|
||||
{
|
||||
origin_circuit_t *circ = NULL;
|
||||
|
||||
tor_assert(ip);
|
||||
|
||||
if (ip->base.is_only_legacy) {
|
||||
uint8_t digest[DIGEST_LEN];
|
||||
if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
|
||||
goto end;
|
||||
}
|
||||
circ = hs_circuitmap_get_intro_circ_v2_service_side(digest);
|
||||
} else {
|
||||
circ = hs_circuitmap_get_intro_circ_v3_service_side(
|
||||
&ip->auth_key_kp.pubkey);
|
||||
}
|
||||
end:
|
||||
return circ;
|
||||
}
|
||||
|
||||
/* Called when we fail building a rendezvous circuit at some point other than
|
||||
* the last hop: launches a new circuit to the same rendezvous point. This
|
||||
* supports legacy service.
|
||||
*
|
||||
* We currently relaunch connections to rendezvous points if:
|
||||
* - A rendezvous circuit timed out before connecting to RP.
|
||||
* - The redenzvous circuit failed to connect to the RP.
|
||||
*
|
||||
* We avoid relaunching a connection to this rendezvous point if:
|
||||
* - We have already tried MAX_REND_FAILURES times to connect to this RP.
|
||||
* - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT
|
||||
* seconds
|
||||
* - We've already retried this specific rendezvous circuit.
|
||||
*/
|
||||
void
|
||||
hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ)
|
||||
{
|
||||
tor_assert(circ);
|
||||
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
|
||||
|
||||
/* Check if we are allowed to relaunch to the rendezvous point of circ. */
|
||||
if (!can_relaunch_service_rendezvous_point(circ)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Flag the circuit that we are relaunching so to avoid to relaunch twice a
|
||||
* circuit to the same rendezvous point at the same time. */
|
||||
circ->hs_service_side_rend_circ_has_been_relaunched = 1;
|
||||
|
||||
/* Legacy service don't have an hidden service ident. */
|
||||
if (circ->hs_ident) {
|
||||
retry_service_rendezvous_point(circ);
|
||||
} else {
|
||||
rend_service_relaunch_rendezvous(circ);
|
||||
}
|
||||
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
/* For a given service and a service intro point, launch a circuit to the
|
||||
* extend info ei. If the service is a single onion, a one-hop circuit will be
|
||||
* requested. Return 0 if the circuit was successfully launched and tagged
|
||||
* with the correct identifier. On error, a negative value is returned. */
|
||||
int
|
||||
hs_circ_launch_intro_point(hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
extend_info_t *ei)
|
||||
{
|
||||
/* Standard flags for introduction circuit. */
|
||||
int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
|
||||
origin_circuit_t *circ;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
tor_assert(ei);
|
||||
|
||||
/* Update circuit flags in case of a single onion service that requires a
|
||||
* direct connection. */
|
||||
if (service->config.is_single_onion) {
|
||||
circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
|
||||
}
|
||||
|
||||
log_info(LD_REND, "Launching a circuit to intro point %s for service %s.",
|
||||
safe_str_client(extend_info_describe(ei)),
|
||||
safe_str_client(service->onion_address));
|
||||
|
||||
/* Note down the launch for the retry period. Even if the circuit fails to
|
||||
* be launched, we still want to respect the retry period to avoid stress on
|
||||
* the circuit subsystem. */
|
||||
service->state.num_intro_circ_launched++;
|
||||
circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO,
|
||||
ei, circ_flags);
|
||||
if (circ == NULL) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Setup the circuit identifier and attach it to it. */
|
||||
circ->hs_ident = create_intro_circuit_identifier(service, ip);
|
||||
tor_assert(circ->hs_ident);
|
||||
/* Register circuit in the global circuitmap. */
|
||||
register_intro_circ(ip, circ);
|
||||
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Called when a service introduction point circuit is done building. Given
|
||||
* the service and intro point object, this function will send the
|
||||
* ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the
|
||||
* circuit has been repurposed to General because we already have too many
|
||||
* opened. */
|
||||
int
|
||||
hs_circ_service_intro_has_opened(hs_service_t *service,
|
||||
hs_service_intro_point_t *ip,
|
||||
const hs_service_descriptor_t *desc,
|
||||
origin_circuit_t *circ)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned int num_intro_circ, num_needed_circ;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
tor_assert(desc);
|
||||
tor_assert(circ);
|
||||
|
||||
/* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already
|
||||
* established introduction circuits */
|
||||
num_intro_circ = count_opened_desc_intro_point_circuits(service, desc);
|
||||
num_needed_circ = service->config.num_intro_points;
|
||||
if (num_intro_circ > num_needed_circ) {
|
||||
/* There are too many opened valid intro circuit for what the service
|
||||
* needs so repurpose this one. */
|
||||
|
||||
/* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just
|
||||
* closes the circuit. I have NO idea why it does that so it hasn't been
|
||||
* added here. I can only assume in case our ExcludeNodes list changes but
|
||||
* in that case, all circuit are flagged unusable (config.c). --dgoulet */
|
||||
|
||||
log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we "
|
||||
"have enough for service %s. Repurposing "
|
||||
"it to general and leaving internal.",
|
||||
safe_str_client(service->onion_address));
|
||||
tor_assert(circ->build_state->is_internal);
|
||||
/* Remove it from the circuitmap. */
|
||||
hs_circuitmap_remove_circuit(TO_CIRCUIT(circ));
|
||||
/* Cleaning up the hidden service identifier and repurpose. */
|
||||
hs_ident_circuit_free(circ->hs_ident);
|
||||
circ->hs_ident = NULL;
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL);
|
||||
/* Inform that this circuit just opened for this new purpose. */
|
||||
circuit_has_opened(circ);
|
||||
/* This return value indicate to the caller that the IP object should be
|
||||
* removed from the service because it's corresponding circuit has just
|
||||
* been repurposed. */
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_info(LD_REND, "Introduction circuit %u established for service %s.",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
circuit_log_path(LOG_INFO, LD_REND, circ);
|
||||
|
||||
/* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call
|
||||
* makes sure the circuit gets closed. */
|
||||
send_establish_intro(service, ip, circ);
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Called when a service rendezvous point circuit is done building. Given the
|
||||
* service and the circuit, this function will send a RENDEZVOUS1 cell on the
|
||||
* circuit using the information in the circuit identifier. If the cell can't
|
||||
* be sent, the circuit is closed. */
|
||||
void
|
||||
hs_circ_service_rp_has_opened(const hs_service_t *service,
|
||||
origin_circuit_t *circ)
|
||||
{
|
||||
size_t payload_len;
|
||||
uint8_t payload[RELAY_PAYLOAD_SIZE] = {0};
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(circ);
|
||||
tor_assert(circ->hs_ident);
|
||||
|
||||
/* Some useful logging. */
|
||||
log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s "
|
||||
"for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
hex_str((const char *) circ->hs_ident->rendezvous_cookie,
|
||||
REND_COOKIE_LEN),
|
||||
safe_str_client(service->onion_address));
|
||||
circuit_log_path(LOG_INFO, LD_REND, circ);
|
||||
|
||||
/* This can't fail. */
|
||||
payload_len = hs_cell_build_rendezvous1(
|
||||
circ->hs_ident->rendezvous_cookie,
|
||||
sizeof(circ->hs_ident->rendezvous_cookie),
|
||||
circ->hs_ident->rendezvous_handshake_info,
|
||||
sizeof(circ->hs_ident->rendezvous_handshake_info),
|
||||
payload);
|
||||
|
||||
if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
|
||||
RELAY_COMMAND_RENDEZVOUS1,
|
||||
(const char *) payload, payload_len,
|
||||
circ->cpath->prev) < 0) {
|
||||
/* On error, circuit is closed. */
|
||||
log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u "
|
||||
"for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Setup end-to-end rendezvous circuit between the client and us. */
|
||||
if (hs_circuit_setup_e2e_rend_circ(circ,
|
||||
circ->hs_ident->rendezvous_ntor_key_seed,
|
||||
sizeof(circ->hs_ident->rendezvous_ntor_key_seed),
|
||||
1) < 0) {
|
||||
log_warn(LD_GENERAL, "Failed to setup circ");
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
memwipe(payload, 0, sizeof(payload));
|
||||
}
|
||||
|
||||
/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle
|
||||
* the INTRO_ESTABLISHED cell payload of length payload_len arriving on the
|
||||
* given introduction circuit circ. The service is only used for logging
|
||||
* purposes. Return 0 on success else a negative value. */
|
||||
int
|
||||
hs_circ_handle_intro_established(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
origin_circuit_t *circ,
|
||||
const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(ip);
|
||||
tor_assert(circ);
|
||||
tor_assert(payload);
|
||||
|
||||
if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Try to parse the payload into a cell making sure we do actually have a
|
||||
* valid cell. For a legacy node, it's an empty payload so as long as we
|
||||
* have the cell, we are good. */
|
||||
if (!ip->base.is_only_legacy &&
|
||||
hs_cell_parse_intro_established(payload, payload_len) < 0) {
|
||||
log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on "
|
||||
"circuit %u for service %s",
|
||||
TO_CIRCUIT(circ)->n_circ_id,
|
||||
safe_str_client(service->onion_address));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Switch the purpose to a fully working intro point. */
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO);
|
||||
/* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the
|
||||
* circuit so update our pathbias subsystem. */
|
||||
pathbias_mark_use_success(circ);
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* We just received an INTRODUCE2 cell on the established introduction circuit
|
||||
* circ. Handle the INTRODUCE2 payload of size payload_len for the given
|
||||
* circuit and service. This cell is associated with the intro point object ip
|
||||
* and the subcredential. Return 0 on success else a negative value. */
|
||||
int
|
||||
hs_circ_handle_introduce2(const hs_service_t *service,
|
||||
const origin_circuit_t *circ,
|
||||
hs_service_intro_point_t *ip,
|
||||
const uint8_t *subcredential,
|
||||
const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
int ret = -1;
|
||||
time_t elapsed;
|
||||
hs_cell_introduce2_data_t data;
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(circ);
|
||||
tor_assert(ip);
|
||||
tor_assert(subcredential);
|
||||
tor_assert(payload);
|
||||
|
||||
/* Populate the data structure with everything we need for the cell to be
|
||||
* parsed, decrypted and key material computed correctly. */
|
||||
data.auth_pk = &ip->auth_key_kp.pubkey;
|
||||
data.enc_kp = &ip->enc_key_kp;
|
||||
data.subcredential = subcredential;
|
||||
data.payload = payload;
|
||||
data.payload_len = payload_len;
|
||||
data.link_specifiers = smartlist_new();
|
||||
data.replay_cache = ip->replay_cache;
|
||||
|
||||
if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Check whether we've seen this REND_COOKIE before to detect repeats. */
|
||||
if (replaycache_add_test_and_elapsed(
|
||||
service->state.replay_cache_rend_cookie,
|
||||
data.rendezvous_cookie, sizeof(data.rendezvous_cookie),
|
||||
&elapsed)) {
|
||||
/* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE
|
||||
* as its previous one if its intro circ times out while in state
|
||||
* CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first
|
||||
* INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2
|
||||
* cell), we are already trying to connect to that rend point (and may
|
||||
* have already succeeded); drop this cell. */
|
||||
log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE "
|
||||
"field %ld seconds ago. Dropping cell.", elapsed);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* At this point, we just confirmed that the full INTRODUCE2 cell is valid
|
||||
* so increment our counter that we've seen one on this intro point. */
|
||||
ip->introduce2_count++;
|
||||
|
||||
/* Launch rendezvous circuit with the onion key and rend cookie. */
|
||||
launch_rendezvous_point_circuit(service, ip, &data);
|
||||
/* Success. */
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec,
|
||||
link_specifier_free(lspec));
|
||||
smartlist_free(data.link_specifiers);
|
||||
memwipe(&data, 0, sizeof(data));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key
|
||||
* exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
|
||||
* serve as a rendezvous end-to-end circuit between the client and the
|
||||
|
@ -10,6 +10,40 @@
|
||||
#define TOR_HS_CIRCUIT_H
|
||||
|
||||
#include "or.h"
|
||||
#include "crypto.h"
|
||||
#include "crypto_ed25519.h"
|
||||
|
||||
#include "hs_service.h"
|
||||
|
||||
/* Circuit API. */
|
||||
int hs_circ_service_intro_has_opened(hs_service_t *service,
|
||||
hs_service_intro_point_t *ip,
|
||||
const hs_service_descriptor_t *desc,
|
||||
origin_circuit_t *circ);
|
||||
void hs_circ_service_rp_has_opened(const hs_service_t *service,
|
||||
origin_circuit_t *circ);
|
||||
int hs_circ_launch_intro_point(hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
extend_info_t *ei);
|
||||
int hs_circ_launch_rendezvous_point(const hs_service_t *service,
|
||||
const curve25519_public_key_t *onion_key,
|
||||
const uint8_t *rendezvous_cookie);
|
||||
void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ);
|
||||
|
||||
origin_circuit_t *hs_circ_service_get_intro_circ(
|
||||
const hs_service_intro_point_t *ip);
|
||||
|
||||
/* Cell API. */
|
||||
int hs_circ_handle_intro_established(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
int hs_circ_handle_introduce2(const hs_service_t *service,
|
||||
const origin_circuit_t *circ,
|
||||
hs_service_intro_point_t *ip,
|
||||
const uint8_t *subcredential,
|
||||
const uint8_t *payload, size_t payload_len);
|
||||
|
||||
/* e2e circuit API. */
|
||||
|
||||
|
@ -15,10 +15,119 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
#include "hs_cache.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_service.h"
|
||||
#include "rendcommon.h"
|
||||
#include "rendservice.h"
|
||||
#include "router.h"
|
||||
#include "shared_random.h"
|
||||
#include "shared_random_state.h"
|
||||
|
||||
/* Ed25519 Basepoint value. Taken from section 5 of
|
||||
* https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */
|
||||
static const char *str_ed25519_basepoint =
|
||||
"(15112221349535400772501151409588531511"
|
||||
"454012693041857206046113283949847762202, "
|
||||
"463168356949264781694283940034751631413"
|
||||
"07993866256225615783033603165251855960)";
|
||||
|
||||
#ifdef HAVE_SYS_UN_H
|
||||
|
||||
/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
|
||||
* add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
|
||||
* else return -ENOSYS if AF_UNIX is not supported (see function in the
|
||||
* #else statement below). */
|
||||
static int
|
||||
add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
|
||||
{
|
||||
tor_assert(ports);
|
||||
tor_assert(p);
|
||||
tor_assert(p->is_unix_addr);
|
||||
|
||||
smartlist_add(ports, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
|
||||
* on success else return -ENOSYS if AF_UNIX is not supported (see function
|
||||
* in the #else statement below). */
|
||||
static int
|
||||
set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
|
||||
{
|
||||
tor_assert(conn);
|
||||
tor_assert(p);
|
||||
tor_assert(p->is_unix_addr);
|
||||
|
||||
conn->base_.socket_family = AF_UNIX;
|
||||
tor_addr_make_unspec(&conn->base_.addr);
|
||||
conn->base_.port = 1;
|
||||
conn->base_.address = tor_strdup(p->unix_addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* defined(HAVE_SYS_UN_H) */
|
||||
|
||||
static int
|
||||
set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
|
||||
{
|
||||
(void) conn;
|
||||
(void) p;
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int
|
||||
add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
|
||||
{
|
||||
(void) ports;
|
||||
(void) p;
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
#endif /* HAVE_SYS_UN_H */
|
||||
|
||||
/* Helper function: The key is a digest that we compare to a node_t object
|
||||
* current hsdir_index. */
|
||||
static int
|
||||
compare_digest_to_current_hsdir_index(const void *_key, const void **_member)
|
||||
{
|
||||
const char *key = _key;
|
||||
const node_t *node = *_member;
|
||||
return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN);
|
||||
}
|
||||
|
||||
/* Helper function: The key is a digest that we compare to a node_t object
|
||||
* next hsdir_index. */
|
||||
static int
|
||||
compare_digest_to_next_hsdir_index(const void *_key, const void **_member)
|
||||
{
|
||||
const char *key = _key;
|
||||
const node_t *node = *_member;
|
||||
return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN);
|
||||
}
|
||||
|
||||
/* Helper function: Compare two node_t objects current hsdir_index. */
|
||||
static int
|
||||
compare_node_current_hsdir_index(const void **a, const void **b)
|
||||
{
|
||||
const node_t *node1= *a;
|
||||
const node_t *node2 = *b;
|
||||
return tor_memcmp(node1->hsdir_index->current,
|
||||
node2->hsdir_index->current,
|
||||
DIGEST256_LEN);
|
||||
}
|
||||
|
||||
/* Helper function: Compare two node_t objects next hsdir_index. */
|
||||
static int
|
||||
compare_node_next_hsdir_index(const void **a, const void **b)
|
||||
{
|
||||
const node_t *node1= *a;
|
||||
const node_t *node2 = *b;
|
||||
return tor_memcmp(node1->hsdir_index->next,
|
||||
node2->hsdir_index->next,
|
||||
DIGEST256_LEN);
|
||||
}
|
||||
|
||||
/* Allocate and return a string containing the path to filename in directory.
|
||||
* This function will never return NULL. The caller must free this path. */
|
||||
@ -72,6 +181,17 @@ hs_check_service_private_dir(const char *username, const char *path,
|
||||
STATIC uint64_t
|
||||
get_time_period_length(void)
|
||||
{
|
||||
/* If we are on a test network, make the time period smaller than normal so
|
||||
that we actually see it rotate. Specifically, make it the same length as
|
||||
an SRV protocol run. */
|
||||
if (get_options()->TestingTorNetwork) {
|
||||
unsigned run_duration = sr_state_get_protocol_run_duration();
|
||||
/* An SRV run should take more than a minute (it's 24 rounds) */
|
||||
tor_assert_nonfatal(run_duration > 60);
|
||||
/* Turn it from seconds to minutes before returning: */
|
||||
return sr_state_get_protocol_run_duration() / 60;
|
||||
}
|
||||
|
||||
int32_t time_period_length = networkstatus_get_param(NULL, "hsdir-interval",
|
||||
HS_TIME_PERIOD_LENGTH_DEFAULT,
|
||||
HS_TIME_PERIOD_LENGTH_MIN,
|
||||
@ -83,17 +203,22 @@ get_time_period_length(void)
|
||||
}
|
||||
|
||||
/** Get the HS time period number at time <b>now</b> */
|
||||
STATIC uint64_t
|
||||
get_time_period_num(time_t now)
|
||||
uint64_t
|
||||
hs_get_time_period_num(time_t now)
|
||||
{
|
||||
uint64_t time_period_num;
|
||||
|
||||
/* Start by calculating minutes since the epoch */
|
||||
uint64_t time_period_length = get_time_period_length();
|
||||
uint64_t minutes_since_epoch = now / 60;
|
||||
|
||||
/* Now subtract half a day to fit the prop224 time period schedule (see
|
||||
* section [TIME-PERIODS]). */
|
||||
tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET);
|
||||
minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET;
|
||||
/* Apply the rotation offset as specified by prop224 (section
|
||||
* [TIME-PERIODS]), so that new time periods synchronize nicely with SRV
|
||||
* publication */
|
||||
unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
|
||||
time_period_rotation_offset /= 60; /* go from seconds to minutes */
|
||||
tor_assert(minutes_since_epoch > time_period_rotation_offset);
|
||||
minutes_since_epoch -= time_period_rotation_offset;
|
||||
|
||||
/* Calculate the time period */
|
||||
time_period_num = minutes_since_epoch / time_period_length;
|
||||
@ -105,7 +230,22 @@ get_time_period_num(time_t now)
|
||||
uint64_t
|
||||
hs_get_next_time_period_num(time_t now)
|
||||
{
|
||||
return get_time_period_num(now) + 1;
|
||||
return hs_get_time_period_num(now) + 1;
|
||||
}
|
||||
|
||||
/* Return the start time of the upcoming time period based on <b>now</b>. */
|
||||
time_t
|
||||
hs_get_start_time_of_next_time_period(time_t now)
|
||||
{
|
||||
uint64_t time_period_length = get_time_period_length();
|
||||
|
||||
/* Get start time of next time period */
|
||||
uint64_t next_time_period_num = hs_get_next_time_period_num(now);
|
||||
uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length;
|
||||
|
||||
/* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */
|
||||
unsigned int time_period_rotation_offset = sr_state_get_phase_duration();
|
||||
return start_of_next_tp_in_mins * 60 + time_period_rotation_offset;
|
||||
}
|
||||
|
||||
/* Create a new rend_data_t for a specific given <b>version</b>.
|
||||
@ -360,6 +500,148 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
|
||||
}
|
||||
}
|
||||
|
||||
/* Using the given time period number, compute the disaster shared random
|
||||
* value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */
|
||||
static void
|
||||
compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
|
||||
{
|
||||
crypto_digest_t *digest;
|
||||
|
||||
tor_assert(srv_out);
|
||||
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
|
||||
/* Start setting up payload:
|
||||
* H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */
|
||||
crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX,
|
||||
HS_SRV_DISASTER_PREFIX_LEN);
|
||||
|
||||
/* Setup INT_8(period_length) | INT_8(period_num) */
|
||||
{
|
||||
uint64_t time_period_length = get_time_period_length();
|
||||
char period_stuff[sizeof(uint64_t)*2];
|
||||
size_t offset = 0;
|
||||
set_uint64(period_stuff, tor_htonll(time_period_length));
|
||||
offset += sizeof(uint64_t);
|
||||
set_uint64(period_stuff+offset, tor_htonll(time_period_num));
|
||||
offset += sizeof(uint64_t);
|
||||
tor_assert(offset == sizeof(period_stuff));
|
||||
|
||||
crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
|
||||
}
|
||||
|
||||
crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
}
|
||||
|
||||
/** Due to the high cost of computing the disaster SRV and that potentially we
|
||||
* would have to do it thousands of times in a row, we always cache the
|
||||
* computer disaster SRV (and its corresponding time period num) in case we
|
||||
* want to reuse it soon after. We need to cache two SRVs, one for each active
|
||||
* time period (in case of overlap mode).
|
||||
*/
|
||||
static uint8_t cached_disaster_srv[2][DIGEST256_LEN];
|
||||
static uint64_t cached_time_period_nums[2] = {0};
|
||||
|
||||
/** Compute the disaster SRV value for this <b>time_period_num</b> and put it
|
||||
* in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches
|
||||
* to see if we have already computed it. */
|
||||
STATIC void
|
||||
get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out)
|
||||
{
|
||||
if (time_period_num == cached_time_period_nums[0]) {
|
||||
memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN);
|
||||
return;
|
||||
} else if (time_period_num == cached_time_period_nums[1]) {
|
||||
memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN);
|
||||
return;
|
||||
} else {
|
||||
int replace_idx;
|
||||
// Replace the lower period number.
|
||||
if (cached_time_period_nums[0] <= cached_time_period_nums[1]) {
|
||||
replace_idx = 0;
|
||||
} else {
|
||||
replace_idx = 1;
|
||||
}
|
||||
cached_time_period_nums[replace_idx] = time_period_num;
|
||||
compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]);
|
||||
memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
/** Get the first cached disaster SRV. Only used by unittests. */
|
||||
STATIC uint8_t *
|
||||
get_first_cached_disaster_srv(void)
|
||||
{
|
||||
return cached_disaster_srv[0];
|
||||
}
|
||||
|
||||
/** Get the second cached disaster SRV. Only used by unittests. */
|
||||
STATIC uint8_t *
|
||||
get_second_cached_disaster_srv(void)
|
||||
{
|
||||
return cached_disaster_srv[1];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* When creating a blinded key, we need a parameter which construction is as
|
||||
* follow: H(pubkey | [secret] | ed25519-basepoint | nonce).
|
||||
*
|
||||
* The nonce has a pre-defined format which uses the time period number
|
||||
* period_num and the start of the period in second start_time_period.
|
||||
*
|
||||
* The secret of size secret_len is optional meaning that it can be NULL and
|
||||
* thus will be ignored for the param construction.
|
||||
*
|
||||
* The result is put in param_out. */
|
||||
static void
|
||||
build_blinded_key_param(const ed25519_public_key_t *pubkey,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
uint64_t period_num, uint64_t period_length,
|
||||
uint8_t *param_out)
|
||||
{
|
||||
size_t offset = 0;
|
||||
const char blind_str[] = "Derive temporary signing key";
|
||||
uint8_t nonce[HS_KEYBLIND_NONCE_LEN];
|
||||
crypto_digest_t *digest;
|
||||
|
||||
tor_assert(pubkey);
|
||||
tor_assert(param_out);
|
||||
|
||||
/* Create the nonce N. The construction is as follow:
|
||||
* N = "key-blind" || INT_8(period_num) || INT_8(period_length) */
|
||||
memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN);
|
||||
offset += HS_KEYBLIND_NONCE_PREFIX_LEN;
|
||||
set_uint64(nonce + offset, tor_htonll(period_num));
|
||||
offset += sizeof(uint64_t);
|
||||
set_uint64(nonce + offset, tor_htonll(period_length));
|
||||
offset += sizeof(uint64_t);
|
||||
tor_assert(offset == HS_KEYBLIND_NONCE_LEN);
|
||||
|
||||
/* Generate the parameter h and the construction is as follow:
|
||||
* h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str));
|
||||
crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN);
|
||||
/* Optional secret. */
|
||||
if (secret) {
|
||||
crypto_digest_add_bytes(digest, (char *) secret, secret_len);
|
||||
}
|
||||
crypto_digest_add_bytes(digest, str_ed25519_basepoint,
|
||||
strlen(str_ed25519_basepoint));
|
||||
crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce));
|
||||
|
||||
/* Extract digest and put it in the param. */
|
||||
crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
|
||||
memwipe(nonce, 0, sizeof(nonce));
|
||||
}
|
||||
|
||||
/* Using an ed25519 public key and version to build the checksum of an
|
||||
* address. Put in checksum_out. Format is:
|
||||
* SHA3-256(".onion checksum" || PUBKEY || VERSION)
|
||||
@ -442,6 +724,98 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
|
||||
tor_assert(offset == HS_SERVICE_ADDR_LEN);
|
||||
}
|
||||
|
||||
/* Using the given identity public key and a blinded public key, compute the
|
||||
* subcredential and put it in subcred_out (must be of size DIGEST256_LEN).
|
||||
* This can't fail. */
|
||||
void
|
||||
hs_get_subcredential(const ed25519_public_key_t *identity_pk,
|
||||
const ed25519_public_key_t *blinded_pk,
|
||||
uint8_t *subcred_out)
|
||||
{
|
||||
uint8_t credential[DIGEST256_LEN];
|
||||
crypto_digest_t *digest;
|
||||
|
||||
tor_assert(identity_pk);
|
||||
tor_assert(blinded_pk);
|
||||
tor_assert(subcred_out);
|
||||
|
||||
/* First, build the credential. Construction is as follow:
|
||||
* credential = H("credential" | public-identity-key) */
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX,
|
||||
HS_CREDENTIAL_PREFIX_LEN);
|
||||
crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
|
||||
ED25519_PUBKEY_LEN);
|
||||
crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
|
||||
/* Now, compute the subcredential. Construction is as follow:
|
||||
* subcredential = H("subcredential" | credential | blinded-public-key). */
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX,
|
||||
HS_SUBCREDENTIAL_PREFIX_LEN);
|
||||
crypto_digest_add_bytes(digest, (const char *) credential,
|
||||
sizeof(credential));
|
||||
crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
|
||||
ED25519_PUBKEY_LEN);
|
||||
crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
|
||||
memwipe(credential, 0, sizeof(credential));
|
||||
}
|
||||
|
||||
/* From the given list of hidden service ports, find the ones that much the
|
||||
* given edge connection conn, pick one at random and use it to set the
|
||||
* connection address. Return 0 on success or -1 if none. */
|
||||
int
|
||||
hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
|
||||
{
|
||||
rend_service_port_config_t *chosen_port;
|
||||
unsigned int warn_once = 0;
|
||||
smartlist_t *matching_ports;
|
||||
|
||||
tor_assert(ports);
|
||||
tor_assert(conn);
|
||||
|
||||
matching_ports = smartlist_new();
|
||||
SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
|
||||
if (TO_CONN(conn)->port != p->virtual_port) {
|
||||
continue;
|
||||
}
|
||||
if (!(p->is_unix_addr)) {
|
||||
smartlist_add(matching_ports, p);
|
||||
} else {
|
||||
if (add_unix_port(matching_ports, p)) {
|
||||
if (!warn_once) {
|
||||
/* Unix port not supported so warn only once. */
|
||||
log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d "
|
||||
"which is unsupported on this platform. "
|
||||
"Ignoring it.",
|
||||
TO_CONN(conn)->port);
|
||||
}
|
||||
warn_once++;
|
||||
}
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(p);
|
||||
|
||||
chosen_port = smartlist_choose(matching_ports);
|
||||
smartlist_free(matching_ports);
|
||||
if (chosen_port) {
|
||||
if (!(chosen_port->is_unix_addr)) {
|
||||
/* Get a non-AF_UNIX connection ready for connection_exit_connect() */
|
||||
tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr);
|
||||
TO_CONN(conn)->port = chosen_port->real_port;
|
||||
} else {
|
||||
if (set_unix_port(conn, chosen_port)) {
|
||||
/* Simply impossible to end up here else we were able to add a Unix
|
||||
* port without AF_UNIX support... ? */
|
||||
tor_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (chosen_port) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Using a base32 representation of a service address, parse its content into
|
||||
* the key_out, checksum_out and version_out. Any out variable can be NULL in
|
||||
* case the caller would want only one field. checksum_out MUST at least be 2
|
||||
@ -541,6 +915,404 @@ hs_build_address(const ed25519_public_key_t *key, uint8_t version,
|
||||
tor_assert(hs_address_is_valid(addr_out));
|
||||
}
|
||||
|
||||
/* Return a newly allocated copy of lspec. */
|
||||
link_specifier_t *
|
||||
hs_link_specifier_dup(const link_specifier_t *lspec)
|
||||
{
|
||||
link_specifier_t *dup = link_specifier_new();
|
||||
memcpy(dup, lspec, sizeof(*dup));
|
||||
/* The unrecognized field is a dynamic array so make sure to copy its
|
||||
* content and not the pointer. */
|
||||
link_specifier_setlen_un_unrecognized(
|
||||
dup, link_specifier_getlen_un_unrecognized(lspec));
|
||||
if (link_specifier_getlen_un_unrecognized(dup)) {
|
||||
memcpy(link_specifier_getarray_un_unrecognized(dup),
|
||||
link_specifier_getconstarray_un_unrecognized(lspec),
|
||||
link_specifier_getlen_un_unrecognized(dup));
|
||||
}
|
||||
return dup;
|
||||
}
|
||||
|
||||
/* From a given ed25519 public key pk and an optional secret, compute a
|
||||
* blinded public key and put it in blinded_pk_out. This is only useful to
|
||||
* the client side because the client only has access to the identity public
|
||||
* key of the service. */
|
||||
void
|
||||
hs_build_blinded_pubkey(const ed25519_public_key_t *pk,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
uint64_t time_period_num,
|
||||
ed25519_public_key_t *blinded_pk_out)
|
||||
{
|
||||
/* Our blinding key API requires a 32 bytes parameter. */
|
||||
uint8_t param[DIGEST256_LEN];
|
||||
|
||||
tor_assert(pk);
|
||||
tor_assert(blinded_pk_out);
|
||||
tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN));
|
||||
|
||||
build_blinded_key_param(pk, secret, secret_len,
|
||||
time_period_num, get_time_period_length(), param);
|
||||
ed25519_public_blind(blinded_pk_out, pk, param);
|
||||
|
||||
memwipe(param, 0, sizeof(param));
|
||||
}
|
||||
|
||||
/* From a given ed25519 keypair kp and an optional secret, compute a blinded
|
||||
* keypair for the current time period and put it in blinded_kp_out. This is
|
||||
* only useful by the service side because the client doesn't have access to
|
||||
* the identity secret key. */
|
||||
void
|
||||
hs_build_blinded_keypair(const ed25519_keypair_t *kp,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
uint64_t time_period_num,
|
||||
ed25519_keypair_t *blinded_kp_out)
|
||||
{
|
||||
/* Our blinding key API requires a 32 bytes parameter. */
|
||||
uint8_t param[DIGEST256_LEN];
|
||||
|
||||
tor_assert(kp);
|
||||
tor_assert(blinded_kp_out);
|
||||
/* Extra safety. A zeroed key is bad. */
|
||||
tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN));
|
||||
tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN));
|
||||
|
||||
build_blinded_key_param(&kp->pubkey, secret, secret_len,
|
||||
time_period_num, get_time_period_length(), param);
|
||||
ed25519_keypair_blind(blinded_kp_out, kp, param);
|
||||
|
||||
memwipe(param, 0, sizeof(param));
|
||||
}
|
||||
|
||||
/* Return true if overlap mode is active given the date in consensus. If
|
||||
* consensus is NULL, then we use the latest live consensus we can find. */
|
||||
MOCK_IMPL(int,
|
||||
hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now))
|
||||
{
|
||||
time_t valid_after;
|
||||
time_t srv_start_time, tp_start_time;
|
||||
|
||||
if (!consensus) {
|
||||
consensus = networkstatus_get_live_consensus(now);
|
||||
if (!consensus) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* We consider to be in overlap mode when we are in the period of time
|
||||
* between a fresh SRV and the beginning of the new time period (in the
|
||||
* normal network this is between 00:00 (inclusive) and 12:00 UTC
|
||||
* (exclusive)) */
|
||||
valid_after = consensus->valid_after;
|
||||
srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after);
|
||||
tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time);
|
||||
|
||||
if (valid_after >= srv_start_time && valid_after < tp_start_time) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return 1 if any virtual port in ports needs a circuit with good uptime.
|
||||
* Else return 0. */
|
||||
int
|
||||
hs_service_requires_uptime_circ(const smartlist_t *ports)
|
||||
{
|
||||
tor_assert(ports);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) {
|
||||
if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
|
||||
p->virtual_port)) {
|
||||
return 1;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Build hs_index which is used to find the responsible hsdirs. This index
|
||||
* value is used to select the responsible HSDir where their hsdir_index is
|
||||
* closest to this value.
|
||||
* SHA3-256("store-at-idx" | blinded_public_key |
|
||||
* INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) )
|
||||
*
|
||||
* hs_index_out must be large enough to receive DIGEST256_LEN bytes. */
|
||||
void
|
||||
hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk,
|
||||
uint64_t period_num, uint8_t *hs_index_out)
|
||||
{
|
||||
crypto_digest_t *digest;
|
||||
|
||||
tor_assert(blinded_pk);
|
||||
tor_assert(hs_index_out);
|
||||
|
||||
/* Build hs_index. See construction at top of function comment. */
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN);
|
||||
crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
|
||||
ED25519_PUBKEY_LEN);
|
||||
|
||||
/* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */
|
||||
{
|
||||
uint64_t period_length = get_time_period_length();
|
||||
char buf[sizeof(uint64_t)*3];
|
||||
size_t offset = 0;
|
||||
set_uint64(buf, tor_htonll(replica));
|
||||
offset += sizeof(uint64_t);
|
||||
set_uint64(buf+offset, tor_htonll(period_length));
|
||||
offset += sizeof(uint64_t);
|
||||
set_uint64(buf+offset, tor_htonll(period_num));
|
||||
offset += sizeof(uint64_t);
|
||||
tor_assert(offset == sizeof(buf));
|
||||
|
||||
crypto_digest_add_bytes(digest, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
}
|
||||
|
||||
/* Build hsdir_index which is used to find the responsible hsdirs. This is the
|
||||
* index value that is compare to the hs_index when selecting an HSDir.
|
||||
* SHA3-256("node-idx" | node_identity |
|
||||
* shared_random_value | INT_8(period_length) | INT_8(period_num) )
|
||||
*
|
||||
* hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */
|
||||
void
|
||||
hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
|
||||
const uint8_t *srv_value, uint64_t period_num,
|
||||
uint8_t *hsdir_index_out)
|
||||
{
|
||||
crypto_digest_t *digest;
|
||||
|
||||
tor_assert(identity_pk);
|
||||
tor_assert(srv_value);
|
||||
tor_assert(hsdir_index_out);
|
||||
|
||||
/* Build hsdir_index. See construction at top of function comment. */
|
||||
digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN);
|
||||
crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey,
|
||||
ED25519_PUBKEY_LEN);
|
||||
crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN);
|
||||
|
||||
{
|
||||
uint64_t time_period_length = get_time_period_length();
|
||||
char period_stuff[sizeof(uint64_t)*2];
|
||||
size_t offset = 0;
|
||||
set_uint64(period_stuff, tor_htonll(period_num));
|
||||
offset += sizeof(uint64_t);
|
||||
set_uint64(period_stuff+offset, tor_htonll(time_period_length));
|
||||
offset += sizeof(uint64_t);
|
||||
tor_assert(offset == sizeof(period_stuff));
|
||||
|
||||
crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff));
|
||||
}
|
||||
|
||||
crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN);
|
||||
crypto_digest_free(digest);
|
||||
}
|
||||
|
||||
/* Return a newly allocated buffer containing the current shared random value
|
||||
* or if not present, a disaster value is computed using the given time period
|
||||
* number. If a consensus is provided in <b>ns</b>, use it to get the SRV
|
||||
* value. This function can't fail. */
|
||||
uint8_t *
|
||||
hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns)
|
||||
{
|
||||
uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
|
||||
const sr_srv_t *current_srv = sr_get_current(ns);
|
||||
|
||||
if (current_srv) {
|
||||
memcpy(sr_value, current_srv->value, sizeof(current_srv->value));
|
||||
} else {
|
||||
/* Disaster mode. */
|
||||
get_disaster_srv(time_period_num, sr_value);
|
||||
}
|
||||
return sr_value;
|
||||
}
|
||||
|
||||
/* Return a newly allocated buffer containing the previous shared random
|
||||
* value or if not present, a disaster value is computed using the given time
|
||||
* period number. This function can't fail. */
|
||||
uint8_t *
|
||||
hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns)
|
||||
{
|
||||
uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
|
||||
const sr_srv_t *previous_srv = sr_get_previous(ns);
|
||||
|
||||
if (previous_srv) {
|
||||
memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value));
|
||||
} else {
|
||||
/* Disaster mode. */
|
||||
get_disaster_srv(time_period_num, sr_value);
|
||||
}
|
||||
return sr_value;
|
||||
}
|
||||
|
||||
/* Return the number of replicas defined by a consensus parameter or the
|
||||
* default value. */
|
||||
int32_t
|
||||
hs_get_hsdir_n_replicas(void)
|
||||
{
|
||||
/* The [1,16] range is a specification requirement. */
|
||||
return networkstatus_get_param(NULL, "hsdir_n_replicas",
|
||||
HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16);
|
||||
}
|
||||
|
||||
/* Return the spread fetch value defined by a consensus parameter or the
|
||||
* default value. */
|
||||
int32_t
|
||||
hs_get_hsdir_spread_fetch(void)
|
||||
{
|
||||
/* The [1,128] range is a specification requirement. */
|
||||
return networkstatus_get_param(NULL, "hsdir_spread_fetch",
|
||||
HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128);
|
||||
}
|
||||
|
||||
/* Return the spread store value defined by a consensus parameter or the
|
||||
* default value. */
|
||||
int32_t
|
||||
hs_get_hsdir_spread_store(void)
|
||||
{
|
||||
/* The [1,128] range is a specification requirement. */
|
||||
return networkstatus_get_param(NULL, "hsdir_spread_store",
|
||||
HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128);
|
||||
}
|
||||
|
||||
/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index.
|
||||
* Return 0 if everything is as expected, else return -1. */
|
||||
static int
|
||||
node_has_hsdir_index(const node_t *node)
|
||||
{
|
||||
tor_assert(node_supports_v3_hsdir(node));
|
||||
|
||||
/* A node can't have an HSDir index without a descriptor since we need desc
|
||||
* to get its ed25519 key */
|
||||
if (!node_has_descriptor(node)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* At this point, since the node has a desc, this node must also have an
|
||||
* hsdir index. If not, something went wrong, so BUG out. */
|
||||
if (BUG(node->hsdir_index == NULL) ||
|
||||
BUG(tor_mem_is_zero((const char*)node->hsdir_index->current,
|
||||
DIGEST256_LEN))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* For a given blinded key and time period number, get the responsible HSDir
|
||||
* and put their routerstatus_t object in the responsible_dirs list. If
|
||||
* is_next_period is true, the next hsdir_index of the node_t is used. If
|
||||
* is_client is true, the spread fetch consensus parameter is used else the
|
||||
* spread store is used which is only for upload. This function can't fail but
|
||||
* it is possible that the responsible_dirs list contains fewer nodes than
|
||||
* expected.
|
||||
*
|
||||
* This function goes over the latest consensus routerstatus list and sorts it
|
||||
* by their node_t hsdir_index then does a binary search to find the closest
|
||||
* node. All of this makes it a bit CPU intensive so use it wisely. */
|
||||
void
|
||||
hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
|
||||
uint64_t time_period_num, int is_next_period,
|
||||
int is_client, smartlist_t *responsible_dirs)
|
||||
{
|
||||
smartlist_t *sorted_nodes;
|
||||
/* The compare function used for the smartlist bsearch. We have two
|
||||
* different depending on is_next_period. */
|
||||
int (*cmp_fct)(const void *, const void **);
|
||||
|
||||
tor_assert(blinded_pk);
|
||||
tor_assert(responsible_dirs);
|
||||
|
||||
sorted_nodes = smartlist_new();
|
||||
|
||||
/* Add every node_t that support HSDir v3 for which we do have a valid
|
||||
* hsdir_index already computed for them for this consensus. */
|
||||
{
|
||||
networkstatus_t *c = networkstatus_get_latest_consensus();
|
||||
if (!c || smartlist_len(c->routerstatus_list) == 0) {
|
||||
log_warn(LD_REND, "No valid consensus so we can't get the responsible "
|
||||
"hidden service directories.");
|
||||
goto done;
|
||||
}
|
||||
SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) {
|
||||
/* Even though this node_t object won't be modified and should be const,
|
||||
* we can't add const object in a smartlist_t. */
|
||||
node_t *n = node_get_mutable_by_id(rs->identity_digest);
|
||||
tor_assert(n);
|
||||
if (node_supports_v3_hsdir(n) && rs->is_hs_dir) {
|
||||
if (!node_has_hsdir_index(n)) {
|
||||
log_info(LD_GENERAL, "Node %s was found without hsdir index.",
|
||||
node_describe(n));
|
||||
continue;
|
||||
}
|
||||
smartlist_add(sorted_nodes, n);
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(rs);
|
||||
}
|
||||
if (smartlist_len(sorted_nodes) == 0) {
|
||||
log_warn(LD_REND, "No nodes found to be HSDir or supporting v3.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* First thing we have to do is sort all node_t by hsdir_index. The
|
||||
* is_next_period tells us if we want the current or the next one. Set the
|
||||
* bsearch compare function also while we are at it. */
|
||||
if (is_next_period) {
|
||||
smartlist_sort(sorted_nodes, compare_node_next_hsdir_index);
|
||||
cmp_fct = compare_digest_to_next_hsdir_index;
|
||||
} else {
|
||||
smartlist_sort(sorted_nodes, compare_node_current_hsdir_index);
|
||||
cmp_fct = compare_digest_to_current_hsdir_index;
|
||||
}
|
||||
|
||||
/* For all replicas, we'll select a set of HSDirs using the consensus
|
||||
* parameters and the sorted list. The replica starting at value 1 is
|
||||
* defined by the specification. */
|
||||
for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) {
|
||||
int idx, start, found, n_added = 0;
|
||||
uint8_t hs_index[DIGEST256_LEN] = {0};
|
||||
/* Number of node to add to the responsible dirs list depends on if we are
|
||||
* trying to fetch or store. A client always fetches. */
|
||||
int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() :
|
||||
hs_get_hsdir_spread_store();
|
||||
|
||||
/* Get the index that we should use to select the node. */
|
||||
hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index);
|
||||
/* The compare function pointer has been set correctly earlier. */
|
||||
start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct,
|
||||
&found);
|
||||
/* Getting the length of the list if no member is greater than the key we
|
||||
* are looking for so start at the first element. */
|
||||
if (idx == smartlist_len(sorted_nodes)) {
|
||||
start = idx = 0;
|
||||
}
|
||||
while (n_added < n_to_add) {
|
||||
const node_t *node = smartlist_get(sorted_nodes, idx);
|
||||
/* If the node has already been selected which is possible between
|
||||
* replicas, the specification says to skip over. */
|
||||
if (!smartlist_contains(responsible_dirs, node->rs)) {
|
||||
smartlist_add(responsible_dirs, node->rs);
|
||||
++n_added;
|
||||
}
|
||||
if (++idx == smartlist_len(sorted_nodes)) {
|
||||
/* Wrap if we've reached the end of the list. */
|
||||
idx = 0;
|
||||
}
|
||||
if (idx == start) {
|
||||
/* We've gone over the whole list, stop and avoid infinite loop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
smartlist_free(sorted_nodes);
|
||||
}
|
||||
|
||||
/* Initialize the entire HS subsytem. This is called in tor_init() before any
|
||||
* torrc options are loaded. Only for >= v3. */
|
||||
void
|
||||
@ -561,3 +1333,37 @@ hs_free_all(void)
|
||||
hs_cache_free_all();
|
||||
}
|
||||
|
||||
/* For the given origin circuit circ, decrement the number of rendezvous
|
||||
* stream counter. This handles every hidden service version. */
|
||||
void
|
||||
hs_dec_rdv_stream_counter(origin_circuit_t *circ)
|
||||
{
|
||||
tor_assert(circ);
|
||||
|
||||
if (circ->rend_data) {
|
||||
circ->rend_data->nr_streams--;
|
||||
} else if (circ->hs_ident) {
|
||||
circ->hs_ident->num_rdv_streams--;
|
||||
} else {
|
||||
/* Should not be called if this circuit is not for hidden service. */
|
||||
tor_assert_nonfatal_unreached();
|
||||
}
|
||||
}
|
||||
|
||||
/* For the given origin circuit circ, increment the number of rendezvous
|
||||
* stream counter. This handles every hidden service version. */
|
||||
void
|
||||
hs_inc_rdv_stream_counter(origin_circuit_t *circ)
|
||||
{
|
||||
tor_assert(circ);
|
||||
|
||||
if (circ->rend_data) {
|
||||
circ->rend_data->nr_streams++;
|
||||
} else if (circ->hs_ident) {
|
||||
circ->hs_ident->num_rdv_streams++;
|
||||
} else {
|
||||
/* Should not be called if this circuit is not for hidden service. */
|
||||
tor_assert_nonfatal_unreached();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
#include "or.h"
|
||||
|
||||
/* Trunnel */
|
||||
#include "ed25519_cert.h"
|
||||
|
||||
/* Protocol version 2. Use this instead of hardcoding "2" in the code base,
|
||||
* this adds a clearer semantic to the value when used. */
|
||||
#define HS_VERSION_TWO 2
|
||||
@ -49,8 +52,6 @@
|
||||
#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
|
||||
/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
|
||||
/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
|
||||
|
||||
/* Prefix of the onion address checksum. */
|
||||
#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
|
||||
@ -76,12 +77,77 @@
|
||||
#define HS_SERVICE_ADDR_LEN_BASE32 \
|
||||
(CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5))
|
||||
|
||||
/* The default HS time period length */
|
||||
#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */
|
||||
/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */
|
||||
/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */
|
||||
/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
|
||||
|
||||
/* Keyblinding parameter construction is as follow:
|
||||
* "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */
|
||||
#define HS_KEYBLIND_NONCE_PREFIX "key-blind"
|
||||
#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1)
|
||||
#define HS_KEYBLIND_NONCE_LEN \
|
||||
(HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t))
|
||||
|
||||
/* Credential and subcredential prefix value. */
|
||||
#define HS_CREDENTIAL_PREFIX "credential"
|
||||
#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1)
|
||||
#define HS_SUBCREDENTIAL_PREFIX "subcredential"
|
||||
#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1)
|
||||
|
||||
/* Node hidden service stored at index prefix value. */
|
||||
#define HS_INDEX_PREFIX "store-at-idx"
|
||||
#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1)
|
||||
|
||||
/* Node hidden service directory index prefix value. */
|
||||
#define HSDIR_INDEX_PREFIX "node-idx"
|
||||
#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1)
|
||||
|
||||
/* Prefix of the shared random value disaster mode. */
|
||||
#define HS_SRV_DISASTER_PREFIX "shared-random-disaster"
|
||||
#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1)
|
||||
|
||||
/* Default value of number of hsdir replicas (hsdir_n_replicas). */
|
||||
#define HS_DEFAULT_HSDIR_N_REPLICAS 2
|
||||
/* Default value of hsdir spread store (hsdir_spread_store). */
|
||||
#define HS_DEFAULT_HSDIR_SPREAD_STORE 3
|
||||
/* Default value of hsdir spread fetch (hsdir_spread_fetch). */
|
||||
#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3
|
||||
|
||||
/* Type of authentication key used by an introduction point. */
|
||||
typedef enum {
|
||||
HS_AUTH_KEY_TYPE_LEGACY = 1,
|
||||
HS_AUTH_KEY_TYPE_ED25519 = 2,
|
||||
} hs_auth_key_type_t;
|
||||
|
||||
/* Represents the mapping from a virtual port of a rendezvous service to a
|
||||
* real port on some IP. */
|
||||
typedef struct rend_service_port_config_t {
|
||||
/* The incoming HS virtual port we're mapping */
|
||||
uint16_t virtual_port;
|
||||
/* Is this an AF_UNIX port? */
|
||||
unsigned int is_unix_addr:1;
|
||||
/* The outgoing TCP port to use, if !is_unix_addr */
|
||||
uint16_t real_port;
|
||||
/* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
|
||||
tor_addr_t real_addr;
|
||||
/* The socket path to connect to, if is_unix_addr */
|
||||
char unix_addr[FLEXIBLE_ARRAY_MEMBER];
|
||||
} rend_service_port_config_t;
|
||||
|
||||
/* Hidden service directory index used in a node_t which is set once we set
|
||||
* the consensus. */
|
||||
typedef struct hsdir_index_t {
|
||||
/* The hsdir index for the current time period. */
|
||||
uint8_t current[DIGEST256_LEN];
|
||||
/* The hsdir index for the next time period. */
|
||||
uint8_t next[DIGEST256_LEN];
|
||||
} hsdir_index_t;
|
||||
|
||||
void hs_init(void);
|
||||
void hs_free_all(void);
|
||||
|
||||
@ -95,6 +161,16 @@ int hs_address_is_valid(const char *address);
|
||||
int hs_parse_address(const char *address, ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out);
|
||||
|
||||
void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
uint64_t time_period_num,
|
||||
ed25519_public_key_t *pubkey_out);
|
||||
void hs_build_blinded_keypair(const ed25519_keypair_t *kp,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
uint64_t time_period_num,
|
||||
ed25519_keypair_t *kp_out);
|
||||
int hs_service_requires_uptime_circ(const smartlist_t *ports);
|
||||
|
||||
void rend_data_free(rend_data_t *data);
|
||||
rend_data_t *rend_data_dup(const rend_data_t *data);
|
||||
rend_data_t *rend_data_client_create(const char *onion_address,
|
||||
@ -111,14 +187,54 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data,
|
||||
const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
|
||||
size_t *len_out);
|
||||
|
||||
void hs_get_subcredential(const ed25519_public_key_t *identity_pk,
|
||||
const ed25519_public_key_t *blinded_pk,
|
||||
uint8_t *subcred_out);
|
||||
|
||||
uint64_t hs_get_time_period_num(time_t now);
|
||||
uint64_t hs_get_next_time_period_num(time_t now);
|
||||
time_t hs_get_start_time_of_next_time_period(time_t now);
|
||||
|
||||
link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec);
|
||||
|
||||
MOCK_DECL(int, hs_overlap_mode_is_active,
|
||||
(const networkstatus_t *consensus, time_t now));
|
||||
|
||||
uint8_t *hs_get_current_srv(uint64_t time_period_num,
|
||||
const networkstatus_t *ns);
|
||||
uint8_t *hs_get_previous_srv(uint64_t time_period_num,
|
||||
const networkstatus_t *ns);
|
||||
|
||||
void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk,
|
||||
const uint8_t *srv, uint64_t period_num,
|
||||
uint8_t *hsdir_index_out);
|
||||
void hs_build_hs_index(uint64_t replica,
|
||||
const ed25519_public_key_t *blinded_pk,
|
||||
uint64_t period_num, uint8_t *hs_index_out);
|
||||
|
||||
int32_t hs_get_hsdir_n_replicas(void);
|
||||
int32_t hs_get_hsdir_spread_fetch(void);
|
||||
int32_t hs_get_hsdir_spread_store(void);
|
||||
|
||||
void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk,
|
||||
uint64_t time_period_num, int is_next_period,
|
||||
int is_client, smartlist_t *responsible_dirs);
|
||||
|
||||
int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn);
|
||||
|
||||
void hs_inc_rdv_stream_counter(origin_circuit_t *circ);
|
||||
void hs_dec_rdv_stream_counter(origin_circuit_t *circ);
|
||||
|
||||
#ifdef HS_COMMON_PRIVATE
|
||||
|
||||
STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out);
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
STATIC uint64_t get_time_period_length(void);
|
||||
STATIC uint64_t get_time_period_num(time_t now);
|
||||
|
||||
STATIC uint8_t *get_first_cached_disaster_srv(void);
|
||||
STATIC uint8_t *get_second_cached_disaster_srv(void);
|
||||
|
||||
#endif /* TOR_UNIT_TESTS */
|
||||
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "hs_descriptor.h"
|
||||
|
||||
#include "or.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "ed25519_cert.h" /* Trunnel interface. */
|
||||
#include "parsecommon.h"
|
||||
#include "rendcache.h"
|
||||
@ -78,6 +79,7 @@
|
||||
#define str_intro_auth_required "intro-auth-required"
|
||||
#define str_single_onion "single-onion-service"
|
||||
#define str_intro_point "introduction-point"
|
||||
#define str_ip_onion_key "onion-key"
|
||||
#define str_ip_auth_key "auth-key"
|
||||
#define str_ip_enc_key "enc-key"
|
||||
#define str_ip_enc_key_cert "enc-key-cert"
|
||||
@ -136,6 +138,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = {
|
||||
/* Descriptor ruleset for the introduction points section. */
|
||||
static token_rule_t hs_desc_intro_point_v3_token_table[] = {
|
||||
T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ),
|
||||
T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK),
|
||||
T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ),
|
||||
T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK),
|
||||
T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK),
|
||||
@ -144,29 +147,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = {
|
||||
END_OF_TABLE
|
||||
};
|
||||
|
||||
/* Free a descriptor intro point object. */
|
||||
STATIC void
|
||||
desc_intro_point_free(hs_desc_intro_point_t *ip)
|
||||
{
|
||||
if (!ip) {
|
||||
return;
|
||||
}
|
||||
if (ip->link_specifiers) {
|
||||
SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
|
||||
ls, tor_free(ls));
|
||||
smartlist_free(ip->link_specifiers);
|
||||
}
|
||||
tor_cert_free(ip->auth_key_cert);
|
||||
tor_cert_free(ip->enc_key_cert);
|
||||
if (ip->legacy.key) {
|
||||
crypto_pk_free(ip->legacy.key);
|
||||
}
|
||||
if (ip->legacy.cert.encoded) {
|
||||
tor_free(ip->legacy.cert.encoded);
|
||||
}
|
||||
tor_free(ip);
|
||||
}
|
||||
|
||||
/* Free the content of the plaintext section of a descriptor. */
|
||||
static void
|
||||
desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc)
|
||||
@ -197,7 +177,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
|
||||
}
|
||||
if (desc->intro_points) {
|
||||
SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
|
||||
desc_intro_point_free(ip));
|
||||
hs_desc_intro_point_free(ip));
|
||||
smartlist_free(desc->intro_points);
|
||||
}
|
||||
memwipe(desc, 0, sizeof(*desc));
|
||||
@ -256,7 +236,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
|
||||
memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential));
|
||||
offset += sizeof(desc->subcredential);
|
||||
/* Copy revision counter value. */
|
||||
set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter));
|
||||
set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter));
|
||||
offset += sizeof(uint64_t);
|
||||
tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
|
||||
}
|
||||
@ -383,6 +363,14 @@ encode_link_specifiers(const smartlist_t *specs)
|
||||
link_specifier_set_ls_len(ls, legacy_id_len);
|
||||
break;
|
||||
}
|
||||
case LS_ED25519_ID:
|
||||
{
|
||||
size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls);
|
||||
uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls);
|
||||
memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len);
|
||||
link_specifier_set_ls_len(ls, ed25519_id_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
tor_assert(0);
|
||||
}
|
||||
@ -479,6 +467,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip)
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/* Encode an introduction point onion key. Return a newly allocated string
|
||||
* with it. On failure, return NULL. */
|
||||
static char *
|
||||
encode_onion_key(const hs_desc_intro_point_t *ip)
|
||||
{
|
||||
char *encoded = NULL;
|
||||
char key_b64[CURVE25519_BASE64_PADDED_LEN + 1];
|
||||
|
||||
tor_assert(ip);
|
||||
|
||||
/* Base64 encode the encryption key for the "onion-key" field. */
|
||||
if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) {
|
||||
goto done;
|
||||
}
|
||||
tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64);
|
||||
|
||||
done:
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/* Encode an introduction point object and return a newly allocated string
|
||||
* with it. On failure, return NULL. */
|
||||
static char *
|
||||
@ -498,6 +506,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key,
|
||||
tor_free(ls_str);
|
||||
}
|
||||
|
||||
/* Onion key encoding. */
|
||||
{
|
||||
char *encoded_onion_key = encode_onion_key(ip);
|
||||
if (encoded_onion_key == NULL) {
|
||||
goto err;
|
||||
}
|
||||
smartlist_add_asprintf(lines, "%s", encoded_onion_key);
|
||||
tor_free(encoded_onion_key);
|
||||
}
|
||||
|
||||
/* Authentication key encoding. */
|
||||
{
|
||||
char *encoded_cert;
|
||||
@ -988,6 +1006,10 @@ desc_encode_v3(const hs_descriptor_t *desc,
|
||||
tor_assert(encoded_out);
|
||||
tor_assert(desc->plaintext_data.version == 3);
|
||||
|
||||
if (BUG(desc->subcredential == NULL)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Build the non-encrypted values. */
|
||||
{
|
||||
char *encoded_cert;
|
||||
@ -1134,6 +1156,15 @@ decode_link_specifiers(const char *encoded)
|
||||
memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls),
|
||||
sizeof(hs_spec->u.legacy_id));
|
||||
break;
|
||||
case LS_ED25519_ID:
|
||||
/* Both are known at compile time so let's make sure they are the same
|
||||
* else we can copy memory out of bound. */
|
||||
tor_assert(link_specifier_getlen_un_ed25519_id(ls) ==
|
||||
sizeof(hs_spec->u.ed25519_id));
|
||||
memcpy(hs_spec->u.ed25519_id,
|
||||
link_specifier_getconstarray_un_ed25519_id(ls),
|
||||
sizeof(hs_spec->u.ed25519_id));
|
||||
break;
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
@ -1626,6 +1657,50 @@ decode_intro_legacy_key(const directory_token_t *tok,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Dig into the descriptor <b>tokens</b> to find the onion key we should use
|
||||
* for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it
|
||||
* was found and well-formed, otherwise return -1 in case of errors. */
|
||||
static int
|
||||
set_intro_point_onion_key(curve25519_public_key_t *onion_key_out,
|
||||
const smartlist_t *tokens)
|
||||
{
|
||||
int retval = -1;
|
||||
smartlist_t *onion_keys = NULL;
|
||||
|
||||
tor_assert(onion_key_out);
|
||||
|
||||
onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY);
|
||||
if (!onion_keys) {
|
||||
log_warn(LD_REND, "Descriptor did not contain intro onion keys");
|
||||
goto err;
|
||||
}
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) {
|
||||
/* This field is using GE(2) so for possible forward compatibility, we
|
||||
* accept more fields but must be at least 2. */
|
||||
tor_assert(tok->n_args >= 2);
|
||||
|
||||
/* Try to find an ntor key, it's the only recognized type right now */
|
||||
if (!strcmp(tok->args[0], "ntor")) {
|
||||
if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) {
|
||||
log_warn(LD_REND, "Introduction point ntor onion-key is invalid");
|
||||
goto err;
|
||||
}
|
||||
/* Got the onion key! Set the appropriate retval */
|
||||
retval = 0;
|
||||
}
|
||||
} SMARTLIST_FOREACH_END(tok);
|
||||
|
||||
/* Log an error if we didn't find it :( */
|
||||
if (retval < 0) {
|
||||
log_warn(LD_REND, "Descriptor did not contain ntor onion keys");
|
||||
}
|
||||
|
||||
err:
|
||||
smartlist_free(onion_keys);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Given the start of a section and the end of it, decode a single
|
||||
* introduction point from that section. Return a newly allocated introduction
|
||||
* point object containing the decoded data. Return NULL if the section can't
|
||||
@ -1651,17 +1726,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
|
||||
|
||||
/* Ok we seem to have a well formed section containing enough tokens to
|
||||
* parse. Allocate our IP object and try to populate it. */
|
||||
ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t));
|
||||
ip = hs_desc_intro_point_new();
|
||||
|
||||
/* "introduction-point" SP link-specifiers NL */
|
||||
tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT);
|
||||
tor_assert(tok->n_args == 1);
|
||||
/* Our constructor creates this list by default so free it. */
|
||||
smartlist_free(ip->link_specifiers);
|
||||
ip->link_specifiers = decode_link_specifiers(tok->args[0]);
|
||||
if (!ip->link_specifiers) {
|
||||
log_warn(LD_REND, "Introduction point has invalid link specifiers");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* "onion-key" SP ntor SP key NL */
|
||||
if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* "auth-key" NL certificate NL */
|
||||
tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
|
||||
tor_assert(tok->object_body);
|
||||
@ -1733,7 +1815,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
|
||||
goto done;
|
||||
|
||||
err:
|
||||
desc_intro_point_free(ip);
|
||||
hs_desc_intro_point_free(ip);
|
||||
ip = NULL;
|
||||
|
||||
done:
|
||||
@ -2215,7 +2297,7 @@ hs_desc_decode_descriptor(const char *encoded,
|
||||
const uint8_t *subcredential,
|
||||
hs_descriptor_t **desc_out)
|
||||
{
|
||||
int ret;
|
||||
int ret = -1;
|
||||
hs_descriptor_t *desc;
|
||||
|
||||
tor_assert(encoded);
|
||||
@ -2223,10 +2305,13 @@ hs_desc_decode_descriptor(const char *encoded,
|
||||
desc = tor_malloc_zero(sizeof(hs_descriptor_t));
|
||||
|
||||
/* Subcredentials are optional. */
|
||||
if (subcredential) {
|
||||
memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
|
||||
if (BUG(!subcredential)) {
|
||||
log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!");
|
||||
goto err;
|
||||
}
|
||||
|
||||
memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
|
||||
|
||||
ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
|
||||
if (ret < 0) {
|
||||
goto err;
|
||||
@ -2352,3 +2437,110 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
|
||||
data->superencrypted_blob_size);
|
||||
}
|
||||
|
||||
/* Return a newly allocated descriptor intro point. */
|
||||
hs_desc_intro_point_t *
|
||||
hs_desc_intro_point_new(void)
|
||||
{
|
||||
hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
|
||||
ip->link_specifiers = smartlist_new();
|
||||
return ip;
|
||||
}
|
||||
|
||||
/* Free a descriptor intro point object. */
|
||||
void
|
||||
hs_desc_intro_point_free(hs_desc_intro_point_t *ip)
|
||||
{
|
||||
if (ip == NULL) {
|
||||
return;
|
||||
}
|
||||
if (ip->link_specifiers) {
|
||||
SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *,
|
||||
ls, hs_desc_link_specifier_free(ls));
|
||||
smartlist_free(ip->link_specifiers);
|
||||
}
|
||||
tor_cert_free(ip->auth_key_cert);
|
||||
tor_cert_free(ip->enc_key_cert);
|
||||
crypto_pk_free(ip->legacy.key);
|
||||
tor_free(ip->legacy.cert.encoded);
|
||||
tor_free(ip);
|
||||
}
|
||||
|
||||
/* Free the given descriptor link specifier. */
|
||||
void
|
||||
hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls)
|
||||
{
|
||||
if (ls == NULL) {
|
||||
return;
|
||||
}
|
||||
tor_free(ls);
|
||||
}
|
||||
|
||||
/* Return a newly allocated descriptor link specifier using the given extend
|
||||
* info and requested type. Return NULL on error. */
|
||||
hs_desc_link_specifier_t *
|
||||
hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type)
|
||||
{
|
||||
hs_desc_link_specifier_t *ls = NULL;
|
||||
|
||||
tor_assert(info);
|
||||
|
||||
ls = tor_malloc_zero(sizeof(*ls));
|
||||
ls->type = type;
|
||||
switch (ls->type) {
|
||||
case LS_IPV4:
|
||||
if (info->addr.family != AF_INET) {
|
||||
goto err;
|
||||
}
|
||||
tor_addr_copy(&ls->u.ap.addr, &info->addr);
|
||||
ls->u.ap.port = info->port;
|
||||
break;
|
||||
case LS_IPV6:
|
||||
if (info->addr.family != AF_INET6) {
|
||||
goto err;
|
||||
}
|
||||
tor_addr_copy(&ls->u.ap.addr, &info->addr);
|
||||
ls->u.ap.port = info->port;
|
||||
break;
|
||||
case LS_LEGACY_ID:
|
||||
/* Bug out if the identity digest is not set */
|
||||
if (BUG(tor_mem_is_zero(info->identity_digest,
|
||||
sizeof(info->identity_digest)))) {
|
||||
goto err;
|
||||
}
|
||||
memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id));
|
||||
break;
|
||||
case LS_ED25519_ID:
|
||||
/* ed25519 keys are optional for intro points */
|
||||
if (ed25519_public_key_is_zero(&info->ed_identity)) {
|
||||
goto err;
|
||||
}
|
||||
memcpy(ls->u.ed25519_id, info->ed_identity.pubkey,
|
||||
sizeof(ls->u.ed25519_id));
|
||||
break;
|
||||
default:
|
||||
/* Unknown type is code flow error. */
|
||||
tor_assert(0);
|
||||
}
|
||||
|
||||
return ls;
|
||||
err:
|
||||
tor_free(ls);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* From the given descriptor, remove and free every introduction point. */
|
||||
void
|
||||
hs_descriptor_clear_intro_points(hs_descriptor_t *desc)
|
||||
{
|
||||
smartlist_t *ips;
|
||||
|
||||
tor_assert(desc);
|
||||
|
||||
ips = desc->encrypted_data.intro_points;
|
||||
if (ips) {
|
||||
SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *,
|
||||
ip, hs_desc_intro_point_free(ip));
|
||||
smartlist_clear(ips);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,15 @@
|
||||
/* The latest descriptor format version we support. */
|
||||
#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3
|
||||
|
||||
/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours
|
||||
* which is 180 minutes or 10800 seconds. */
|
||||
#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60)
|
||||
/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours
|
||||
* which is 720 minutes or 43200 seconds. */
|
||||
#define HS_DESC_MAX_LIFETIME (12 * 60 * 60)
|
||||
/* Lifetime of certificate in the descriptor. This defines the lifetime of the
|
||||
* descriptor signing key and the cross certification cert of that key. */
|
||||
#define HS_DESC_CERT_LIFETIME (24 * 60 * 60)
|
||||
#define HS_DESC_CERT_LIFETIME (36 * 60 * 60)
|
||||
/* Length of the salt needed for the encrypted section of a descriptor. */
|
||||
#define HS_DESC_ENCRYPTED_SALT_LEN 16
|
||||
/* Length of the secret input needed for the KDF construction which derives
|
||||
@ -65,12 +68,14 @@ typedef struct hs_desc_link_specifier_t {
|
||||
* specification. */
|
||||
uint8_t type;
|
||||
|
||||
/* It's either an address/port or a legacy identity fingerprint. */
|
||||
/* It must be one of these types, can't be more than one. */
|
||||
union {
|
||||
/* IP address and port of the relay use to extend. */
|
||||
tor_addr_port_t ap;
|
||||
/* Legacy identity. A 20-byte SHA1 identity fingerprint. */
|
||||
uint8_t legacy_id[DIGEST_LEN];
|
||||
/* ed25519 identity. A 32-byte key. */
|
||||
uint8_t ed25519_id[ED25519_PUBKEY_LEN];
|
||||
} u;
|
||||
} hs_desc_link_specifier_t;
|
||||
|
||||
@ -80,6 +85,10 @@ typedef struct hs_desc_intro_point_t {
|
||||
* contains hs_desc_link_specifier_t object. It MUST have at least one. */
|
||||
smartlist_t *link_specifiers;
|
||||
|
||||
/* Onion key of the introduction point used to extend to it for the ntor
|
||||
* handshake. */
|
||||
curve25519_public_key_t onion_key;
|
||||
|
||||
/* Authentication key used to establish the introduction point circuit and
|
||||
* cross-certifies the blinded public key for the replica thus signed by
|
||||
* the blinded key and in turn signs it. */
|
||||
@ -197,6 +206,11 @@ void hs_descriptor_free(hs_descriptor_t *desc);
|
||||
void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc);
|
||||
void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc);
|
||||
|
||||
void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls);
|
||||
hs_desc_link_specifier_t *hs_desc_link_specifier_new(
|
||||
const extend_info_t *info, uint8_t type);
|
||||
void hs_descriptor_clear_intro_points(hs_descriptor_t *desc);
|
||||
|
||||
int hs_desc_encode_descriptor(const hs_descriptor_t *desc,
|
||||
const ed25519_keypair_t *signing_kp,
|
||||
char **encoded_out);
|
||||
@ -211,6 +225,9 @@ int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
|
||||
|
||||
size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
|
||||
|
||||
hs_desc_intro_point_t *hs_desc_intro_point_new(void);
|
||||
void hs_desc_intro_point_free(hs_desc_intro_point_t *ip);
|
||||
|
||||
#ifdef HS_DESCRIPTOR_PRIVATE
|
||||
|
||||
/* Encoding. */
|
||||
@ -229,7 +246,6 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type,
|
||||
STATIC int desc_sig_is_valid(const char *b64_sig,
|
||||
const ed25519_public_key_t *signing_pubkey,
|
||||
const char *encoded_desc, size_t encoded_len);
|
||||
STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip);
|
||||
STATIC size_t decode_superencrypted(const char *message, size_t message_len,
|
||||
uint8_t **encrypted_out);
|
||||
#endif /* HS_DESCRIPTOR_PRIVATE */
|
||||
|
@ -30,13 +30,20 @@ hs_ident_circuit_free(hs_ident_circuit_t *ident)
|
||||
if (ident == NULL) {
|
||||
return;
|
||||
}
|
||||
if (ident->auth_key_type == HS_AUTH_KEY_TYPE_LEGACY) {
|
||||
crypto_pk_free(ident->auth_rsa_pk);
|
||||
}
|
||||
memwipe(ident, 0, sizeof(hs_ident_circuit_t));
|
||||
tor_free(ident);
|
||||
}
|
||||
|
||||
/* For a given circuit identifier src, return a newly allocated copy of it.
|
||||
* This can't fail. */
|
||||
hs_ident_circuit_t *
|
||||
hs_ident_circuit_dup(const hs_ident_circuit_t *src)
|
||||
{
|
||||
hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
|
||||
memcpy(ident, src, sizeof(*ident));
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* For a given directory connection identifier src, return a newly allocated
|
||||
* copy of it. This can't fail. */
|
||||
hs_ident_dir_conn_t *
|
||||
|
@ -52,27 +52,32 @@ typedef struct hs_ident_circuit_t {
|
||||
* set when an object is initialized in its constructor. */
|
||||
hs_ident_circuit_type_t circuit_type;
|
||||
|
||||
/* (Only intro point circuit) Which type of authentication key this
|
||||
* circuit identifier is using. */
|
||||
hs_auth_key_type_t auth_key_type;
|
||||
/* (All circuit) Introduction point authentication key. It's also needed on
|
||||
* the rendezvous circuit for the ntor handshake. It's used as the unique key
|
||||
* of the introduction point so it should not be shared between multiple
|
||||
* intro points. */
|
||||
ed25519_public_key_t intro_auth_pk;
|
||||
|
||||
/* (Only intro point circuit) Introduction point authentication key. In
|
||||
* legacy mode, we use an RSA key else an ed25519 public key. */
|
||||
crypto_pk_t *auth_rsa_pk;
|
||||
ed25519_public_key_t auth_ed25519_pk;
|
||||
/* (Only client rendezvous circuit) Introduction point encryption public
|
||||
* key. We keep it in the rendezvous identifier for the ntor handshake. */
|
||||
curve25519_public_key_t intro_enc_pk;
|
||||
|
||||
/* (Only rendezvous circuit) Rendezvous cookie sent from the client to the
|
||||
* service with an INTRODUCE1 cell and used by the service in an
|
||||
* RENDEZVOUS1 cell. */
|
||||
uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN];
|
||||
|
||||
/* (Only rendezvous circuit) The HANDSHAKE_INFO needed in the RENDEZVOUS1
|
||||
* cell of the service. The construction is as follows:
|
||||
/* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the
|
||||
* RENDEZVOUS1 cell of the service. The construction is as follows:
|
||||
* SERVER_PK [32 bytes]
|
||||
* AUTH_MAC [32 bytes]
|
||||
*/
|
||||
uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
|
||||
|
||||
/* (Only client rendezvous circuit) Client ephemeral keypair needed for the
|
||||
* e2e encryption with the service. */
|
||||
curve25519_keypair_t rendezvous_client_kp;
|
||||
|
||||
/* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for
|
||||
* the e2e encryption with the client on the circuit. */
|
||||
uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN];
|
||||
@ -110,6 +115,7 @@ hs_ident_circuit_t *hs_ident_circuit_new(
|
||||
const ed25519_public_key_t *identity_pk,
|
||||
hs_ident_circuit_type_t circuit_type);
|
||||
void hs_ident_circuit_free(hs_ident_circuit_t *ident);
|
||||
hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src);
|
||||
|
||||
/* Directory connection identifier API. */
|
||||
hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "hs/cell_introduce1.h"
|
||||
|
||||
#include "hs_circuitmap.h"
|
||||
#include "hs_descriptor.h"
|
||||
#include "hs_intropoint.h"
|
||||
#include "hs_common.h"
|
||||
|
||||
@ -591,3 +592,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Clear memory allocated by the given intropoint object ip (but don't free the
|
||||
* object itself). */
|
||||
void
|
||||
hs_intropoint_clear(hs_intropoint_t *ip)
|
||||
{
|
||||
if (ip == NULL) {
|
||||
return;
|
||||
}
|
||||
tor_cert_free(ip->auth_key_cert);
|
||||
SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls,
|
||||
hs_desc_link_specifier_free(ls));
|
||||
smartlist_free(ip->link_specifiers);
|
||||
memset(ip, 0, sizeof(hs_intropoint_t));
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,11 @@
|
||||
#include "torcert.h"
|
||||
|
||||
/* Authentication key type in an ESTABLISH_INTRO cell. */
|
||||
enum hs_intro_auth_key_type {
|
||||
typedef enum {
|
||||
HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00,
|
||||
HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01,
|
||||
HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02,
|
||||
};
|
||||
} hs_intro_auth_key_type_t;
|
||||
|
||||
/* INTRODUCE_ACK status code. */
|
||||
typedef enum {
|
||||
@ -30,6 +30,9 @@ typedef enum {
|
||||
/* Object containing introduction point common data between the service and
|
||||
* the client side. */
|
||||
typedef struct hs_intropoint_t {
|
||||
/* Does this intro point only supports legacy ID ?. */
|
||||
unsigned int is_only_legacy : 1;
|
||||
|
||||
/* Authentication key certificate from the descriptor. */
|
||||
tor_cert_t *auth_key_cert;
|
||||
/* A list of link specifier. */
|
||||
@ -47,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ));
|
||||
/* also used by rendservice.c */
|
||||
int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ);
|
||||
|
||||
hs_intropoint_t *hs_intro_new(void);
|
||||
void hs_intropoint_clear(hs_intropoint_t *ip);
|
||||
|
||||
#ifdef HS_INTROPOINT_PRIVATE
|
||||
|
||||
#include "hs/cell_establish_intro.h"
|
||||
|
2667
src/or/hs_service.c
2667
src/or/hs_service.c
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,7 @@
|
||||
|
||||
#include "hs_common.h"
|
||||
#include "hs_descriptor.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_intropoint.h"
|
||||
|
||||
/* Trunnel */
|
||||
@ -25,17 +26,30 @@
|
||||
* present. */
|
||||
#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO
|
||||
|
||||
/* As described in the specification, service publishes their next descriptor
|
||||
* at a random time between those two values (in seconds). */
|
||||
#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60)
|
||||
#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
|
||||
|
||||
/* Service side introduction point. */
|
||||
typedef struct hs_service_intro_point_t {
|
||||
/* Top level intropoint "shared" data between client/service. */
|
||||
hs_intropoint_t base;
|
||||
|
||||
/* Onion key of the introduction point used to extend to it for the ntor
|
||||
* handshake. */
|
||||
curve25519_public_key_t onion_key;
|
||||
|
||||
/* Authentication keypair used to create the authentication certificate
|
||||
* which is published in the descriptor. */
|
||||
ed25519_keypair_t auth_key_kp;
|
||||
|
||||
/* Encryption private key. */
|
||||
curve25519_secret_key_t enc_key_sk;
|
||||
/* Encryption keypair for the "ntor" type. */
|
||||
curve25519_keypair_t enc_key_kp;
|
||||
|
||||
/* Legacy key if that intro point doesn't support v3. This should be used if
|
||||
* the base object legacy flag is set. */
|
||||
crypto_pk_t *legacy_key;
|
||||
|
||||
/* Amount of INTRODUCE2 cell accepted from this intro point. */
|
||||
uint64_t introduce2_count;
|
||||
@ -74,6 +88,12 @@ typedef struct hs_service_intropoints_t {
|
||||
/* Contains the current hs_service_intro_point_t objects indexed by
|
||||
* authentication public key. */
|
||||
digest256map_t *map;
|
||||
|
||||
/* Contains node's identity key digest that were introduction point for this
|
||||
* descriptor but were retried to many times. We keep those so we avoid
|
||||
* re-picking them over and over for a circuit retry period.
|
||||
* XXX: Once we have #22173, change this to only use ed25519 identity. */
|
||||
digestmap_t *failed_id;
|
||||
} hs_service_intropoints_t;
|
||||
|
||||
/* Representation of a service descriptor. */
|
||||
@ -95,6 +115,20 @@ typedef struct hs_service_descriptor_t {
|
||||
* hs_service_intropoints_t object indexed by authentication key (the RSA
|
||||
* key if the node is legacy). */
|
||||
hs_service_intropoints_t intro_points;
|
||||
|
||||
/* The time period number this descriptor has been created for. */
|
||||
uint64_t time_period_num;
|
||||
|
||||
/* True iff we have missing intro points for this descriptor because we
|
||||
* couldn't pick any nodes. */
|
||||
unsigned int missing_intro_points : 1;
|
||||
|
||||
/* List of identity digests for hidden service directories to which we
|
||||
* couldn't upload this descriptor because we didn't have its router
|
||||
* descriptor at the time. If this list is non-empty, only the relays in this
|
||||
* list are re-tried to upload this descriptor when our directory information
|
||||
* have been updated. */
|
||||
smartlist_t *hsdir_missing_info;
|
||||
} hs_service_descriptor_t;
|
||||
|
||||
/* Service key material. */
|
||||
@ -123,10 +157,6 @@ typedef struct hs_service_config_t {
|
||||
* if the service is ephemeral. Specified by HiddenServiceDir option. */
|
||||
char *directory_path;
|
||||
|
||||
/* The time period after which a descriptor is uploaded to the directories
|
||||
* in seconds. Specified by RendPostPeriod option. */
|
||||
uint32_t descriptor_post_period;
|
||||
|
||||
/* The maximum number of simultaneous streams per rendezvous circuit that
|
||||
* are allowed to be created. No limit if 0. Specified by
|
||||
* HiddenServiceMaxStreams option. */
|
||||
@ -170,6 +200,13 @@ typedef struct hs_service_state_t {
|
||||
/* Indicate that the service has entered the overlap period. We use this
|
||||
* flag to check for descriptor rotation. */
|
||||
unsigned int in_overlap_period : 1;
|
||||
|
||||
/* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect
|
||||
* repeats. Clients may send INTRODUCE1 cells for the same rendezvous point
|
||||
* through two or more different introduction points; when they do, this
|
||||
* keeps us from launching multiple simultaneous attempts to connect to the
|
||||
* same rend point. */
|
||||
replaycache_t *replay_cache_rend_cookie;
|
||||
} hs_service_state_t;
|
||||
|
||||
/* Representation of a service running on this tor instance. */
|
||||
@ -216,19 +253,25 @@ void hs_service_free_all(void);
|
||||
hs_service_t *hs_service_new(const or_options_t *options);
|
||||
void hs_service_free(hs_service_t *service);
|
||||
|
||||
unsigned int hs_service_get_num_services(void);
|
||||
void hs_service_stage_services(const smartlist_t *service_list);
|
||||
int hs_service_load_all_keys(void);
|
||||
void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list,
|
||||
smartlist_t *dir_list);
|
||||
int hs_service_set_conn_addr_port(const origin_circuit_t *circ,
|
||||
edge_connection_t *conn);
|
||||
|
||||
/* These functions are only used by unit tests and we need to expose them else
|
||||
* hs_service.o ends up with no symbols in libor.a which makes clang throw a
|
||||
* warning at compile time. See #21825. */
|
||||
void hs_service_dir_info_changed(void);
|
||||
void hs_service_run_scheduled_events(time_t now);
|
||||
void hs_service_circuit_has_opened(origin_circuit_t *circ);
|
||||
int hs_service_receive_intro_established(origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
int hs_service_receive_introduce2(origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
|
||||
trn_cell_establish_intro_t *
|
||||
generate_establish_intro_cell(const uint8_t *circuit_key_material,
|
||||
size_t circuit_key_material_len);
|
||||
ssize_t
|
||||
get_establish_intro_payload(uint8_t *buf, size_t buf_len,
|
||||
const trn_cell_establish_intro_t *cell);
|
||||
void hs_service_intro_circ_has_closed(origin_circuit_t *circ);
|
||||
|
||||
#ifdef HS_SERVICE_PRIVATE
|
||||
|
||||
@ -245,6 +288,55 @@ STATIC hs_service_t *find_service(hs_service_ht *map,
|
||||
const ed25519_public_key_t *pk);
|
||||
STATIC void remove_service(hs_service_ht *map, hs_service_t *service);
|
||||
STATIC int register_service(hs_service_ht *map, hs_service_t *service);
|
||||
/* Service introduction point functions. */
|
||||
STATIC hs_service_intro_point_t *service_intro_point_new(
|
||||
const extend_info_t *ei,
|
||||
unsigned int is_legacy);
|
||||
STATIC void service_intro_point_free(hs_service_intro_point_t *ip);
|
||||
STATIC void service_intro_point_add(digest256map_t *map,
|
||||
hs_service_intro_point_t *ip);
|
||||
STATIC void service_intro_point_remove(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip);
|
||||
STATIC hs_service_intro_point_t *service_intro_point_find(
|
||||
const hs_service_t *service,
|
||||
const ed25519_public_key_t *auth_key);
|
||||
STATIC hs_service_intro_point_t *service_intro_point_find_by_ident(
|
||||
const hs_service_t *service,
|
||||
const hs_ident_circuit_t *ident);
|
||||
/* Service descriptor functions. */
|
||||
STATIC hs_service_descriptor_t *service_descriptor_new(void);
|
||||
STATIC hs_service_descriptor_t *service_desc_find_by_intro(
|
||||
const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip);
|
||||
/* Helper functions. */
|
||||
STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident,
|
||||
hs_service_t **service,
|
||||
hs_service_intro_point_t **ip,
|
||||
hs_service_descriptor_t **desc);
|
||||
STATIC const node_t *
|
||||
get_node_from_intro_point(const hs_service_intro_point_t *ip);
|
||||
STATIC int can_service_launch_intro_circuit(hs_service_t *service,
|
||||
time_t now);
|
||||
STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip,
|
||||
time_t now);
|
||||
STATIC void run_housekeeping_event(time_t now);
|
||||
STATIC void rotate_all_descriptors(time_t now);
|
||||
STATIC void build_all_descriptors(time_t now);
|
||||
STATIC void update_all_descriptors(time_t now);
|
||||
STATIC void run_upload_descriptor_event(time_t now);
|
||||
|
||||
STATIC char *
|
||||
encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc);
|
||||
|
||||
STATIC void service_descriptor_free(hs_service_descriptor_t *desc);
|
||||
|
||||
STATIC uint64_t
|
||||
check_state_line_for_service_rev_counter(const char *state_line,
|
||||
const ed25519_public_key_t *blinded_pubkey,
|
||||
int *service_found_out);
|
||||
|
||||
STATIC int
|
||||
write_address_to_file(const hs_service_t *service, const char *fname_);
|
||||
|
||||
#endif /* TOR_UNIT_TESTS */
|
||||
|
||||
|
@ -54,6 +54,7 @@ LIBTOR_A_SOURCES = \
|
||||
src/or/ext_orport.c \
|
||||
src/or/hibernate.c \
|
||||
src/or/hs_cache.c \
|
||||
src/or/hs_cell.c \
|
||||
src/or/hs_circuit.c \
|
||||
src/or/hs_circuitmap.c \
|
||||
src/or/hs_client.c \
|
||||
@ -184,11 +185,12 @@ ORHEADERS = \
|
||||
src/or/entrynodes.h \
|
||||
src/or/hibernate.h \
|
||||
src/or/hs_cache.h \
|
||||
src/or/hs_cell.h \
|
||||
src/or/hs_config.h \
|
||||
src/or/hs_circuit.h \
|
||||
src/or/hs_circuitmap.h \
|
||||
src/or/hs_client.h \
|
||||
src/or/hs_common.h \
|
||||
src/or/hs_config.h \
|
||||
src/or/hs_descriptor.h \
|
||||
src/or/hs_ident.h \
|
||||
src/or/hs_intropoint.h \
|
||||
|
@ -1194,6 +1194,7 @@ CALLBACK(heartbeat);
|
||||
CALLBACK(clean_consdiffmgr);
|
||||
CALLBACK(reset_padding_counts);
|
||||
CALLBACK(check_canonical_channels);
|
||||
CALLBACK(hs_service);
|
||||
|
||||
#undef CALLBACK
|
||||
|
||||
@ -1229,6 +1230,7 @@ static periodic_event_item_t periodic_events[] = {
|
||||
CALLBACK(clean_consdiffmgr),
|
||||
CALLBACK(reset_padding_counts),
|
||||
CALLBACK(check_canonical_channels),
|
||||
CALLBACK(hs_service),
|
||||
END_OF_PERIODIC_EVENTS
|
||||
};
|
||||
#undef CALLBACK
|
||||
@ -1461,12 +1463,6 @@ run_scheduled_events(time_t now)
|
||||
/* 6. And remove any marked circuits... */
|
||||
circuit_close_all_marked();
|
||||
|
||||
/* 7. And upload service descriptors if necessary. */
|
||||
if (have_completed_a_circuit() && !net_is_disabled()) {
|
||||
rend_consider_services_upload(now);
|
||||
rend_consider_descriptor_republication();
|
||||
}
|
||||
|
||||
/* 8. and blow away any connections that need to die. have to do this now,
|
||||
* because if we marked a conn for close and left its socket -1, then
|
||||
* we'll pass it to poll/select and bad things will happen.
|
||||
@ -2101,6 +2097,28 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options)
|
||||
return CDM_CLEAN_CALLBACK_INTERVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Periodic callback: Run scheduled events for HS service. This is called
|
||||
* every second.
|
||||
*/
|
||||
static int
|
||||
hs_service_callback(time_t now, const or_options_t *options)
|
||||
{
|
||||
(void) options;
|
||||
|
||||
/* We need to at least be able to build circuits and that we actually have
|
||||
* a working network. */
|
||||
if (!have_completed_a_circuit() || net_is_disabled()) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
hs_service_run_scheduled_events(now);
|
||||
|
||||
end:
|
||||
/* Every 1 second. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Timer: used to invoke second_elapsed_callback() once per second. */
|
||||
static periodic_timer_t *second_timer = NULL;
|
||||
/** Number of libevent errors in the last second: we die if we get too many. */
|
||||
@ -3554,7 +3572,7 @@ sandbox_init_filter(void)
|
||||
{
|
||||
smartlist_t *files = smartlist_new();
|
||||
smartlist_t *dirs = smartlist_new();
|
||||
rend_services_add_filenames_to_lists(files, dirs);
|
||||
hs_service_lists_fnames_for_sandbox(files, dirs);
|
||||
SMARTLIST_FOREACH(files, char *, file_name, {
|
||||
char *tmp_name = NULL;
|
||||
tor_asprintf(&tmp_name, "%s.tmp", file_name);
|
||||
@ -3563,6 +3581,7 @@ sandbox_init_filter(void)
|
||||
/* steals references */
|
||||
sandbox_cfg_allow_open_filename(&cfg, file_name);
|
||||
sandbox_cfg_allow_open_filename(&cfg, tmp_name);
|
||||
tor_free(file_name);
|
||||
});
|
||||
SMARTLIST_FOREACH(dirs, char *, dir, {
|
||||
/* steals reference */
|
||||
|
@ -1393,14 +1393,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f))
|
||||
MOCK_IMPL(networkstatus_t *,
|
||||
networkstatus_get_live_consensus,(time_t now))
|
||||
{
|
||||
if (networkstatus_get_latest_consensus() &&
|
||||
networkstatus_get_latest_consensus()->valid_after <= now &&
|
||||
now <= networkstatus_get_latest_consensus()->valid_until)
|
||||
return networkstatus_get_latest_consensus();
|
||||
networkstatus_t *ns = networkstatus_get_latest_consensus();
|
||||
if (ns && networkstatus_is_live(ns, now))
|
||||
return ns;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Given a consensus in <b>ns</b>, return true iff currently live and
|
||||
* unexpired. */
|
||||
int
|
||||
networkstatus_is_live(const networkstatus_t *ns, time_t now)
|
||||
{
|
||||
return (ns->valid_after <= now && now <= ns->valid_until);
|
||||
}
|
||||
|
||||
/** Determine if <b>consensus</b> is valid or expired recently enough that
|
||||
* we can still use it.
|
||||
*
|
||||
|
@ -81,6 +81,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void));
|
||||
MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor,
|
||||
(consensus_flavor_t f));
|
||||
MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now));
|
||||
int networkstatus_is_live(const networkstatus_t *ns, time_t now);
|
||||
int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus,
|
||||
time_t now);
|
||||
int networkstatus_valid_until_is_reasonably_live(time_t valid_until,
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "dirserv.h"
|
||||
#include "entrynodes.h"
|
||||
#include "geoip.h"
|
||||
#include "hs_common.h"
|
||||
#include "main.h"
|
||||
#include "microdesc.h"
|
||||
#include "networkstatus.h"
|
||||
@ -54,6 +55,7 @@
|
||||
#include "rendservice.h"
|
||||
#include "router.h"
|
||||
#include "routerlist.h"
|
||||
#include "routerparse.h"
|
||||
#include "routerset.h"
|
||||
#include "torcert.h"
|
||||
|
||||
@ -164,12 +166,78 @@ node_get_or_create(const char *identity_digest)
|
||||
|
||||
smartlist_add(the_nodelist->nodes, node);
|
||||
node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1;
|
||||
node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t));
|
||||
|
||||
node->country = -1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index
|
||||
* for the node, both current and next if possible. This can only fails if the
|
||||
* node_t ed25519 identity key can't be found which would be a bug. */
|
||||
static void
|
||||
node_set_hsdir_index(node_t *node, const networkstatus_t *ns)
|
||||
{
|
||||
time_t now = approx_time();
|
||||
const ed25519_public_key_t *node_identity_pk;
|
||||
uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL;
|
||||
uint64_t next_time_period_num, current_time_period_num;
|
||||
|
||||
tor_assert(node);
|
||||
tor_assert(ns);
|
||||
|
||||
if (!networkstatus_is_live(ns, now)) {
|
||||
log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
node_identity_pk = node_get_ed25519_id(node);
|
||||
if (node_identity_pk == NULL) {
|
||||
log_debug(LD_GENERAL, "ed25519 identity public key not found when "
|
||||
"trying to build the hsdir indexes for node %s",
|
||||
node_describe(node));
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Get the current and next time period number, we might use them both. */
|
||||
current_time_period_num = hs_get_time_period_num(now);
|
||||
next_time_period_num = hs_get_next_time_period_num(now);
|
||||
|
||||
if (hs_overlap_mode_is_active(ns, now)) {
|
||||
/* We are in overlap mode, this means that our consensus has just cycled
|
||||
* from current SRV to previous SRV so for the _next_ upcoming time
|
||||
* period, we have to use the current SRV and use the previous SRV for the
|
||||
* current time period. If the current or previous SRV can't be found, the
|
||||
* disaster one is returned. */
|
||||
next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns);
|
||||
/* The following can be confusing so again, in overlap mode, we use our
|
||||
* previous SRV for our _current_ hsdir index. */
|
||||
current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns);
|
||||
} else {
|
||||
/* If NOT in overlap mode, we only need to compute the current hsdir index
|
||||
* for the ongoing time period and thus the current SRV. If it can't be
|
||||
* found, the disaster one is returned. */
|
||||
current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns);
|
||||
}
|
||||
|
||||
/* Build the current hsdir index. */
|
||||
hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv,
|
||||
current_time_period_num, node->hsdir_index->current);
|
||||
if (next_hsdir_index_srv) {
|
||||
/* Build the next hsdir index if we have a next SRV that we can use. */
|
||||
hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv,
|
||||
next_time_period_num, node->hsdir_index->next);
|
||||
} else {
|
||||
memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next));
|
||||
}
|
||||
|
||||
done:
|
||||
tor_free(current_hsdir_index_srv);
|
||||
tor_free(next_hsdir_index_srv);
|
||||
return;
|
||||
}
|
||||
|
||||
/** Called when a node's address changes. */
|
||||
static void
|
||||
node_addrs_changed(node_t *node)
|
||||
@ -216,6 +284,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out)
|
||||
dirserv_set_node_flags_from_authoritative_status(node, status);
|
||||
}
|
||||
|
||||
/* Setting the HSDir index requires the ed25519 identity key which can
|
||||
* only be found either in the ri or md. This is why this is called here.
|
||||
* Only nodes supporting HSDir=2 protocol version needs this index. */
|
||||
if (node->rs && node->rs->supports_v3_hsdir) {
|
||||
node_set_hsdir_index(node,
|
||||
networkstatus_get_latest_consensus());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -246,6 +322,12 @@ nodelist_add_microdesc(microdesc_t *md)
|
||||
node->md->held_by_nodes--;
|
||||
node->md = md;
|
||||
md->held_by_nodes++;
|
||||
/* Setting the HSDir index requires the ed25519 identity key which can
|
||||
* only be found either in the ri or md. This is why this is called here.
|
||||
* Only nodes supporting HSDir=2 protocol version needs this index. */
|
||||
if (rs->supports_v3_hsdir) {
|
||||
node_set_hsdir_index(node, ns);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@ -283,6 +365,9 @@ nodelist_set_consensus(networkstatus_t *ns)
|
||||
}
|
||||
}
|
||||
|
||||
if (rs->supports_v3_hsdir) {
|
||||
node_set_hsdir_index(node, ns);
|
||||
}
|
||||
node_set_country(node);
|
||||
|
||||
/* If we're not an authdir, believe others. */
|
||||
@ -410,6 +495,7 @@ node_free(node_t *node)
|
||||
if (node->md)
|
||||
node->md->held_by_nodes--;
|
||||
tor_assert(node->nodelist_idx == -1);
|
||||
tor_free(node->hsdir_index);
|
||||
tor_free(node);
|
||||
}
|
||||
|
||||
@ -719,6 +805,15 @@ node_supports_v3_hsdir(const node_t *node)
|
||||
if (node->ri->protocol_list == NULL) {
|
||||
return 0;
|
||||
}
|
||||
/* Bug #22447 forces us to filter on tor version:
|
||||
* If platform is a Tor version, and older than 0.3.0.8, return False.
|
||||
* Else, obey the protocol list. */
|
||||
if (node->ri->platform) {
|
||||
if (!strcmpstart(node->ri->platform, "Tor ") &&
|
||||
!tor_version_as_new_as(node->ri->platform, "0.3.0.8")) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return protocol_list_supports_protocol(node->ri->protocol_list,
|
||||
PRT_HSDIR, PROTOVER_HSDIR_V3);
|
||||
}
|
||||
|
20
src/or/or.h
20
src/or/or.h
@ -421,15 +421,20 @@ typedef enum {
|
||||
#define DIR_PURPOSE_FETCH_RENDDESC_V2 18
|
||||
/** A connection to a directory server: download a microdescriptor. */
|
||||
#define DIR_PURPOSE_FETCH_MICRODESC 19
|
||||
#define DIR_PURPOSE_MAX_ 19
|
||||
/** A connection to a hidden service directory: upload a v3 descriptor. */
|
||||
#define DIR_PURPOSE_UPLOAD_HSDESC 20
|
||||
/** A connection to a hidden service directory: fetch a v3 descriptor. */
|
||||
#define DIR_PURPOSE_FETCH_HSDESC 21
|
||||
#define DIR_PURPOSE_MAX_ 21
|
||||
|
||||
/** True iff <b>p</b> is a purpose corresponding to uploading
|
||||
* data to a directory server. */
|
||||
#define DIR_PURPOSE_IS_UPLOAD(p) \
|
||||
((p)==DIR_PURPOSE_UPLOAD_DIR || \
|
||||
(p)==DIR_PURPOSE_UPLOAD_VOTE || \
|
||||
(p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
|
||||
(p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2)
|
||||
(p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \
|
||||
(p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \
|
||||
(p)==DIR_PURPOSE_UPLOAD_HSDESC)
|
||||
|
||||
#define EXIT_PURPOSE_MIN_ 1
|
||||
/** This exit stream wants to do an ordinary connect. */
|
||||
@ -850,6 +855,8 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
|
||||
struct hs_ident_edge_conn_t;
|
||||
struct hs_ident_dir_conn_t;
|
||||
struct hs_ident_circuit_t;
|
||||
/* Stub because we can't include hs_common.h. */
|
||||
struct hsdir_index_t;
|
||||
|
||||
/** Time interval for tracking replays of DH public keys received in
|
||||
* INTRODUCE2 cells. Used only to avoid launching multiple
|
||||
@ -2490,6 +2497,10 @@ typedef struct node_t {
|
||||
time_t last_reachable; /* IPv4. */
|
||||
time_t last_reachable6; /* IPv6. */
|
||||
|
||||
/* Hidden service directory index data. This is used by a service or client
|
||||
* in order to know what's the hs directory index for this node at the time
|
||||
* the consensus is set. */
|
||||
struct hsdir_index_t *hsdir_index;
|
||||
} node_t;
|
||||
|
||||
/** Linked list of microdesc hash lines for a single router in a directory
|
||||
@ -4616,6 +4627,9 @@ typedef struct {
|
||||
|
||||
config_line_t *TransportProxies;
|
||||
|
||||
/** Cached revision counters for active hidden services on this host */
|
||||
config_line_t *HidServRevCounter;
|
||||
|
||||
/** These fields hold information on the history of bandwidth usage for
|
||||
* servers. The "Ends" fields hold the time when we last updated the
|
||||
* bandwidth usage. The "Interval" fields hold the granularity, in seconds,
|
||||
|
@ -436,7 +436,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
|
||||
* in the same order in which they occur in <b>s</b>. Otherwise return
|
||||
* NULL. */
|
||||
smartlist_t *
|
||||
find_all_by_keyword(smartlist_t *s, directory_keyword k)
|
||||
find_all_by_keyword(const smartlist_t *s, directory_keyword k)
|
||||
{
|
||||
smartlist_t *out = NULL;
|
||||
SMARTLIST_FOREACH(s, directory_token_t *, t,
|
||||
|
@ -160,6 +160,7 @@ typedef enum {
|
||||
R3_INTRO_AUTH_REQUIRED,
|
||||
R3_SINGLE_ONION_SERVICE,
|
||||
R3_INTRODUCTION_POINT,
|
||||
R3_INTRO_ONION_KEY,
|
||||
R3_INTRO_AUTH_KEY,
|
||||
R3_INTRO_ENC_KEY,
|
||||
R3_INTRO_ENC_KEY_CERT,
|
||||
@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s,
|
||||
|
||||
directory_token_t *find_opt_by_keyword(smartlist_t *s,
|
||||
directory_keyword keyword);
|
||||
smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k);
|
||||
smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k);
|
||||
|
||||
#endif /* TOR_PARSECOMMON_H */
|
||||
|
||||
|
@ -777,7 +777,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
|
||||
break;
|
||||
case RELAY_COMMAND_INTRODUCE2:
|
||||
if (origin_circ)
|
||||
r = rend_service_receive_introduction(origin_circ,payload,length);
|
||||
r = hs_service_receive_introduce2(origin_circ,payload,length);
|
||||
break;
|
||||
case RELAY_COMMAND_INTRODUCE_ACK:
|
||||
if (origin_circ)
|
||||
@ -793,7 +793,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
|
||||
break;
|
||||
case RELAY_COMMAND_INTRO_ESTABLISHED:
|
||||
if (origin_circ)
|
||||
r = rend_service_intro_established(origin_circ,payload,length);
|
||||
r = hs_service_receive_intro_established(origin_circ,payload,length);
|
||||
break;
|
||||
case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED:
|
||||
if (origin_circ)
|
||||
|
@ -83,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable(
|
||||
smartlist_t* substitute_service_list);
|
||||
static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted);
|
||||
|
||||
/** Represents the mapping from a virtual port of a rendezvous service to
|
||||
* a real port on some IP.
|
||||
*/
|
||||
struct rend_service_port_config_s {
|
||||
/* The incoming HS virtual port we're mapping */
|
||||
uint16_t virtual_port;
|
||||
/* Is this an AF_UNIX port? */
|
||||
unsigned int is_unix_addr:1;
|
||||
/* The outgoing TCP port to use, if !is_unix_addr */
|
||||
uint16_t real_port;
|
||||
/* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */
|
||||
tor_addr_t real_addr;
|
||||
/* The socket path to connect to, if is_unix_addr */
|
||||
char unix_addr[FLEXIBLE_ARRAY_MEMBER];
|
||||
};
|
||||
|
||||
/* Hidden service directory file names:
|
||||
* new file names should be added to rend_service_add_filenames_to_list()
|
||||
* for sandboxing purposes. */
|
||||
@ -164,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s)
|
||||
|
||||
/** Return the number of rendezvous services we have configured. */
|
||||
int
|
||||
num_rend_services(void)
|
||||
rend_num_services(void)
|
||||
{
|
||||
if (!rend_service_list)
|
||||
return 0;
|
||||
@ -1694,24 +1678,6 @@ rend_service_get_by_service_id(const char *id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Return 1 if any virtual port in <b>service</b> wants a circuit
|
||||
* to have good uptime. Else return 0.
|
||||
*/
|
||||
static int
|
||||
rend_service_requires_uptime(rend_service_t *service)
|
||||
{
|
||||
int i;
|
||||
rend_service_port_config_t *p;
|
||||
|
||||
for (i=0; i < smartlist_len(service->ports); ++i) {
|
||||
p = smartlist_get(service->ports, i);
|
||||
if (smartlist_contains_int_as_string(get_options()->LongLivedPorts,
|
||||
p->virtual_port))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Check client authorization of a given <b>descriptor_cookie</b> of
|
||||
* length <b>cookie_len</b> for <b>service</b>. Return 1 for success
|
||||
* and 0 for failure. */
|
||||
@ -2029,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
|
||||
goto err;
|
||||
}
|
||||
|
||||
circ_needs_uptime = rend_service_requires_uptime(service);
|
||||
circ_needs_uptime = hs_service_requires_uptime_circ(service->ports);
|
||||
|
||||
/* help predict this next time */
|
||||
rep_hist_note_used_internal(now, circ_needs_uptime, 1);
|
||||
@ -2926,29 +2892,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc)
|
||||
|
||||
tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
|
||||
|
||||
/* Don't relaunch the same rend circ twice. */
|
||||
if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) {
|
||||
log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; "
|
||||
"not relaunching it again.",
|
||||
oldcirc->build_state ?
|
||||
safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
|
||||
: "*unknown*");
|
||||
return;
|
||||
}
|
||||
oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1;
|
||||
|
||||
if (!oldcirc->build_state ||
|
||||
oldcirc->build_state->failure_count > MAX_REND_FAILURES ||
|
||||
oldcirc->build_state->expiry_time < time(NULL)) {
|
||||
log_info(LD_REND,
|
||||
"Attempt to build circuit to %s for rendezvous has failed "
|
||||
"too many times or expired; giving up.",
|
||||
oldcirc->build_state ?
|
||||
safe_str(extend_info_describe(oldcirc->build_state->chosen_exit))
|
||||
: "*unknown*");
|
||||
return;
|
||||
}
|
||||
|
||||
oldstate = oldcirc->build_state;
|
||||
tor_assert(oldstate);
|
||||
|
||||
@ -3116,10 +3059,11 @@ count_intro_point_circuits(const rend_service_t *service)
|
||||
crypto material. On success, fill <b>cell_body_out</b> and return the number
|
||||
of bytes written. On fail, return -1.
|
||||
*/
|
||||
STATIC ssize_t
|
||||
encode_establish_intro_cell_legacy(char *cell_body_out,
|
||||
size_t cell_body_out_len,
|
||||
crypto_pk_t *intro_key, char *rend_circ_nonce)
|
||||
ssize_t
|
||||
rend_service_encode_establish_intro_cell(char *cell_body_out,
|
||||
size_t cell_body_out_len,
|
||||
crypto_pk_t *intro_key,
|
||||
const char *rend_circ_nonce)
|
||||
{
|
||||
int retval = -1;
|
||||
int r;
|
||||
@ -3256,7 +3200,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
|
||||
/* Send the ESTABLISH_INTRO cell */
|
||||
{
|
||||
ssize_t len;
|
||||
len = encode_establish_intro_cell_legacy(buf, sizeof(buf),
|
||||
len = rend_service_encode_establish_intro_cell(buf, sizeof(buf),
|
||||
circuit->intro_key,
|
||||
circuit->cpath->prev->rend_circ_nonce);
|
||||
if (len < 0) {
|
||||
@ -3983,10 +3927,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted)
|
||||
* This is called once a second by the main loop.
|
||||
*/
|
||||
void
|
||||
rend_consider_services_intro_points(void)
|
||||
rend_consider_services_intro_points(time_t now)
|
||||
{
|
||||
int i;
|
||||
time_t now;
|
||||
const or_options_t *options = get_options();
|
||||
/* Are we in single onion mode? */
|
||||
const int allow_direct = rend_service_allow_non_anonymous_connection(
|
||||
@ -4003,7 +3946,6 @@ rend_consider_services_intro_points(void)
|
||||
|
||||
exclude_nodes = smartlist_new();
|
||||
retry_nodes = smartlist_new();
|
||||
now = time(NULL);
|
||||
|
||||
SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) {
|
||||
int r;
|
||||
@ -4281,60 +4223,6 @@ rend_service_dump_stats(int severity)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SYS_UN_H
|
||||
|
||||
/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
|
||||
* add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success
|
||||
* else return -ENOSYS if AF_UNIX is not supported (see function in the
|
||||
* #else statement below). */
|
||||
static int
|
||||
add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
|
||||
{
|
||||
tor_assert(ports);
|
||||
tor_assert(p);
|
||||
tor_assert(p->is_unix_addr);
|
||||
|
||||
smartlist_add(ports, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0
|
||||
* on success else return -ENOSYS if AF_UNIX is not supported (see function
|
||||
* in the #else statement below). */
|
||||
static int
|
||||
set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
|
||||
{
|
||||
tor_assert(conn);
|
||||
tor_assert(p);
|
||||
tor_assert(p->is_unix_addr);
|
||||
|
||||
conn->base_.socket_family = AF_UNIX;
|
||||
tor_addr_make_unspec(&conn->base_.addr);
|
||||
conn->base_.port = 1;
|
||||
conn->base_.address = tor_strdup(p->unix_addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* defined(HAVE_SYS_UN_H) */
|
||||
|
||||
static int
|
||||
set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p)
|
||||
{
|
||||
(void) conn;
|
||||
(void) p;
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int
|
||||
add_unix_port(smartlist_t *ports, rend_service_port_config_t *p)
|
||||
{
|
||||
(void) ports;
|
||||
(void) p;
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
#endif /* HAVE_SYS_UN_H */
|
||||
|
||||
/** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for
|
||||
* 'circ', and look up the port and address based on conn-\>port.
|
||||
* Assign the actual conn-\>addr and conn-\>port. Return -2 on failure
|
||||
@ -4347,9 +4235,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
|
||||
{
|
||||
rend_service_t *service;
|
||||
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
|
||||
smartlist_t *matching_ports;
|
||||
rend_service_port_config_t *chosen_port;
|
||||
unsigned int warn_once = 0;
|
||||
const char *rend_pk_digest;
|
||||
|
||||
tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
|
||||
@ -4385,41 +4270,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
|
||||
return service->max_streams_close_circuit ? -2 : -1;
|
||||
}
|
||||
}
|
||||
matching_ports = smartlist_new();
|
||||
SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p,
|
||||
{
|
||||
if (conn->base_.port != p->virtual_port) {
|
||||
continue;
|
||||
}
|
||||
if (!(p->is_unix_addr)) {
|
||||
smartlist_add(matching_ports, p);
|
||||
} else {
|
||||
if (add_unix_port(matching_ports, p)) {
|
||||
if (!warn_once) {
|
||||
/* Unix port not supported so warn only once. */
|
||||
log_warn(LD_REND,
|
||||
"Saw AF_UNIX virtual port mapping for port %d on service "
|
||||
"%s, which is unsupported on this platform. Ignoring it.",
|
||||
conn->base_.port, serviceid);
|
||||
}
|
||||
warn_once++;
|
||||
}
|
||||
}
|
||||
});
|
||||
chosen_port = smartlist_choose(matching_ports);
|
||||
smartlist_free(matching_ports);
|
||||
if (chosen_port) {
|
||||
if (!(chosen_port->is_unix_addr)) {
|
||||
/* Get a non-AF_UNIX connection ready for connection_exit_connect() */
|
||||
tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr);
|
||||
conn->base_.port = chosen_port->real_port;
|
||||
} else {
|
||||
if (set_unix_port(conn, chosen_port)) {
|
||||
/* Simply impossible to end up here else we were able to add a Unix
|
||||
* port without AF_UNIX support... ? */
|
||||
tor_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (hs_set_conn_addr_port(service->ports, conn) == 0) {
|
||||
/* Successfully set the port to the connection. We are done. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,6 @@
|
||||
#include "hs_service.h"
|
||||
|
||||
typedef struct rend_intro_cell_s rend_intro_cell_t;
|
||||
typedef struct rend_service_port_config_s rend_service_port_config_t;
|
||||
|
||||
#ifdef RENDSERVICE_PRIVATE
|
||||
|
||||
/* This can be used for both INTRODUCE1 and INTRODUCE2 */
|
||||
|
||||
@ -64,6 +61,8 @@ struct rend_intro_cell_s {
|
||||
uint8_t dh[DH_KEY_LEN];
|
||||
};
|
||||
|
||||
#ifdef RENDSERVICE_PRIVATE
|
||||
|
||||
/** Represents a single hidden service running at this OP. */
|
||||
typedef struct rend_service_t {
|
||||
/* Fields specified in config file */
|
||||
@ -126,10 +125,6 @@ STATIC int rend_service_verify_single_onion_poison(
|
||||
STATIC int rend_service_poison_new_single_onion_dir(
|
||||
const rend_service_t *s,
|
||||
const or_options_t* options);
|
||||
STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out,
|
||||
size_t cell_body_out_len,
|
||||
crypto_pk_t *intro_key,
|
||||
char *rend_circ_nonce);
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
STATIC void set_rend_service_list(smartlist_t *new_list);
|
||||
@ -140,7 +135,7 @@ STATIC void rend_service_prune_list_impl_(void);
|
||||
|
||||
#endif /* RENDSERVICE_PRIVATE */
|
||||
|
||||
int num_rend_services(void);
|
||||
int rend_num_services(void);
|
||||
int rend_config_service(const config_line_t *line_,
|
||||
const or_options_t *options,
|
||||
hs_service_config_t *config);
|
||||
@ -149,7 +144,7 @@ void rend_service_free_staging_list(void);
|
||||
int rend_service_load_all_keys(const smartlist_t *service_list);
|
||||
void rend_services_add_filenames_to_lists(smartlist_t *open_lst,
|
||||
smartlist_t *stat_lst);
|
||||
void rend_consider_services_intro_points(void);
|
||||
void rend_consider_services_intro_points(time_t now);
|
||||
void rend_consider_services_upload(time_t now);
|
||||
void rend_hsdir_routers_changed(void);
|
||||
void rend_consider_descriptor_republication(void);
|
||||
@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request,
|
||||
char **err_msg_out);
|
||||
int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro,
|
||||
char **err_msg_out);
|
||||
ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out,
|
||||
size_t cell_body_out_len,
|
||||
crypto_pk_t *intro_key,
|
||||
const char *rend_circ_nonce);
|
||||
int rend_service_validate_intro_late(const rend_intro_cell_t *intro,
|
||||
char **err_msg_out);
|
||||
void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc);
|
||||
|
@ -2706,7 +2706,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
|
||||
rs->supports_ed25519_hs_intro =
|
||||
protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4);
|
||||
rs->supports_v3_hsdir =
|
||||
protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2);
|
||||
protocol_list_supports_protocol(tok->args[0], PRT_HSDIR,
|
||||
PROTOVER_HSDIR_V3);
|
||||
}
|
||||
if ((tok = find_opt_by_keyword(tokens, K_V))) {
|
||||
tor_assert(tok->n_args == 1);
|
||||
@ -2718,6 +2719,12 @@ routerstatus_parse_entry_from_string(memarea_t *area,
|
||||
tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha");
|
||||
rs->protocols_known = 1;
|
||||
}
|
||||
if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) {
|
||||
/* Bug #22447 forces us to filter on this version. */
|
||||
if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) {
|
||||
rs->supports_v3_hsdir = 0;
|
||||
}
|
||||
}
|
||||
if (vote_rs) {
|
||||
vote_rs->version = tor_strdup(tok->args[0]);
|
||||
}
|
||||
|
@ -1390,6 +1390,52 @@ sr_get_previous_for_control(void)
|
||||
return srv_str;
|
||||
}
|
||||
|
||||
/* Return current shared random value from the latest consensus. Caller can
|
||||
* NOT keep a reference to the returned pointer. Return NULL if none. */
|
||||
const sr_srv_t *
|
||||
sr_get_current(const networkstatus_t *ns)
|
||||
{
|
||||
const networkstatus_t *consensus;
|
||||
|
||||
/* Use provided ns else get a live one */
|
||||
if (ns) {
|
||||
consensus = ns;
|
||||
} else {
|
||||
consensus = networkstatus_get_live_consensus(approx_time());
|
||||
}
|
||||
/* Ideally we would never be asked for an SRV without a live consensus. Make
|
||||
* sure this assumption is correct. */
|
||||
tor_assert_nonfatal(consensus);
|
||||
|
||||
if (consensus) {
|
||||
return consensus->sr_info.current_srv;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return previous shared random value from the latest consensus. Caller can
|
||||
* NOT keep a reference to the returned pointer. Return NULL if none. */
|
||||
const sr_srv_t *
|
||||
sr_get_previous(const networkstatus_t *ns)
|
||||
{
|
||||
const networkstatus_t *consensus;
|
||||
|
||||
/* Use provided ns else get a live one */
|
||||
if (ns) {
|
||||
consensus = ns;
|
||||
} else {
|
||||
consensus = networkstatus_get_live_consensus(approx_time());
|
||||
}
|
||||
/* Ideally we would never be asked for an SRV without a live consensus. Make
|
||||
* sure this assumption is correct. */
|
||||
tor_assert_nonfatal(consensus);
|
||||
|
||||
if (consensus) {
|
||||
return consensus->sr_info.previous_srv;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
/* Set the global value of number of SRV agreements so the test can play
|
||||
|
@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp,
|
||||
char *sr_get_current_for_control(void);
|
||||
char *sr_get_previous_for_control(void);
|
||||
|
||||
const sr_srv_t *sr_get_current(const networkstatus_t *ns);
|
||||
const sr_srv_t *sr_get_previous(const networkstatus_t *ns);
|
||||
|
||||
#ifdef SHARED_RANDOM_PRIVATE
|
||||
|
||||
/* Encode */
|
||||
|
@ -133,7 +133,7 @@ get_voting_interval(void)
|
||||
/* Given the time <b>now</b>, return the start time of the current round of
|
||||
* the SR protocol. For example, if it's 23:47:08, the current round thus
|
||||
* started at 23:47:00 for a voting interval of 10 seconds. */
|
||||
static time_t
|
||||
STATIC time_t
|
||||
get_start_time_of_current_round(time_t now)
|
||||
{
|
||||
const or_options_t *options = get_options();
|
||||
@ -156,6 +156,42 @@ get_start_time_of_current_round(time_t now)
|
||||
return curr_start;
|
||||
}
|
||||
|
||||
/** Return the start time of the current SR protocol run. For example, if the
|
||||
* time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this
|
||||
* function should return 23/06/2017 00:00:00. */
|
||||
time_t
|
||||
sr_state_get_start_time_of_current_protocol_run(time_t now)
|
||||
{
|
||||
int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
|
||||
int voting_interval = get_voting_interval();
|
||||
/* Find the time the current round started. */
|
||||
time_t beginning_of_current_round = get_start_time_of_current_round(now);
|
||||
|
||||
/* Get current SR protocol round */
|
||||
int current_round = (now / voting_interval) % total_rounds;
|
||||
|
||||
/* Get start time by subtracting the time elapsed from the beginning of the
|
||||
protocol run */
|
||||
time_t time_elapsed_since_start_of_run = current_round * voting_interval;
|
||||
return beginning_of_current_round - time_elapsed_since_start_of_run;
|
||||
}
|
||||
|
||||
/** Return the time (in seconds) it takes to complete a full SR protocol phase
|
||||
* (e.g. the commit phase). */
|
||||
unsigned int
|
||||
sr_state_get_phase_duration(void)
|
||||
{
|
||||
return SHARED_RANDOM_N_ROUNDS * get_voting_interval();
|
||||
}
|
||||
|
||||
/** Return the time (in seconds) it takes to complete a full SR protocol run */
|
||||
unsigned int
|
||||
sr_state_get_protocol_run_duration(void)
|
||||
{
|
||||
int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
|
||||
return total_protocol_rounds * get_voting_interval();
|
||||
}
|
||||
|
||||
/* Return the time we should expire the state file created at <b>now</b>.
|
||||
* We expire the state file in the beginning of the next protocol run. */
|
||||
STATIC time_t
|
||||
|
@ -121,11 +121,16 @@ int sr_state_is_initialized(void);
|
||||
void sr_state_save(void);
|
||||
void sr_state_free(void);
|
||||
|
||||
time_t sr_state_get_start_time_of_current_protocol_run(time_t now);
|
||||
unsigned int sr_state_get_phase_duration(void);
|
||||
unsigned int sr_state_get_protocol_run_duration(void);
|
||||
|
||||
#ifdef SHARED_RANDOM_STATE_PRIVATE
|
||||
|
||||
STATIC int disk_state_load_from_disk_impl(const char *fname);
|
||||
|
||||
STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after);
|
||||
STATIC time_t get_start_time_of_current_round(time_t now);
|
||||
|
||||
STATIC time_t get_state_valid_until_time(time_t now);
|
||||
STATIC const char *get_phase_str(sr_phase_t phase);
|
||||
|
@ -85,6 +85,8 @@ static config_var_t state_vars_[] = {
|
||||
VAR("TransportProxy", LINELIST_S, TransportProxies, NULL),
|
||||
V(TransportProxies, LINELIST_V, NULL),
|
||||
|
||||
V(HidServRevCounter, LINELIST, NULL),
|
||||
|
||||
V(BWHistoryReadEnds, ISOTIME, NULL),
|
||||
V(BWHistoryReadInterval, UINT, "900"),
|
||||
V(BWHistoryReadValues, CSV, ""),
|
||||
|
@ -32,8 +32,7 @@ def curve25519ToEd25519(c, sign):
|
||||
return encodepoint([x,y])
|
||||
|
||||
def blindESK(esk, param):
|
||||
h = H("Derive temporary signing key" + param)
|
||||
mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
|
||||
mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
|
||||
s = decodeint(esk[:32])
|
||||
s_prime = (s * mult) % ell
|
||||
k = esk[32:]
|
||||
@ -42,8 +41,7 @@ def blindESK(esk, param):
|
||||
return encodeint(s_prime) + k_prime
|
||||
|
||||
def blindPK(pk, param):
|
||||
h = H("Derive temporary signing key" + param)
|
||||
mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
|
||||
mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2))
|
||||
P = decodepoint(pk)
|
||||
return encodepoint(scalarmult(P, mult))
|
||||
|
||||
|
@ -91,21 +91,21 @@ static const char *ED25519_BLINDING_PARAMS[] = {
|
||||
* blinding parameter.
|
||||
*/
|
||||
static const char *ED25519_BLINDED_SECRET_KEYS[] = {
|
||||
"014e83abadb2ca9a27e0ffe23920333d817729f48700e97656ec2823d694050e171d43"
|
||||
"293c3acff4e902f6f63ddc5d5caa2a57e771db4f24de65d4c28df3232f47fa01171d43"
|
||||
"f24e3f53e70ec7ac280044ac77d4942dee5d6807118a59bdf3ee647e89",
|
||||
"fad8cca0b4335847795288b1452508752b253e64e6c7c78d4a02dbbd7d46aa0eb8ceff"
|
||||
"38b88f9f9440358da544504ee152fb475528f7c51c285bd1c68b14ade8e29a07b8ceff"
|
||||
"20dfcf53eb52b891fc078c934efbf0353af7242e7dc51bb32a093afa29",
|
||||
"116eb0ae0a4a91763365bdf86db427b00862db448487808788cc339ac10e5e089217f5"
|
||||
"4d03ce16a3f3249846aac9de0a0075061495c3b027248eeee47da4ddbaf9e0049217f5"
|
||||
"2e92797462bd890fc274672e05c98f2c82970d640084781334aae0f940",
|
||||
"bd1fbb0ee5acddc4adbcf5f33e95d9445f40326ce579fdd764a24483a9ccb20f509ece"
|
||||
"51d7db01aaa0d937a9fd7c8c7381445a14d8fa61f43347af5460d7cd8fda9904509ece"
|
||||
"e77082ce088f7c19d5a00e955eeef8df6fa41686abc1030c2d76807733",
|
||||
"237f5345cefe8573ce9fa7e216381a1172796c9e3f70668ab503b1352952530fb57b95"
|
||||
"1f76cab834e222bd2546efa7e073425680ab88df186ff41327d3e40770129b00b57b95"
|
||||
"a440570659a440a3e4771465022a8e67af86bdf2d0990c54e7bb87ff9a",
|
||||
"ba8ff23bc4ad2b739e1ccffc9fbc7837053ea81cdfdb15073f56411cfbae1d0ec492fc"
|
||||
"c23588c23ee76093419d07b27c6df5922a03ac58f96c53671456a7d1bdbf560ec492fc"
|
||||
"87d5ec2a1b185ca5a40541fdef0b1e128fd5c2380c888bfa924711bcab",
|
||||
"0fa68f969de038c7a90a4a74ee6167c77582006f2dedecc1956501ba6b6fb10391b476"
|
||||
"3ed249c6932d076e1a2f6916975914b14e8c739da00992358b8f37d3e790650691b476"
|
||||
"8f8e556d78f4bdcb9a13b6f6066fe81d3134ae965dc48cd0785b3af2b8",
|
||||
"deaa3456d1c21944d5dcd361a646858c6cf9336b0a6851d925717eb1ae186902053d9c"
|
||||
"288cbfd923cb286d48c084555b5bdd06c05e92fb81acdb45271367f57515380e053d9c"
|
||||
"00c81e1331c06ab50087be8cfc7dc11691b132614474f1aa9c2503cccd",
|
||||
};
|
||||
|
||||
@ -115,14 +115,14 @@ static const char *ED25519_BLINDED_SECRET_KEYS[] = {
|
||||
* blinding parameter.
|
||||
*/
|
||||
static const char *ED25519_BLINDED_PUBLIC_KEYS[] = {
|
||||
"722d6da6348e618967ef782e71061e27163a8b35f21856475d9d2023f65b6495",
|
||||
"1dffa0586da6cbfcff2024eedf4fc6c818242d9a82dbbe635d6da1b975a1160d",
|
||||
"5ed81f98fed5a6acda4ea6da2c34fab0ab359d950c510c256473f1f33ff438b4",
|
||||
"6e6f92a54fb282120c46d9603df41135f025bc1f58f283809d04be96aeb04040",
|
||||
"cda236f28edc4c7e02d18007b8dab49d669265b0f7aefb1824d7cc8e73a2cd63",
|
||||
"367b03b17b67ca7329b89a520bdab91782402a41cd67264e34b5541a4b3f875b",
|
||||
"8d486b03ac4e3b486b7a1d563706c7fdac75aee789a7cf6f22789eedeff61a31",
|
||||
"9f297ff0aa2ceda91c5ab1b6446f12533d145940de6d850dc323417afde0cb78",
|
||||
"1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41",
|
||||
"1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e",
|
||||
"c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028",
|
||||
"3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f",
|
||||
"59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d",
|
||||
"2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9",
|
||||
"c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e",
|
||||
"0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac",
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "test.h"
|
||||
#include "torcert.h"
|
||||
|
||||
#include "hs_common.h"
|
||||
#include "hs_test_helpers.h"
|
||||
|
||||
hs_desc_intro_point_t *
|
||||
@ -15,8 +16,7 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
int ret;
|
||||
ed25519_keypair_t auth_kp;
|
||||
hs_desc_intro_point_t *intro_point = NULL;
|
||||
hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip));
|
||||
ip->link_specifiers = smartlist_new();
|
||||
hs_desc_intro_point_t *ip = hs_desc_intro_point_new();
|
||||
|
||||
{
|
||||
hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls));
|
||||
@ -94,8 +94,7 @@ static hs_descriptor_t *
|
||||
hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
const ed25519_keypair_t *signing_kp)
|
||||
{
|
||||
int ret;
|
||||
time_t now = time(NULL);
|
||||
time_t now = approx_time();
|
||||
ed25519_keypair_t blinded_kp;
|
||||
hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc));
|
||||
|
||||
@ -105,8 +104,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
memcpy(&desc->plaintext_data.signing_pubkey, &signing_kp->pubkey,
|
||||
sizeof(ed25519_public_key_t));
|
||||
|
||||
ret = ed25519_keypair_generate(&blinded_kp, 0);
|
||||
tt_int_op(ret, ==, 0);
|
||||
uint64_t current_time_period = hs_get_time_period_num(approx_time());
|
||||
hs_build_blinded_keypair(signing_kp, NULL, 0,
|
||||
current_time_period, &blinded_kp);
|
||||
/* Copy only the public key into the descriptor. */
|
||||
memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey,
|
||||
sizeof(ed25519_public_key_t));
|
||||
@ -119,6 +119,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
desc->plaintext_data.revision_counter = 42;
|
||||
desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
|
||||
|
||||
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
|
||||
desc->subcredential);
|
||||
|
||||
/* Setup encrypted data section. */
|
||||
desc->encrypted_data.create2_ntor = 1;
|
||||
desc->encrypted_data.intro_auth_types = smartlist_new();
|
||||
@ -142,6 +145,21 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
return descp;
|
||||
}
|
||||
|
||||
/** Helper function to get the HS subcredential using the identity keypair of
|
||||
* an HS. Used to decrypt descriptors in unittests. */
|
||||
void
|
||||
hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
|
||||
uint8_t *subcred_out)
|
||||
{
|
||||
ed25519_keypair_t blinded_kp;
|
||||
uint64_t current_time_period = hs_get_time_period_num(approx_time());
|
||||
hs_build_blinded_keypair(signing_kp, NULL, 0,
|
||||
current_time_period, &blinded_kp);
|
||||
|
||||
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
|
||||
subcred_out);
|
||||
}
|
||||
|
||||
/* Build a descriptor with introduction points. */
|
||||
hs_descriptor_t *
|
||||
hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp)
|
||||
|
@ -17,6 +17,9 @@ hs_descriptor_t *hs_helper_build_hs_desc_with_ip(
|
||||
const ed25519_keypair_t *signing_kp);
|
||||
void hs_helper_desc_equal(const hs_descriptor_t *desc1,
|
||||
const hs_descriptor_t *desc2);
|
||||
void
|
||||
hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
|
||||
uint8_t *subcred_out);
|
||||
|
||||
#endif /* TOR_HS_TEST_HELPERS_H */
|
||||
|
||||
|
@ -115,7 +115,10 @@ src_test_test_SOURCES = \
|
||||
src/test/test_guardfraction.c \
|
||||
src/test/test_extorport.c \
|
||||
src/test/test_hs.c \
|
||||
src/test/test_hs_common.c \
|
||||
src/test/test_hs_config.c \
|
||||
src/test/test_hs_cell.c \
|
||||
src/test/test_hs_ntor.c \
|
||||
src/test/test_hs_service.c \
|
||||
src/test/test_hs_client.c \
|
||||
src/test/test_hs_intropoint.c \
|
||||
|
@ -1214,8 +1214,11 @@ struct testgroup_t testgroups[] = {
|
||||
{ "extorport/", extorport_tests },
|
||||
{ "legacy_hs/", hs_tests },
|
||||
{ "hs_cache/", hs_cache },
|
||||
{ "hs_cell/", hs_cell_tests },
|
||||
{ "hs_common/", hs_common_tests },
|
||||
{ "hs_config/", hs_config_tests },
|
||||
{ "hs_descriptor/", hs_descriptor },
|
||||
{ "hs_ntor/", hs_ntor_tests },
|
||||
{ "hs_service/", hs_service_tests },
|
||||
{ "hs_client/", hs_client_tests },
|
||||
{ "hs_intropoint/", hs_intropoint_tests },
|
||||
|
@ -207,8 +207,11 @@ extern struct testcase_t guardfraction_tests[];
|
||||
extern struct testcase_t extorport_tests[];
|
||||
extern struct testcase_t hs_tests[];
|
||||
extern struct testcase_t hs_cache[];
|
||||
extern struct testcase_t hs_cell_tests[];
|
||||
extern struct testcase_t hs_common_tests[];
|
||||
extern struct testcase_t hs_config_tests[];
|
||||
extern struct testcase_t hs_descriptor[];
|
||||
extern struct testcase_t hs_ntor_tests[];
|
||||
extern struct testcase_t hs_service_tests[];
|
||||
extern struct testcase_t hs_client_tests[];
|
||||
extern struct testcase_t hs_intropoint_tests[];
|
||||
|
@ -342,6 +342,7 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
hs_descriptor_t *published_desc = NULL;
|
||||
char *published_desc_str = NULL;
|
||||
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
char *received_desc_str = NULL;
|
||||
hs_descriptor_t *received_desc = NULL;
|
||||
|
||||
@ -378,9 +379,11 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
const ed25519_public_key_t *blinded_key;
|
||||
|
||||
blinded_key = &published_desc->plaintext_data.blinded_pubkey;
|
||||
hs_get_subcredential(&signing_kp.pubkey, blinded_key, subcredential);
|
||||
received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
|
||||
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc);
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,
|
||||
subcredential, &received_desc);
|
||||
tt_int_op(retval, ==, 0);
|
||||
tt_assert(received_desc);
|
||||
|
||||
@ -412,7 +415,8 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
blinded_key = &published_desc->plaintext_data.blinded_pubkey;
|
||||
received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
|
||||
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc);
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,
|
||||
subcredential, &received_desc);
|
||||
tt_int_op(retval, ==, 0);
|
||||
tt_assert(received_desc);
|
||||
|
||||
|
130
src/test/test_hs_cell.c
Normal file
130
src/test/test_hs_cell.c
Normal file
@ -0,0 +1,130 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file test_hs_cell.c
|
||||
* \brief Test hidden service cell functionality.
|
||||
*/
|
||||
|
||||
#define HS_INTROPOINT_PRIVATE
|
||||
#define HS_SERVICE_PRIVATE
|
||||
|
||||
#include "test.h"
|
||||
#include "test_helpers.h"
|
||||
#include "log_test_helpers.h"
|
||||
|
||||
#include "crypto_ed25519.h"
|
||||
#include "hs_cell.h"
|
||||
#include "hs_intropoint.h"
|
||||
#include "hs_service.h"
|
||||
|
||||
/* Trunnel. */
|
||||
#include "hs/cell_establish_intro.h"
|
||||
|
||||
/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we
|
||||
* parse it from the receiver side. */
|
||||
static void
|
||||
test_gen_establish_intro_cell(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
ssize_t ret;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t buf[RELAY_PAYLOAD_SIZE];
|
||||
trn_cell_establish_intro_t *cell_in = NULL;
|
||||
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
{
|
||||
/* We only need the auth key pair here. */
|
||||
hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0);
|
||||
/* Auth key pair is generated in the constructor so we are all set for
|
||||
* using this IP object. */
|
||||
ret = hs_cell_build_establish_intro(circ_nonce, ip, buf);
|
||||
service_intro_point_free(ip);
|
||||
tt_u64_op(ret, OP_GT, 0);
|
||||
}
|
||||
|
||||
/* Check the contents of the cell */
|
||||
{
|
||||
/* First byte is the auth key type: make sure its correct */
|
||||
tt_int_op(buf[0], OP_EQ, HS_INTRO_AUTH_KEY_TYPE_ED25519);
|
||||
/* Next two bytes is auth key len */
|
||||
tt_int_op(ntohs(get_uint16(buf+1)), OP_EQ, ED25519_PUBKEY_LEN);
|
||||
/* Skip to the number of extensions: no extensions */
|
||||
tt_int_op(buf[35], OP_EQ, 0);
|
||||
/* Skip to the sig len. Make sure it's the size of an ed25519 sig */
|
||||
tt_int_op(ntohs(get_uint16(buf+35+1+32)), OP_EQ, ED25519_SIG_LEN);
|
||||
}
|
||||
|
||||
/* Parse it as the receiver */
|
||||
{
|
||||
ret = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf));
|
||||
tt_u64_op(ret, OP_GT, 0);
|
||||
|
||||
ret = verify_establish_intro_cell(cell_in,
|
||||
(const uint8_t *) circ_nonce,
|
||||
sizeof(circ_nonce));
|
||||
tt_u64_op(ret, OP_EQ, 0);
|
||||
}
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(cell_in);
|
||||
}
|
||||
|
||||
/* Mocked ed25519_sign_prefixed() function that always fails :) */
|
||||
static int
|
||||
mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out,
|
||||
const uint8_t *msg, size_t msg_len,
|
||||
const char *prefix_str,
|
||||
const ed25519_keypair_t *keypair) {
|
||||
(void) signature_out;
|
||||
(void) msg;
|
||||
(void) msg_len;
|
||||
(void) prefix_str;
|
||||
(void) keypair;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** We simulate a failure to create an ESTABLISH_INTRO cell */
|
||||
static void
|
||||
test_gen_establish_intro_cell_bad(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
ssize_t cell_len = 0;
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
hs_service_intro_point_t *ip = NULL;
|
||||
|
||||
MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
|
||||
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
/* Easiest way to make that function fail is to mock the
|
||||
ed25519_sign_prefixed() function and make it fail. */
|
||||
cell = trn_cell_establish_intro_new();
|
||||
tt_assert(cell);
|
||||
ip = service_intro_point_new(NULL, 0);
|
||||
cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL);
|
||||
service_intro_point_free(ip);
|
||||
expect_log_msg_containing("Unable to make signature for "
|
||||
"ESTABLISH_INTRO cell.");
|
||||
teardown_capture_of_logs();
|
||||
tt_i64_op(cell_len, OP_EQ, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(cell);
|
||||
UNMOCK(ed25519_sign_prefixed);
|
||||
}
|
||||
|
||||
struct testcase_t hs_cell_tests[] = {
|
||||
{ "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
507
src/test/test_hs_common.c
Normal file
507
src/test/test_hs_common.c
Normal file
@ -0,0 +1,507 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file test_hs_common.c
|
||||
* \brief Test hidden service common functionalities.
|
||||
*/
|
||||
|
||||
#define HS_COMMON_PRIVATE
|
||||
#define HS_SERVICE_PRIVATE
|
||||
|
||||
#include "test.h"
|
||||
#include "test_helpers.h"
|
||||
#include "log_test_helpers.h"
|
||||
#include "hs_test_helpers.h"
|
||||
|
||||
#include "hs_common.h"
|
||||
#include "hs_service.h"
|
||||
#include "config.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
|
||||
/** Test the validation of HS v3 addresses */
|
||||
static void
|
||||
test_validate_address(void *arg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Address too short and too long. */
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
ret = hs_address_is_valid("blah");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("has an invalid length");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
ret = hs_address_is_valid(
|
||||
"p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("has an invalid length");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Invalid checksum (taken from prop224) */
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
ret = hs_address_is_valid(
|
||||
"l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("invalid checksum");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
ret = hs_address_is_valid(
|
||||
"btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("invalid checksum");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Non base32 decodable string. */
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
ret = hs_address_is_valid(
|
||||
"????????????????????????????????????????????????????????");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("can't be decoded");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Valid address. */
|
||||
ret = hs_address_is_valid(
|
||||
"p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad");
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
static int
|
||||
mock_write_str_to_file(const char *path, const char *str, int bin)
|
||||
{
|
||||
(void)bin;
|
||||
tt_str_op(path, OP_EQ, "/double/five/squared");
|
||||
tt_str_op(str, OP_EQ,
|
||||
"ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid.onion\n");
|
||||
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Test building HS v3 onion addresses */
|
||||
static void
|
||||
test_build_address(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
|
||||
ed25519_public_key_t pubkey;
|
||||
hs_service_t *service = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
MOCK(write_str_to_file, mock_write_str_to_file);
|
||||
|
||||
/* The following has been created with hs_build_address.py script that
|
||||
* follows proposal 224 specification to build an onion address. */
|
||||
static const char *test_addr =
|
||||
"ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid";
|
||||
|
||||
/* Let's try to build the same onion address that the script can do. Key is
|
||||
* a long set of very random \x42 :). */
|
||||
memset(&pubkey, '\x42', sizeof(pubkey));
|
||||
hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr);
|
||||
tt_str_op(test_addr, OP_EQ, onion_addr);
|
||||
/* Validate that address. */
|
||||
ret = hs_address_is_valid(onion_addr);
|
||||
tt_int_op(ret, OP_EQ, 1);
|
||||
|
||||
service = tor_malloc_zero(sizeof(hs_service_t));
|
||||
memcpy(service->onion_address, onion_addr, sizeof(service->onion_address));
|
||||
tor_asprintf(&service->config.directory_path, "/double/five");
|
||||
ret = write_address_to_file(service, "squared");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
done:
|
||||
hs_service_free(service);
|
||||
}
|
||||
|
||||
/** Test that our HS time period calculation functions work properly */
|
||||
static void
|
||||
test_time_period(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
uint64_t tn;
|
||||
int retval;
|
||||
time_t fake_time, correct_time, start_time;
|
||||
|
||||
/* Let's do the example in prop224 section [TIME-PERIODS] */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC",
|
||||
&fake_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Check that the time period number is right */
|
||||
tn = hs_get_time_period_num(fake_time);
|
||||
tt_u64_op(tn, ==, 16903);
|
||||
|
||||
/* Increase current time to 11:59:59 UTC and check that the time period
|
||||
number is still the same */
|
||||
fake_time += 3599;
|
||||
tn = hs_get_time_period_num(fake_time);
|
||||
tt_u64_op(tn, ==, 16903);
|
||||
|
||||
{ /* Check start time of next time period */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC",
|
||||
&correct_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
tt_int_op(start_time, OP_EQ, correct_time);
|
||||
}
|
||||
|
||||
/* Now take time to 12:00:00 UTC and check that the time period rotated */
|
||||
fake_time += 1;
|
||||
tn = hs_get_time_period_num(fake_time);
|
||||
tt_u64_op(tn, ==, 16904);
|
||||
|
||||
/* Now also check our hs_get_next_time_period_num() function */
|
||||
tn = hs_get_next_time_period_num(fake_time);
|
||||
tt_u64_op(tn, ==, 16905);
|
||||
|
||||
{ /* Check start time of next time period again */
|
||||
retval = parse_rfc1123_time("Wed, 14 Apr 2016 12:00:00 UTC",
|
||||
&correct_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
tt_int_op(start_time, OP_EQ, correct_time);
|
||||
}
|
||||
|
||||
/* Now do another sanity check: The time period number at the start of the
|
||||
* next time period, must be the same time period number as the one returned
|
||||
* from hs_get_next_time_period_num() */
|
||||
{
|
||||
time_t next_tp_start = hs_get_start_time_of_next_time_period(fake_time);
|
||||
tt_int_op(hs_get_time_period_num(next_tp_start), OP_EQ,
|
||||
hs_get_next_time_period_num(fake_time));
|
||||
}
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
/** Test that we can correctly find the start time of the next time period */
|
||||
static void
|
||||
test_start_time_of_next_time_period(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
int retval;
|
||||
time_t fake_time;
|
||||
char tbuf[ISO_TIME_LEN + 1];
|
||||
time_t next_tp_start_time;
|
||||
|
||||
/* Do some basic tests */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC",
|
||||
&fake_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, next_tp_start_time);
|
||||
tt_str_op("2016-04-13 12:00:00", OP_EQ, tbuf);
|
||||
|
||||
/* Another test with an edge-case time (start of TP) */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC",
|
||||
&fake_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
format_iso_time(tbuf, next_tp_start_time);
|
||||
tt_str_op("2016-04-14 12:00:00", OP_EQ, tbuf);
|
||||
|
||||
{
|
||||
/* Now pretend we are on a testing network and alter the voting schedule to
|
||||
be every 10 seconds. This means that a time period has length 10*24
|
||||
seconds (4 minutes). It also means that we apply a rotational offset of
|
||||
120 seconds to the time period, so that it starts at 00:02:00 instead of
|
||||
00:00:00. */
|
||||
or_options_t *options = get_options_mutable();
|
||||
options->TestingTorNetwork = 1;
|
||||
options->V3AuthVotingInterval = 10;
|
||||
options->TestingV3AuthInitialVotingInterval = 10;
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
|
||||
&fake_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, next_tp_start_time);
|
||||
tt_str_op("2016-04-13 00:02:00", OP_EQ, tbuf);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC",
|
||||
&fake_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time);
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, next_tp_start_time);
|
||||
tt_str_op("2016-04-13 00:06:00", OP_EQ, tbuf);
|
||||
}
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
/** Test that our HS overlap period functions work properly. */
|
||||
static void
|
||||
test_desc_overlap_period(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
int retval;
|
||||
time_t now = time(NULL);
|
||||
networkstatus_t *dummy_consensus = NULL;
|
||||
|
||||
/* First try with a consensus just inside the overlap period */
|
||||
dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t));
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
/* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap
|
||||
period is still active. */
|
||||
dummy_consensus->valid_after += 3600*11;
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
/* Now increase the valid_after so that it goes to 11:59:59 UTC. Overlap
|
||||
period is still active. */
|
||||
dummy_consensus->valid_after += 3599;
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
/* Now increase the valid_after so that it drifts to noon, and check that
|
||||
overlap mode is not active anymore. */
|
||||
dummy_consensus->valid_after += 1;
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Check that overlap mode is also inactive at 23:59:59 UTC */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 23:59:59 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
done:
|
||||
tor_free(dummy_consensus);
|
||||
}
|
||||
|
||||
/* Test the overlap period functions on a testnet with altered voting
|
||||
* schedule */
|
||||
static void
|
||||
test_desc_overlap_period_testnet(void *arg)
|
||||
{
|
||||
int retval;
|
||||
time_t now = approx_time();
|
||||
networkstatus_t *dummy_consensus = NULL;
|
||||
or_options_t *options = get_options_mutable();
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Set the testnet option and a 10-second voting interval */
|
||||
options->TestingTorNetwork = 1;
|
||||
options->V3AuthVotingInterval = 10;
|
||||
options->TestingV3AuthInitialVotingInterval = 10;
|
||||
|
||||
dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t));
|
||||
|
||||
/* A 10-second voting interval means that the lengths of an SRV run and of a
|
||||
* time period are both 10*24 seconds (4 minutes). The SRV gets published at
|
||||
* 00:00:00 and the TP starts at 00:02:00 (rotation offset: 2 mins). Those
|
||||
* two minutes between SRV publish and TP start is the overlap period
|
||||
* window. Let's test it: */
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:01:59 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:04:00 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:05:59 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 1);
|
||||
|
||||
retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:06:00 UTC",
|
||||
&dummy_consensus->valid_after);
|
||||
tt_int_op(retval, ==, 0);
|
||||
retval = hs_overlap_mode_is_active(dummy_consensus, now);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
done:
|
||||
tor_free(dummy_consensus);
|
||||
}
|
||||
|
||||
static networkstatus_t *mock_ns = NULL;
|
||||
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_latest_consensus(void)
|
||||
{
|
||||
time_t now = approx_time();
|
||||
|
||||
/* If initialized, return it */
|
||||
if (mock_ns) {
|
||||
return mock_ns;
|
||||
}
|
||||
|
||||
/* Initialize fake consensus */
|
||||
mock_ns = tor_malloc_zero(sizeof(networkstatus_t));
|
||||
|
||||
/* This consensus is live */
|
||||
mock_ns->valid_after = now-1;
|
||||
mock_ns->fresh_until = now+1;
|
||||
mock_ns->valid_until = now+2;
|
||||
/* Create routerstatus list */
|
||||
mock_ns->routerstatus_list = smartlist_new();
|
||||
|
||||
return mock_ns;
|
||||
}
|
||||
|
||||
/** Test the responsible HSDirs calculation function */
|
||||
static void
|
||||
test_responsible_hsdirs(void *arg)
|
||||
{
|
||||
time_t now = approx_time();
|
||||
smartlist_t *responsible_dirs = smartlist_new();
|
||||
networkstatus_t *ns = NULL;
|
||||
routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t));
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
|
||||
MOCK(networkstatus_get_latest_consensus,
|
||||
mock_networkstatus_get_latest_consensus);
|
||||
|
||||
ns = networkstatus_get_latest_consensus();
|
||||
|
||||
{ /* First router: HSdir */
|
||||
tor_addr_t ipv4_addr;
|
||||
memset(rs->identity_digest, 'A', DIGEST_LEN);
|
||||
rs->is_hs_dir = 1;
|
||||
rs->supports_v3_hsdir = 1;
|
||||
routerinfo_t ri;
|
||||
memset(&ri, 0 ,sizeof(routerinfo_t));
|
||||
tor_addr_parse(&ipv4_addr, "127.0.0.1");
|
||||
ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
|
||||
ri.nickname = tor_strdup("fatal");
|
||||
ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3";
|
||||
memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN);
|
||||
tt_assert(nodelist_set_routerinfo(&ri, NULL));
|
||||
node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest);
|
||||
memset(node->hsdir_index->current, 'Z',
|
||||
sizeof(node->hsdir_index->current));
|
||||
smartlist_add(ns->routerstatus_list, rs);
|
||||
}
|
||||
|
||||
ed25519_public_key_t blinded_pk;
|
||||
uint64_t time_period_num = hs_get_time_period_num(now);
|
||||
hs_get_responsible_hsdirs(&blinded_pk, time_period_num,
|
||||
0, 0, responsible_dirs);
|
||||
tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1);
|
||||
|
||||
/** TODO: Build a bigger network and do more tests here */
|
||||
|
||||
done:
|
||||
routerstatus_free(rs);
|
||||
smartlist_free(responsible_dirs);
|
||||
smartlist_clear(ns->routerstatus_list);
|
||||
networkstatus_vote_free(mock_ns);
|
||||
}
|
||||
|
||||
/** Test disaster SRV computation and caching */
|
||||
static void
|
||||
test_disaster_srv(void *arg)
|
||||
{
|
||||
uint8_t *cached_disaster_srv_one = NULL;
|
||||
uint8_t *cached_disaster_srv_two = NULL;
|
||||
uint8_t srv_one[DIGEST256_LEN] = {0};
|
||||
uint8_t srv_two[DIGEST256_LEN] = {0};
|
||||
uint8_t srv_three[DIGEST256_LEN] = {0};
|
||||
uint8_t srv_four[DIGEST256_LEN] = {0};
|
||||
uint8_t srv_five[DIGEST256_LEN] = {0};
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Get the cached SRVs: we gonna use them later for verification */
|
||||
cached_disaster_srv_one = get_first_cached_disaster_srv();
|
||||
cached_disaster_srv_two = get_second_cached_disaster_srv();
|
||||
|
||||
/* Compute some srvs */
|
||||
get_disaster_srv(1, srv_one);
|
||||
get_disaster_srv(2, srv_two);
|
||||
|
||||
/* Check that the cached ones where updated */
|
||||
tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN);
|
||||
tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
|
||||
|
||||
/* Ask for an SRV that has already been computed */
|
||||
get_disaster_srv(2, srv_two);
|
||||
/* and check that the cache entries have not changed */
|
||||
tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN);
|
||||
tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
|
||||
|
||||
/* Ask for a new SRV */
|
||||
get_disaster_srv(3, srv_three);
|
||||
tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN);
|
||||
tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN);
|
||||
|
||||
/* Ask for another SRV: none of the original SRVs should now be cached */
|
||||
get_disaster_srv(4, srv_four);
|
||||
tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN);
|
||||
tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN);
|
||||
|
||||
/* Ask for yet another SRV */
|
||||
get_disaster_srv(5, srv_five);
|
||||
tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_five, DIGEST256_LEN);
|
||||
tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN);
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
struct testcase_t hs_common_tests[] = {
|
||||
{ "build_address", test_build_address, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "validate_address", test_validate_address, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "time_period", test_time_period, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "start_time_of_next_time_period", test_start_time_of_next_time_period,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "desc_overlap_period", test_desc_overlap_period, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -453,7 +453,7 @@ test_staging_service_v3(void *arg)
|
||||
/* Ok, we have a service in our map! Registration went well. */
|
||||
tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
|
||||
/* Make sure we don't have a magic v2 service out of this. */
|
||||
tt_int_op(num_rend_services(), OP_EQ, 0);
|
||||
tt_int_op(rend_num_services(), OP_EQ, 0);
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
|
@ -296,6 +296,7 @@ test_decode_descriptor(void *arg)
|
||||
hs_descriptor_t *desc = NULL;
|
||||
hs_descriptor_t *decoded = NULL;
|
||||
hs_descriptor_t *desc_no_ip = NULL;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
|
||||
(void) arg;
|
||||
|
||||
@ -303,15 +304,18 @@ test_decode_descriptor(void *arg)
|
||||
tt_int_op(ret, ==, 0);
|
||||
desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp,
|
||||
subcredential);
|
||||
|
||||
/* Give some bad stuff to the decoding function. */
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded);
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded);
|
||||
tt_int_op(ret, OP_EQ, -1);
|
||||
|
||||
ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
|
||||
tt_int_op(ret, ==, 0);
|
||||
tt_assert(encoded);
|
||||
|
||||
ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
|
||||
tt_int_op(ret, ==, 0);
|
||||
tt_assert(decoded);
|
||||
|
||||
@ -322,6 +326,8 @@ test_decode_descriptor(void *arg)
|
||||
ed25519_keypair_t signing_kp_no_ip;
|
||||
ret = ed25519_keypair_generate(&signing_kp_no_ip, 0);
|
||||
tt_int_op(ret, ==, 0);
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip,
|
||||
subcredential);
|
||||
desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip);
|
||||
tt_assert(desc_no_ip);
|
||||
tor_free(encoded);
|
||||
@ -329,7 +335,7 @@ test_decode_descriptor(void *arg)
|
||||
tt_int_op(ret, ==, 0);
|
||||
tt_assert(encoded);
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded);
|
||||
tt_int_op(ret, ==, 0);
|
||||
tt_assert(decoded);
|
||||
}
|
||||
@ -427,7 +433,7 @@ test_decode_invalid_intro_point(void *arg)
|
||||
const char *junk = "this is not a descriptor";
|
||||
ip = decode_introduction_point(desc, junk);
|
||||
tt_assert(!ip);
|
||||
desc_intro_point_free(ip);
|
||||
hs_desc_intro_point_free(ip);
|
||||
ip = NULL;
|
||||
}
|
||||
|
||||
@ -445,7 +451,7 @@ test_decode_invalid_intro_point(void *arg)
|
||||
tt_assert(!ip);
|
||||
tor_free(encoded_ip);
|
||||
smartlist_free(lines);
|
||||
desc_intro_point_free(ip);
|
||||
hs_desc_intro_point_free(ip);
|
||||
ip = NULL;
|
||||
}
|
||||
|
||||
@ -545,7 +551,7 @@ test_decode_invalid_intro_point(void *arg)
|
||||
|
||||
done:
|
||||
hs_descriptor_free(desc);
|
||||
desc_intro_point_free(ip);
|
||||
hs_desc_intro_point_free(ip);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -17,21 +17,66 @@
|
||||
#include "log_test_helpers.h"
|
||||
|
||||
#include "or.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "ht.h"
|
||||
#include "relay.h"
|
||||
#include "rendservice.h"
|
||||
|
||||
#include "hs_cell.h"
|
||||
#include "hs_circuitmap.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_intropoint.h"
|
||||
#include "hs_service.h"
|
||||
|
||||
/* Trunnel. */
|
||||
#include "hs/cell_establish_intro.h"
|
||||
#include "hs/cell_introduce1.h"
|
||||
#include "hs/cell_common.h"
|
||||
#include "hs_service.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_circuitmap.h"
|
||||
#include "hs_intropoint.h"
|
||||
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "rendservice.h"
|
||||
#include "relay.h"
|
||||
static size_t
|
||||
new_establish_intro_cell(const char *circ_nonce,
|
||||
trn_cell_establish_intro_t **cell_out)
|
||||
{
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t buf[RELAY_PAYLOAD_SIZE] = {0};
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
hs_service_intro_point_t *ip = NULL;
|
||||
|
||||
/* Auth key pair is generated in the constructor so we are all set for
|
||||
* using this IP object. */
|
||||
ip = service_intro_point_new(NULL, 0);
|
||||
tt_assert(ip);
|
||||
cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
|
||||
cell_len = trn_cell_establish_intro_parse(&cell, buf, sizeof(buf));
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
tt_assert(cell);
|
||||
*cell_out = cell;
|
||||
|
||||
done:
|
||||
service_intro_point_free(ip);
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out)
|
||||
{
|
||||
ssize_t cell_len = 0;
|
||||
hs_service_intro_point_t *ip = NULL;
|
||||
|
||||
/* Auth key pair is generated in the constructor so we are all set for
|
||||
* using this IP object. */
|
||||
ip = service_intro_point_new(NULL, 0);
|
||||
tt_assert(ip);
|
||||
cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
|
||||
done:
|
||||
service_intro_point_free(ip);
|
||||
return cell_len;
|
||||
}
|
||||
|
||||
/* Mock function to avoid networking in unittests */
|
||||
static int
|
||||
@ -122,29 +167,24 @@ static void
|
||||
test_establish_intro_wrong_purpose(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
memcpy(intro_circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
memcpy(intro_circ->rend_circ_nonce, circ_nonce, DIGEST_LEN);
|
||||
|
||||
/* Set a bad circuit purpose!! :) */
|
||||
circuit_change_purpose(TO_CIRCUIT(intro_circ), CIRCUIT_PURPOSE_INTRO_POINT);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Receive the cell. Should fail. */
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
@ -154,18 +194,16 @@ test_establish_intro_wrong_purpose(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
/* Prepare a circuit for accepting an ESTABLISH_INTRO cell */
|
||||
static void
|
||||
helper_prepare_circ_for_intro(or_circuit_t *circ,
|
||||
uint8_t *circuit_key_material)
|
||||
helper_prepare_circ_for_intro(or_circuit_t *circ, const char *circ_nonce)
|
||||
{
|
||||
/* Prepare the circuit for the incoming ESTABLISH_INTRO */
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
|
||||
memcpy(circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN);
|
||||
memcpy(circ->rend_circ_nonce, circ_nonce, DIGEST_LEN);
|
||||
}
|
||||
|
||||
/* Send an empty ESTABLISH_INTRO cell. Should fail. */
|
||||
@ -174,17 +212,17 @@ test_establish_intro_wrong_keytype(void *arg)
|
||||
{
|
||||
int retval;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Receive the cell. Should fail. */
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
retval = hs_intro_received_establish_intro(intro_circ, (uint8_t*)"", 0);
|
||||
retval = hs_intro_received_establish_intro(intro_circ, (uint8_t *) "", 0);
|
||||
expect_log_msg_containing("Empty ESTABLISH_INTRO cell.");
|
||||
teardown_capture_of_logs();
|
||||
tt_int_op(retval, ==, -1);
|
||||
@ -198,26 +236,21 @@ static void
|
||||
test_establish_intro_wrong_keytype2(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
* attempt to parse it. */
|
||||
cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Mutate the auth key type! :) */
|
||||
cell_body[0] = 42;
|
||||
@ -230,7 +263,6 @@ test_establish_intro_wrong_keytype2(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
@ -239,26 +271,27 @@ static void
|
||||
test_establish_intro_wrong_mac(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
* attempt to parse it. */
|
||||
cell_len = new_establish_intro_cell(circ_nonce, &cell);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
tt_assert(cell);
|
||||
|
||||
/* Mangle one byte of the MAC. */
|
||||
uint8_t *handshake_ptr =
|
||||
trn_cell_establish_intro_getarray_handshake_mac(establish_intro_cell);
|
||||
trn_cell_establish_intro_getarray_handshake_mac(cell);
|
||||
handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++;
|
||||
/* We need to resign the payload with that change. */
|
||||
{
|
||||
@ -269,26 +302,26 @@ test_establish_intro_wrong_mac(void *arg)
|
||||
retval = ed25519_keypair_generate(&key_struct, 0);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
uint8_t *auth_key_ptr =
|
||||
trn_cell_establish_intro_getarray_auth_key(establish_intro_cell);
|
||||
trn_cell_establish_intro_getarray_auth_key(cell);
|
||||
memcpy(auth_key_ptr, key_struct.pubkey.pubkey, ED25519_PUBKEY_LEN);
|
||||
/* Encode payload so we can sign it. */
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
|
||||
cell);
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
|
||||
retval = ed25519_sign_prefixed(&sig, cell_body,
|
||||
cell_len -
|
||||
(ED25519_SIG_LEN +
|
||||
sizeof(establish_intro_cell->sig_len)),
|
||||
(ED25519_SIG_LEN + sizeof(cell->sig_len)),
|
||||
ESTABLISH_INTRO_SIG_PREFIX, &key_struct);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
/* And write the signature to the cell */
|
||||
uint8_t *sig_ptr =
|
||||
trn_cell_establish_intro_getarray_sig(establish_intro_cell);
|
||||
memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len);
|
||||
trn_cell_establish_intro_getarray_sig(cell);
|
||||
memcpy(sig_ptr, sig.sig, cell->sig_len);
|
||||
/* Re-encode with the new signature. */
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
|
||||
cell);
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
}
|
||||
|
||||
/* Receive the cell. Should fail because our MAC is wrong. */
|
||||
@ -299,7 +332,7 @@ test_establish_intro_wrong_mac(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
trn_cell_establish_intro_free(cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
@ -309,32 +342,32 @@ static void
|
||||
test_establish_intro_wrong_auth_key_len(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
size_t bad_auth_key_len = ED25519_PUBKEY_LEN - 1;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
* attempt to parse it. */
|
||||
cell_len = new_establish_intro_cell(circ_nonce, &cell);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
tt_assert(cell);
|
||||
|
||||
/* Mangle the auth key length. */
|
||||
trn_cell_establish_intro_set_auth_key_len(establish_intro_cell,
|
||||
bad_auth_key_len);
|
||||
trn_cell_establish_intro_setlen_auth_key(establish_intro_cell,
|
||||
bad_auth_key_len);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
trn_cell_establish_intro_set_auth_key_len(cell, bad_auth_key_len);
|
||||
trn_cell_establish_intro_setlen_auth_key(cell, bad_auth_key_len);
|
||||
/* Encode cell. */
|
||||
cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
|
||||
cell);
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Receive the cell. Should fail. */
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
@ -344,7 +377,7 @@ test_establish_intro_wrong_auth_key_len(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
trn_cell_establish_intro_free(cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
@ -354,30 +387,32 @@ static void
|
||||
test_establish_intro_wrong_sig_len(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
size_t bad_sig_len = ED25519_SIG_LEN - 1;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
* attempt to parse it. */
|
||||
cell_len = new_establish_intro_cell(circ_nonce, &cell);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
tt_assert(cell);
|
||||
|
||||
/* Mangle the signature length. */
|
||||
trn_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len);
|
||||
trn_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
trn_cell_establish_intro_set_sig_len(cell, bad_sig_len);
|
||||
trn_cell_establish_intro_setlen_sig(cell, bad_sig_len);
|
||||
/* Encode cell. */
|
||||
cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
|
||||
cell);
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Receive the cell. Should fail. */
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
@ -387,7 +422,7 @@ test_establish_intro_wrong_sig_len(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
trn_cell_establish_intro_free(cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
@ -397,29 +432,24 @@ static void
|
||||
test_establish_intro_wrong_sig(void *arg)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
or_circuit_t *intro_circ = or_circuit_new(0,NULL);;
|
||||
|
||||
(void)arg;
|
||||
(void) arg;
|
||||
|
||||
/* Get the auth key of the intro point */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Mutate the last byte (signature)! :) */
|
||||
cell_body[cell_len-1]++;
|
||||
cell_body[cell_len - 1]++;
|
||||
|
||||
/* Receive the cell. Should fail. */
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
@ -429,7 +459,6 @@ test_establish_intro_wrong_sig(void *arg)
|
||||
tt_int_op(retval, ==, -1);
|
||||
|
||||
done:
|
||||
trn_cell_establish_intro_free(establish_intro_cell);
|
||||
circuit_free(TO_CIRCUIT(intro_circ));
|
||||
}
|
||||
|
||||
@ -439,32 +468,32 @@ static trn_cell_establish_intro_t *
|
||||
helper_establish_intro_v3(or_circuit_t *intro_circ)
|
||||
{
|
||||
int retval;
|
||||
trn_cell_establish_intro_t *establish_intro_cell = NULL;
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
trn_cell_establish_intro_t *cell = NULL;
|
||||
|
||||
tt_assert(intro_circ);
|
||||
|
||||
/* Prepare the circuit for the incoming ESTABLISH_INTRO */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
|
||||
attempt to parse it. */
|
||||
establish_intro_cell = generate_establish_intro_cell(circuit_key_material,
|
||||
sizeof(circuit_key_material));
|
||||
tt_assert(establish_intro_cell);
|
||||
cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
|
||||
establish_intro_cell);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
* attempt to parse it. */
|
||||
cell_len = new_establish_intro_cell(circ_nonce, &cell);
|
||||
tt_u64_op(cell_len, OP_GT, 0);
|
||||
tt_assert(cell);
|
||||
cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body),
|
||||
cell);
|
||||
tt_int_op(cell_len, OP_GT, 0);
|
||||
|
||||
/* Receive the cell */
|
||||
retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
done:
|
||||
return establish_intro_cell;
|
||||
return cell;
|
||||
}
|
||||
|
||||
/* Helper function: Send a well-formed v2 ESTABLISH_INTRO cell to
|
||||
@ -476,22 +505,22 @@ helper_establish_intro_v2(or_circuit_t *intro_circ)
|
||||
int retval;
|
||||
uint8_t cell_body[RELAY_PAYLOAD_SIZE];
|
||||
ssize_t cell_len = 0;
|
||||
uint8_t circuit_key_material[DIGEST_LEN] = {0};
|
||||
char circ_nonce[DIGEST_LEN] = {0};
|
||||
|
||||
tt_assert(intro_circ);
|
||||
|
||||
/* Prepare the circuit for the incoming ESTABLISH_INTRO */
|
||||
crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
|
||||
helper_prepare_circ_for_intro(intro_circ, circuit_key_material);
|
||||
crypto_rand(circ_nonce, sizeof(circ_nonce));
|
||||
helper_prepare_circ_for_intro(intro_circ, circ_nonce);
|
||||
|
||||
/* Send legacy establish_intro */
|
||||
key1 = pk_generate(0);
|
||||
|
||||
/* Use old circuit_key_material why not */
|
||||
cell_len = encode_establish_intro_cell_legacy((char*)cell_body,
|
||||
sizeof(cell_body),
|
||||
key1,
|
||||
(char *) circuit_key_material);
|
||||
/* Use old circ_nonce why not */
|
||||
cell_len = rend_service_encode_establish_intro_cell(
|
||||
(char*)cell_body,
|
||||
sizeof(cell_body), key1,
|
||||
circ_nonce);
|
||||
tt_int_op(cell_len, >, 0);
|
||||
|
||||
/* Receive legacy establish_intro */
|
||||
|
114
src/test/test_hs_ntor.c
Normal file
114
src/test/test_hs_ntor.c
Normal file
@ -0,0 +1,114 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file test_hs_ntor.c
|
||||
* \brief Test hidden service ntor functionality.
|
||||
*/
|
||||
|
||||
#include "test.h"
|
||||
#include "test_helpers.h"
|
||||
#include "log_test_helpers.h"
|
||||
|
||||
#include "hs_ntor.h"
|
||||
|
||||
/* Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1
|
||||
* cell, and verify the proper derivation of decryption keys on the other end.
|
||||
* Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify
|
||||
* the proper verification on the other end. */
|
||||
static void
|
||||
test_hs_ntor(void *arg)
|
||||
{
|
||||
int retval;
|
||||
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
|
||||
ed25519_keypair_t service_intro_auth_keypair;
|
||||
curve25519_keypair_t service_intro_enc_keypair;
|
||||
curve25519_keypair_t service_ephemeral_rend_keypair;
|
||||
|
||||
curve25519_keypair_t client_ephemeral_enc_keypair;
|
||||
|
||||
hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys;
|
||||
hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys;
|
||||
|
||||
hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys;
|
||||
hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Generate fake data for this unittest */
|
||||
{
|
||||
/* Generate fake subcredential */
|
||||
memset(subcredential, 'Z', DIGEST256_LEN);
|
||||
|
||||
/* service */
|
||||
curve25519_keypair_generate(&service_intro_enc_keypair, 0);
|
||||
ed25519_keypair_generate(&service_intro_auth_keypair, 0);
|
||||
curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0);
|
||||
/* client */
|
||||
curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0);
|
||||
}
|
||||
|
||||
/* Client: Simulate the sending of an encrypted INTRODUCE1 cell */
|
||||
retval =
|
||||
hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&service_intro_enc_keypair.pubkey,
|
||||
&client_ephemeral_enc_keypair,
|
||||
subcredential,
|
||||
&client_hs_ntor_intro_cell_keys);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Service: Simulate the decryption of the received INTRODUCE1 */
|
||||
retval =
|
||||
hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&service_intro_enc_keypair,
|
||||
&client_ephemeral_enc_keypair.pubkey,
|
||||
subcredential,
|
||||
&service_hs_ntor_intro_cell_keys);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Test that the INTRODUCE1 encryption/mac keys match! */
|
||||
tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ,
|
||||
service_hs_ntor_intro_cell_keys.enc_key,
|
||||
CIPHER256_KEY_LEN);
|
||||
tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ,
|
||||
service_hs_ntor_intro_cell_keys.mac_key,
|
||||
DIGEST256_LEN);
|
||||
|
||||
/* Service: Simulate creation of RENDEZVOUS1 key material. */
|
||||
retval =
|
||||
hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&service_intro_enc_keypair,
|
||||
&service_ephemeral_rend_keypair,
|
||||
&client_ephemeral_enc_keypair.pubkey,
|
||||
&service_hs_ntor_rend_cell_keys);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Client: Simulate the verification of a received RENDEZVOUS1 cell */
|
||||
retval =
|
||||
hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&client_ephemeral_enc_keypair,
|
||||
&service_intro_enc_keypair.pubkey,
|
||||
&service_ephemeral_rend_keypair.pubkey,
|
||||
&client_hs_ntor_rend_cell_keys);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Test that the RENDEZVOUS1 key material match! */
|
||||
tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ,
|
||||
service_hs_ntor_rend_cell_keys.rend_cell_auth_mac,
|
||||
DIGEST256_LEN);
|
||||
tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ,
|
||||
service_hs_ntor_rend_cell_keys.ntor_key_seed,
|
||||
DIGEST256_LEN);
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
struct testcase_t hs_ntor_tests[] = {
|
||||
{ "hs_ntor", test_hs_ntor, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -189,6 +189,120 @@ test_get_state_valid_until_time(void *arg)
|
||||
;
|
||||
}
|
||||
|
||||
/** Test the function that calculates the start time of the current SRV
|
||||
* protocol run. */
|
||||
static void
|
||||
test_get_start_time_of_current_run(void *arg)
|
||||
{
|
||||
int retval;
|
||||
char tbuf[ISO_TIME_LEN + 1];
|
||||
time_t current_time, run_start_time;
|
||||
|
||||
(void) arg;
|
||||
|
||||
{
|
||||
/* Get start time if called at 00:00:01 */
|
||||
retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC",
|
||||
¤t_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
run_start_time =
|
||||
sr_state_get_start_time_of_current_protocol_run(current_time);
|
||||
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, run_start_time);
|
||||
tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
|
||||
}
|
||||
|
||||
{
|
||||
retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:59 UTC",
|
||||
¤t_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
run_start_time =
|
||||
sr_state_get_start_time_of_current_protocol_run(current_time);
|
||||
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, run_start_time);
|
||||
tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
|
||||
}
|
||||
|
||||
{
|
||||
retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC",
|
||||
¤t_time);
|
||||
tt_int_op(retval, ==, 0);
|
||||
run_start_time =
|
||||
sr_state_get_start_time_of_current_protocol_run(current_time);
|
||||
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, run_start_time);
|
||||
tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf);
|
||||
}
|
||||
|
||||
/* Now let's alter the voting schedule and check the correctness of the
|
||||
* function. Voting interval of 10 seconds, means that an SRV protocol run
|
||||
* takes 10 seconds * 24 rounds = 4 mins */
|
||||
{
|
||||
or_options_t *options = get_options_mutable();
|
||||
options->V3AuthVotingInterval = 10;
|
||||
options->TestingV3AuthInitialVotingInterval = 10;
|
||||
retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:15:32 UTC",
|
||||
¤t_time);
|
||||
|
||||
tt_int_op(retval, ==, 0);
|
||||
run_start_time =
|
||||
sr_state_get_start_time_of_current_protocol_run(current_time);
|
||||
|
||||
/* Compare it with the correct result */
|
||||
format_iso_time(tbuf, run_start_time);
|
||||
tt_str_op("2015-04-20 00:12:00", OP_EQ, tbuf);
|
||||
}
|
||||
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
/** Do some rudimentary consistency checks between the functions that
|
||||
* understand the shared random protocol schedule */
|
||||
static void
|
||||
test_get_start_time_functions(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
time_t now = approx_time();
|
||||
|
||||
time_t start_time_of_protocol_run =
|
||||
sr_state_get_start_time_of_current_protocol_run(now);
|
||||
tt_assert(start_time_of_protocol_run);
|
||||
|
||||
/* Check that the round start time of the beginning of the run, is itself */
|
||||
tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run), OP_EQ,
|
||||
start_time_of_protocol_run);
|
||||
|
||||
/* Check that even if we increment the start time, we still get the start
|
||||
time of the run as the beginning of the round. */
|
||||
tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run+1),
|
||||
OP_EQ, start_time_of_protocol_run);
|
||||
|
||||
done: ;
|
||||
}
|
||||
|
||||
static void
|
||||
test_get_sr_protocol_duration(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
/* Check that by default an SR phase is 12 hours */
|
||||
tt_int_op(sr_state_get_phase_duration(), ==, 12*60*60);
|
||||
tt_int_op(sr_state_get_protocol_run_duration(), ==, 24*60*60);
|
||||
|
||||
/* Now alter the voting interval and check that the SR phase is 2 mins long
|
||||
* if voting happens every 10 seconds (10*12 seconds = 2 mins) */
|
||||
or_options_t *options = get_options_mutable();
|
||||
options->V3AuthVotingInterval = 10;
|
||||
tt_int_op(sr_state_get_phase_duration(), ==, 2*60);
|
||||
tt_int_op(sr_state_get_protocol_run_duration(), ==, 4*60);
|
||||
|
||||
done: ;
|
||||
}
|
||||
|
||||
/* Mock function to immediately return our local 'mock_consensus'. */
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_live_consensus(time_t now)
|
||||
@ -1272,6 +1386,12 @@ struct testcase_t sr_tests[] = {
|
||||
NULL, NULL },
|
||||
{ "get_next_valid_after_time", test_get_next_valid_after_time, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "get_start_time_of_current_run", test_get_start_time_of_current_run,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "get_start_time_functions", test_get_start_time_functions,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "get_sr_protocol_duration", test_get_sr_protocol_duration, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "vote", test_vote, TT_FORK,
|
||||
|
@ -59,6 +59,10 @@ struct link_specifier_st {
|
||||
};
|
||||
#endif
|
||||
typedef struct link_specifier_st link_specifier_t;
|
||||
/** XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by
|
||||
* taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE we would
|
||||
* need to refactor that function to do the coyp by encoding and decoding the
|
||||
* object. */
|
||||
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT)
|
||||
struct ed25519_cert_st {
|
||||
uint8_t version;
|
||||
|
@ -28,6 +28,12 @@ const LS_IPV6 = 0x01;
|
||||
const LS_LEGACY_ID = 0x02;
|
||||
const LS_ED25519_ID = 0x03;
|
||||
|
||||
// XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by
|
||||
// taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE, or
|
||||
// if we ever make link_specifier contain other types, we will
|
||||
// need to refactor that function to do the copy by encoding and decoding the
|
||||
// object.
|
||||
|
||||
// amended from tor.trunnel
|
||||
struct link_specifier {
|
||||
u8 ls_type;
|
||||
|
292
src/trunnel/hs/cell_rendezvous.c
Normal file
292
src/trunnel/hs/cell_rendezvous.c
Normal file
@ -0,0 +1,292 @@
|
||||
/* cell_rendezvous.c -- generated by Trunnel v1.5.1.
|
||||
* https://gitweb.torproject.org/trunnel.git
|
||||
* You probably shouldn't edit this file.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "trunnel-impl.h"
|
||||
|
||||
#include "cell_rendezvous.h"
|
||||
|
||||
#define TRUNNEL_SET_ERROR_CODE(obj) \
|
||||
do { \
|
||||
(obj)->trunnel_error_code_ = 1; \
|
||||
} while (0)
|
||||
|
||||
#if defined(__COVERITY__) || defined(__clang_analyzer__)
|
||||
/* If we're runnning a static analysis tool, we don't want it to complain
|
||||
* that some of our remaining-bytes checks are dead-code. */
|
||||
int cellrendezvous_deadcode_dummy__ = 0;
|
||||
#define OR_DEADCODE_DUMMY || cellrendezvous_deadcode_dummy__
|
||||
#else
|
||||
#define OR_DEADCODE_DUMMY
|
||||
#endif
|
||||
|
||||
#define CHECK_REMAINING(nbytes, label) \
|
||||
do { \
|
||||
if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \
|
||||
goto label; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
trn_cell_rendezvous1_t *
|
||||
trn_cell_rendezvous1_new(void)
|
||||
{
|
||||
trn_cell_rendezvous1_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous1_t));
|
||||
if (NULL == val)
|
||||
return NULL;
|
||||
return val;
|
||||
}
|
||||
|
||||
/** Release all storage held inside 'obj', but do not free 'obj'.
|
||||
*/
|
||||
static void
|
||||
trn_cell_rendezvous1_clear(trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
(void) obj;
|
||||
TRUNNEL_DYNARRAY_WIPE(&obj->handshake_info);
|
||||
TRUNNEL_DYNARRAY_CLEAR(&obj->handshake_info);
|
||||
}
|
||||
|
||||
void
|
||||
trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
if (obj == NULL)
|
||||
return;
|
||||
trn_cell_rendezvous1_clear(obj);
|
||||
trunnel_memwipe(obj, sizeof(trn_cell_rendezvous1_t));
|
||||
trunnel_free_(obj);
|
||||
}
|
||||
|
||||
size_t
|
||||
trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
(void)inp; return TRUNNEL_REND_COOKIE_LEN;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx)
|
||||
{
|
||||
trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
|
||||
return inp->rendezvous_cookie[idx];
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx)
|
||||
{
|
||||
return trn_cell_rendezvous1_get_rendezvous_cookie((trn_cell_rendezvous1_t*)inp, idx);
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt)
|
||||
{
|
||||
trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
|
||||
inp->rendezvous_cookie[idx] = elt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
return inp->rendezvous_cookie;
|
||||
}
|
||||
const uint8_t *
|
||||
trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
return (const uint8_t *)trn_cell_rendezvous1_getarray_rendezvous_cookie((trn_cell_rendezvous1_t*)inp);
|
||||
}
|
||||
size_t
|
||||
trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
return TRUNNEL_DYNARRAY_LEN(&inp->handshake_info);
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx)
|
||||
{
|
||||
return TRUNNEL_DYNARRAY_GET(&inp->handshake_info, idx);
|
||||
}
|
||||
|
||||
uint8_t
|
||||
trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx)
|
||||
{
|
||||
return trn_cell_rendezvous1_get_handshake_info((trn_cell_rendezvous1_t*)inp, idx);
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt)
|
||||
{
|
||||
TRUNNEL_DYNARRAY_SET(&inp->handshake_info, idx, elt);
|
||||
return 0;
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt)
|
||||
{
|
||||
TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->handshake_info, elt, {});
|
||||
return 0;
|
||||
trunnel_alloc_failed:
|
||||
TRUNNEL_SET_ERROR_CODE(inp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
return inp->handshake_info.elts_;
|
||||
}
|
||||
const uint8_t *
|
||||
trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp)
|
||||
{
|
||||
return (const uint8_t *)trn_cell_rendezvous1_getarray_handshake_info((trn_cell_rendezvous1_t*)inp);
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen)
|
||||
{
|
||||
uint8_t *newptr;
|
||||
newptr = trunnel_dynarray_setlen(&inp->handshake_info.allocated_,
|
||||
&inp->handshake_info.n_, inp->handshake_info.elts_, newlen,
|
||||
sizeof(inp->handshake_info.elts_[0]), (trunnel_free_fn_t) NULL,
|
||||
&inp->trunnel_error_code_);
|
||||
if (newlen != 0 && newptr == NULL)
|
||||
goto trunnel_alloc_failed;
|
||||
inp->handshake_info.elts_ = newptr;
|
||||
return 0;
|
||||
trunnel_alloc_failed:
|
||||
TRUNNEL_SET_ERROR_CODE(inp);
|
||||
return -1;
|
||||
}
|
||||
const char *
|
||||
trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
if (obj == NULL)
|
||||
return "Object was NULL";
|
||||
if (obj->trunnel_error_code_)
|
||||
return "A set function failed on this object";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
|
||||
if (NULL != trn_cell_rendezvous1_check(obj))
|
||||
return -1;
|
||||
|
||||
|
||||
/* Length of u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
|
||||
result += TRUNNEL_REND_COOKIE_LEN;
|
||||
|
||||
/* Length of u8 handshake_info[] */
|
||||
result += TRUNNEL_DYNARRAY_LEN(&obj->handshake_info);
|
||||
return result;
|
||||
}
|
||||
int
|
||||
trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
int r = obj->trunnel_error_code_;
|
||||
obj->trunnel_error_code_ = 0;
|
||||
return r;
|
||||
}
|
||||
ssize_t
|
||||
trn_cell_rendezvous1_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous1_t *obj)
|
||||
{
|
||||
ssize_t result = 0;
|
||||
size_t written = 0;
|
||||
uint8_t *ptr = output;
|
||||
const char *msg;
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
const ssize_t encoded_len = trn_cell_rendezvous1_encoded_len(obj);
|
||||
#endif
|
||||
|
||||
if (NULL != (msg = trn_cell_rendezvous1_check(obj)))
|
||||
goto check_failed;
|
||||
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
trunnel_assert(encoded_len >= 0);
|
||||
#endif
|
||||
|
||||
/* Encode u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
|
||||
trunnel_assert(written <= avail);
|
||||
if (avail - written < TRUNNEL_REND_COOKIE_LEN)
|
||||
goto truncated;
|
||||
memcpy(ptr, obj->rendezvous_cookie, TRUNNEL_REND_COOKIE_LEN);
|
||||
written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
|
||||
|
||||
/* Encode u8 handshake_info[] */
|
||||
{
|
||||
size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->handshake_info);
|
||||
trunnel_assert(written <= avail);
|
||||
if (avail - written < elt_len)
|
||||
goto truncated;
|
||||
if (elt_len)
|
||||
memcpy(ptr, obj->handshake_info.elts_, elt_len);
|
||||
written += elt_len; ptr += elt_len;
|
||||
}
|
||||
|
||||
|
||||
trunnel_assert(ptr == output + written);
|
||||
#ifdef TRUNNEL_CHECK_ENCODED_LEN
|
||||
{
|
||||
trunnel_assert(encoded_len >= 0);
|
||||
trunnel_assert((size_t)encoded_len == written);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return written;
|
||||
|
||||
truncated:
|
||||
result = -2;
|
||||
goto fail;
|
||||
check_failed:
|
||||
(void)msg;
|
||||
result = -1;
|
||||
goto fail;
|
||||
fail:
|
||||
trunnel_assert(result < 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** As trn_cell_rendezvous1_parse(), but do not allocate the output
|
||||
* object.
|
||||
*/
|
||||
static ssize_t
|
||||
trn_cell_rendezvous1_parse_into(trn_cell_rendezvous1_t *obj, const uint8_t *input, const size_t len_in)
|
||||
{
|
||||
const uint8_t *ptr = input;
|
||||
size_t remaining = len_in;
|
||||
ssize_t result = 0;
|
||||
(void)result;
|
||||
|
||||
/* Parse u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */
|
||||
CHECK_REMAINING(TRUNNEL_REND_COOKIE_LEN, truncated);
|
||||
memcpy(obj->rendezvous_cookie, ptr, TRUNNEL_REND_COOKIE_LEN);
|
||||
remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
|
||||
|
||||
/* Parse u8 handshake_info[] */
|
||||
TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->handshake_info, remaining, {});
|
||||
obj->handshake_info.n_ = remaining;
|
||||
if (remaining)
|
||||
memcpy(obj->handshake_info.elts_, ptr, remaining);
|
||||
ptr += remaining; remaining -= remaining;
|
||||
trunnel_assert(ptr + remaining == input + len_in);
|
||||
return len_in - remaining;
|
||||
|
||||
truncated:
|
||||
return -2;
|
||||
trunnel_alloc_failed:
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in)
|
||||
{
|
||||
ssize_t result;
|
||||
*output = trn_cell_rendezvous1_new();
|
||||
if (NULL == *output)
|
||||
return -1;
|
||||
result = trn_cell_rendezvous1_parse_into(*output, input, len_in);
|
||||
if (result < 0) {
|
||||
trn_cell_rendezvous1_free(*output);
|
||||
*output = NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
118
src/trunnel/hs/cell_rendezvous.h
Normal file
118
src/trunnel/hs/cell_rendezvous.h
Normal file
@ -0,0 +1,118 @@
|
||||
/* cell_rendezvous.h -- generated by by Trunnel v1.5.1.
|
||||
* https://gitweb.torproject.org/trunnel.git
|
||||
* You probably shouldn't edit this file.
|
||||
*/
|
||||
#ifndef TRUNNEL_CELL_RENDEZVOUS_H
|
||||
#define TRUNNEL_CELL_RENDEZVOUS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "trunnel.h"
|
||||
|
||||
#define TRUNNEL_REND_COOKIE_LEN 20
|
||||
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1)
|
||||
struct trn_cell_rendezvous1_st {
|
||||
uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
|
||||
TRUNNEL_DYNARRAY_HEAD(, uint8_t) handshake_info;
|
||||
uint8_t trunnel_error_code_;
|
||||
};
|
||||
#endif
|
||||
typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t;
|
||||
/** Return a newly allocated trn_cell_rendezvous1 with all elements
|
||||
* set to zero.
|
||||
*/
|
||||
trn_cell_rendezvous1_t *trn_cell_rendezvous1_new(void);
|
||||
/** Release all storage held by the trn_cell_rendezvous1 in 'victim'.
|
||||
* (Do nothing if 'victim' is NULL.)
|
||||
*/
|
||||
void trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *victim);
|
||||
/** Try to parse a trn_cell_rendezvous1 from the buffer in 'input',
|
||||
* using up to 'len_in' bytes from the input buffer. On success,
|
||||
* return the number of bytes consumed and set *output to the newly
|
||||
* allocated trn_cell_rendezvous1_t. On failure, return -2 if the
|
||||
* input appears truncated, and -1 if the input is otherwise invalid.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in);
|
||||
/** Return the number of bytes we expect to need to encode the
|
||||
* trn_cell_rendezvous1 in 'obj'. On failure, return a negative value.
|
||||
* Note that this value may be an overestimate, and can even be an
|
||||
* underestimate for certain unencodeable objects.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj);
|
||||
/** Try to encode the trn_cell_rendezvous1 from 'input' into the
|
||||
* buffer at 'output', using up to 'avail' bytes of the output buffer.
|
||||
* On success, return the number of bytes used. On failure, return -2
|
||||
* if the buffer was not long enough, and -1 if the input was invalid.
|
||||
*/
|
||||
ssize_t trn_cell_rendezvous1_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous1_t *input);
|
||||
/** Check whether the internal state of the trn_cell_rendezvous1 in
|
||||
* 'obj' is consistent. Return NULL if it is, and a short message if
|
||||
* it is not.
|
||||
*/
|
||||
const char *trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj);
|
||||
/** Clear any errors that were set on the object 'obj' by its setter
|
||||
* functions. Return true iff errors were cleared.
|
||||
*/
|
||||
int trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj);
|
||||
/** Return the (constant) length of the array holding the
|
||||
* rendezvous_cookie field of the trn_cell_rendezvous1_t in 'inp'.
|
||||
*/
|
||||
size_t trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp);
|
||||
/** Return the element at position 'idx' of the fixed array field
|
||||
* rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp'.
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx);
|
||||
/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return
|
||||
* a const pointer
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx);
|
||||
/** Change the element at position 'idx' of the fixed array field
|
||||
* rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp', so that
|
||||
* it will hold the value 'elt'.
|
||||
*/
|
||||
int trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt);
|
||||
/** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array
|
||||
* field rendezvous_cookie of 'inp'.
|
||||
*/
|
||||
uint8_t * trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp);
|
||||
/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return
|
||||
* a const pointer
|
||||
*/
|
||||
const uint8_t * trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp);
|
||||
/** Return the length of the dynamic array holding the handshake_info
|
||||
* field of the trn_cell_rendezvous1_t in 'inp'.
|
||||
*/
|
||||
size_t trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp);
|
||||
/** Return the element at position 'idx' of the dynamic array field
|
||||
* handshake_info of the trn_cell_rendezvous1_t in 'inp'.
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx);
|
||||
/** As trn_cell_rendezvous1_get_handshake_info, but take and return a
|
||||
* const pointer
|
||||
*/
|
||||
uint8_t trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx);
|
||||
/** Change the element at position 'idx' of the dynamic array field
|
||||
* handshake_info of the trn_cell_rendezvous1_t in 'inp', so that it
|
||||
* will hold the value 'elt'.
|
||||
*/
|
||||
int trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt);
|
||||
/** Append a new element 'elt' to the dynamic array field
|
||||
* handshake_info of the trn_cell_rendezvous1_t in 'inp'.
|
||||
*/
|
||||
int trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt);
|
||||
/** Return a pointer to the variable-length array field handshake_info
|
||||
* of 'inp'.
|
||||
*/
|
||||
uint8_t * trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp);
|
||||
/** As trn_cell_rendezvous1_get_handshake_info, but take and return a
|
||||
* const pointer
|
||||
*/
|
||||
const uint8_t * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp);
|
||||
/** Change the length of the variable-length array field
|
||||
* handshake_info of 'inp' to 'newlen'.Fill extra elements with 0.
|
||||
* Return 0 on success; return -1 and set the error code on 'inp' on
|
||||
* failure.
|
||||
*/
|
||||
int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen);
|
||||
|
||||
|
||||
#endif
|
18
src/trunnel/hs/cell_rendezvous.trunnel
Normal file
18
src/trunnel/hs/cell_rendezvous.trunnel
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* This contains the definition of the RENDEZVOUS1 cell for onion service
|
||||
* version 3 and onward. The following format is specified in proposal 224
|
||||
* section 4.2.
|
||||
*/
|
||||
|
||||
/* Rendezvous cookie length. */
|
||||
const TRUNNEL_REND_COOKIE_LEN = 20;
|
||||
|
||||
/* RENDEZVOUS1 payload. See details in section 4.2. */
|
||||
struct trn_cell_rendezvous1 {
|
||||
/* The RENDEZVOUS_COOKIE field. */
|
||||
u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN];
|
||||
|
||||
/* The HANDSHAKE_INFO field which has a variable length depending on the
|
||||
* handshake type used. */
|
||||
u8 handshake_info[];
|
||||
};
|
@ -22,6 +22,7 @@ TRUNNELSOURCES = \
|
||||
src/trunnel/hs/cell_common.c \
|
||||
src/trunnel/hs/cell_establish_intro.c \
|
||||
src/trunnel/hs/cell_introduce1.c \
|
||||
src/trunnel/hs/cell_rendezvous.c \
|
||||
src/trunnel/channelpadding_negotiation.c
|
||||
|
||||
TRUNNELHEADERS = \
|
||||
@ -34,6 +35,7 @@ TRUNNELHEADERS = \
|
||||
src/trunnel/hs/cell_common.h \
|
||||
src/trunnel/hs/cell_establish_intro.h \
|
||||
src/trunnel/hs/cell_introduce1.h \
|
||||
src/trunnel/hs/cell_rendezvous.h \
|
||||
src/trunnel/channelpadding_negotiation.h
|
||||
|
||||
src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES)
|
||||
|
Loading…
Reference in New Issue
Block a user