Merge branch 'ticket20657_nickm_bugfixes_squashed'

This commit is contained in:
Nick Mathewson 2017-08-08 20:31:57 -04:00
commit 34e4122025
63 changed files with 8475 additions and 1028 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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
View 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 */

View File

@ -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,
&ocirc->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

View File

@ -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. */

View File

@ -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();
}
}

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

@ -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));
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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, ""),

View File

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

View File

@ -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",
};
/**

View File

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

View File

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

View File

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

View File

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

View File

@ -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[];

View File

@ -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
View 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
View 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
};

View File

@ -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();

View File

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

View File

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

View File

@ -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",
&current_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",
&current_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",
&current_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",
&current_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,

View File

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

View File

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

View 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;
}

View 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

View 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[];
};

View File

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