mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-11 05:33:47 +01:00
Merge branch 'ticket21859_032_01_squashed'
This commit is contained in:
commit
c387cc5022
@ -1854,6 +1854,18 @@ struct crypto_digest_t {
|
||||
} d;
|
||||
};
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
digest_algorithm_t
|
||||
crypto_digest_get_algorithm(crypto_digest_t *digest)
|
||||
{
|
||||
tor_assert(digest);
|
||||
|
||||
return digest->algorithm;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return the number of bytes we need to malloc in order to get a
|
||||
* crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe
|
||||
|
@ -20,6 +20,9 @@
|
||||
#include "testsupport.h"
|
||||
#include "compat.h"
|
||||
|
||||
#include <openssl/engine.h>
|
||||
#include "keccak-tiny/keccak-tiny.h"
|
||||
|
||||
/*
|
||||
Macro to create an arbitrary OpenSSL version number as used by
|
||||
OPENSSL_VERSION_NUMBER or SSLeay(), since the actual numbers are a bit hard
|
||||
@ -335,6 +338,7 @@ struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh);
|
||||
void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in);
|
||||
|
||||
#ifdef CRYPTO_PRIVATE
|
||||
|
||||
STATIC int crypto_force_rand_ssleay(void);
|
||||
STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len);
|
||||
|
||||
@ -346,6 +350,7 @@ extern int break_strongest_rng_fallback;
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src);
|
||||
digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "crypto.h"
|
||||
#include "directory.h"
|
||||
#include "entrynodes.h"
|
||||
#include "hs_ntor.h"
|
||||
#include "main.h"
|
||||
#include "microdesc.h"
|
||||
#include "networkstatus.h"
|
||||
@ -1370,40 +1371,76 @@ circuit_extend(cell_t *cell, circuit_t *circ)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in
|
||||
* key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are
|
||||
* used as follows:
|
||||
/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data.
|
||||
*
|
||||
* If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
|
||||
* service circuits and <b>key_data</b> must be at least
|
||||
* HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
|
||||
*
|
||||
* If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
|
||||
* bytes, which are used as follows:
|
||||
* - 20 to initialize f_digest
|
||||
* - 20 to initialize b_digest
|
||||
* - 16 to key f_crypto
|
||||
* - 16 to key b_crypto
|
||||
*
|
||||
* (If 'reverse' is true, then f_XX and b_XX are swapped.)
|
||||
*
|
||||
* Return 0 if init was successful, else -1 if it failed.
|
||||
*/
|
||||
int
|
||||
circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
|
||||
int reverse)
|
||||
circuit_init_cpath_crypto(crypt_path_t *cpath,
|
||||
const char *key_data, size_t key_data_len,
|
||||
int reverse, int is_hs_v3)
|
||||
{
|
||||
crypto_digest_t *tmp_digest;
|
||||
crypto_cipher_t *tmp_crypto;
|
||||
size_t digest_len = 0;
|
||||
size_t cipher_key_len = 0;
|
||||
|
||||
tor_assert(cpath);
|
||||
tor_assert(key_data);
|
||||
tor_assert(!(cpath->f_crypto || cpath->b_crypto ||
|
||||
cpath->f_digest || cpath->b_digest));
|
||||
|
||||
cpath->f_digest = crypto_digest_new();
|
||||
crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN);
|
||||
cpath->b_digest = crypto_digest_new();
|
||||
crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN);
|
||||
/* Basic key size validation */
|
||||
if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
|
||||
return -1;
|
||||
} else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(cpath->f_crypto =
|
||||
crypto_cipher_new(key_data+(2*DIGEST_LEN)))) {
|
||||
/* If we are using this cpath for next gen onion services use SHA3-256,
|
||||
otherwise use good ol' SHA1 */
|
||||
if (is_hs_v3) {
|
||||
digest_len = DIGEST256_LEN;
|
||||
cipher_key_len = CIPHER256_KEY_LEN;
|
||||
cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256);
|
||||
} else {
|
||||
digest_len = DIGEST_LEN;
|
||||
cipher_key_len = CIPHER_KEY_LEN;
|
||||
cpath->f_digest = crypto_digest_new();
|
||||
cpath->b_digest = crypto_digest_new();
|
||||
}
|
||||
|
||||
tor_assert(digest_len != 0);
|
||||
tor_assert(cipher_key_len != 0);
|
||||
|
||||
crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len);
|
||||
crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len);
|
||||
|
||||
cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len),
|
||||
cipher_key_len*8);
|
||||
if (!cpath->f_crypto) {
|
||||
log_warn(LD_BUG,"Forward cipher initialization failed.");
|
||||
return -1;
|
||||
}
|
||||
if (!(cpath->b_crypto =
|
||||
crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) {
|
||||
|
||||
cpath->b_crypto = crypto_cipher_new_with_bits(
|
||||
key_data+(2*digest_len)+cipher_key_len,
|
||||
cipher_key_len*8);
|
||||
if (!cpath->b_crypto) {
|
||||
log_warn(LD_BUG,"Backward cipher initialization failed.");
|
||||
return -1;
|
||||
}
|
||||
@ -1469,7 +1506,7 @@ circuit_finish_handshake(origin_circuit_t *circ,
|
||||
|
||||
onion_handshake_state_release(&hop->handshake_state);
|
||||
|
||||
if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
|
||||
if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) {
|
||||
return -END_CIRC_REASON_TORPROTOCOL;
|
||||
}
|
||||
|
||||
@ -1536,12 +1573,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason)
|
||||
int
|
||||
onionskin_answer(or_circuit_t *circ,
|
||||
const created_cell_t *created_cell,
|
||||
const char *keys,
|
||||
const char *keys, size_t keys_len,
|
||||
const uint8_t *rend_circ_nonce)
|
||||
{
|
||||
cell_t cell;
|
||||
crypt_path_t *tmp_cpath;
|
||||
|
||||
tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN);
|
||||
|
||||
if (created_cell_format(&cell, created_cell) < 0) {
|
||||
log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)",
|
||||
(int)created_cell->cell_type, (int)created_cell->handshake_len);
|
||||
@ -1557,7 +1596,7 @@ onionskin_answer(or_circuit_t *circ,
|
||||
log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
|
||||
(unsigned int)get_uint32(keys),
|
||||
(unsigned int)get_uint32(keys+20));
|
||||
if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
|
||||
if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) {
|
||||
log_warn(LD_BUG,"Circuit initialization failed");
|
||||
tor_free(tmp_cpath);
|
||||
return -1;
|
||||
@ -2357,6 +2396,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TOR_UNIT_TESTS
|
||||
|
||||
/** Unittest helper function: Count number of hops in cpath linked list. */
|
||||
unsigned int
|
||||
cpath_get_n_hops(crypt_path_t **head_ptr)
|
||||
{
|
||||
unsigned int n_hops = 0;
|
||||
crypt_path_t *tmp;
|
||||
|
||||
if (!*head_ptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = *head_ptr;
|
||||
if (tmp) {
|
||||
n_hops++;
|
||||
tmp = (*head_ptr)->next;
|
||||
}
|
||||
|
||||
return n_hops;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
|
||||
* and <b>state</b> and the cpath <b>head</b> (currently populated only
|
||||
* to length <b>cur_len</b> to decide a suitable middle hop for a
|
||||
|
@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ);
|
||||
int circuit_send_next_onion_skin(origin_circuit_t *circ);
|
||||
void circuit_note_clock_jumped(int seconds_elapsed);
|
||||
int circuit_extend(cell_t *cell, circuit_t *circ);
|
||||
int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
|
||||
int reverse);
|
||||
int circuit_init_cpath_crypto(crypt_path_t *cpath,
|
||||
const char *key_data, size_t key_data_len,
|
||||
int reverse, int is_hs_v3);
|
||||
struct created_cell_t;
|
||||
int circuit_finish_handshake(origin_circuit_t *circ,
|
||||
const struct created_cell_t *created_cell);
|
||||
@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer,
|
||||
int reason);
|
||||
int onionskin_answer(or_circuit_t *circ,
|
||||
const struct created_cell_t *created_cell,
|
||||
const char *keys,
|
||||
const char *keys, size_t keys_len,
|
||||
const uint8_t *rend_circ_nonce);
|
||||
MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
|
||||
int *need_uptime,
|
||||
@ -83,6 +84,8 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes));
|
||||
#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS)
|
||||
STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags,
|
||||
const or_options_t *options);
|
||||
unsigned int cpath_get_n_hops(crypt_path_t **head_ptr);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -67,6 +67,7 @@
|
||||
#include "main.h"
|
||||
#include "hs_circuitmap.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_ident.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
#include "onion.h"
|
||||
@ -957,6 +958,7 @@ circuit_free(circuit_t *circ)
|
||||
|
||||
crypto_pk_free(ocirc->intro_key);
|
||||
rend_data_free(ocirc->rend_data);
|
||||
hs_ident_circuit_free(ocirc->hs_ident);
|
||||
|
||||
tor_free(ocirc->dest_address);
|
||||
if (ocirc->socks_username) {
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "control.h"
|
||||
#include "entrynodes.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_client.h"
|
||||
#include "hs_ident.h"
|
||||
#include "nodelist.h"
|
||||
#include "networkstatus.h"
|
||||
#include "policies.h"
|
||||
@ -55,6 +57,36 @@
|
||||
static void circuit_expire_old_circuits_clientside(void);
|
||||
static void circuit_increment_failure_count(void);
|
||||
|
||||
/** Check whether the hidden service destination of the stream at
|
||||
* <b>edge_conn</b> is the same as the destination of the circuit at
|
||||
* <b>origin_circ</b>. */
|
||||
static int
|
||||
circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
|
||||
const origin_circuit_t *origin_circ)
|
||||
{
|
||||
/* Check if this is a v2 rendezvous circ/stream */
|
||||
if ((edge_conn->rend_data && !origin_circ->rend_data) ||
|
||||
(!edge_conn->rend_data && origin_circ->rend_data) ||
|
||||
(edge_conn->rend_data && origin_circ->rend_data &&
|
||||
rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
|
||||
rend_data_get_address(origin_circ->rend_data)))) {
|
||||
/* this circ is not for this conn */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if this is a v3 rendezvous circ/stream */
|
||||
if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
|
||||
(!edge_conn->hs_ident && origin_circ->hs_ident) ||
|
||||
(edge_conn->hs_ident && origin_circ->hs_ident &&
|
||||
!ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
|
||||
&origin_circ->hs_ident->identity_pk))) {
|
||||
/* this circ is not for this conn */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Return 1 if <b>circ</b> could be returned by circuit_get_best().
|
||||
* Else return 0.
|
||||
*/
|
||||
@ -169,14 +201,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
|
||||
/* can't exit from this router */
|
||||
return 0;
|
||||
}
|
||||
} else { /* not general */
|
||||
} else { /* not general: this might be a rend circuit */
|
||||
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
|
||||
if ((edge_conn->rend_data && !origin_circ->rend_data) ||
|
||||
(!edge_conn->rend_data && origin_circ->rend_data) ||
|
||||
(edge_conn->rend_data && origin_circ->rend_data &&
|
||||
rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
|
||||
rend_data_get_address(origin_circ->rend_data)))) {
|
||||
/* this circ is not for this conn */
|
||||
if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -2348,8 +2375,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
|
||||
/* We are attaching a stream to a rendezvous circuit. That means
|
||||
* that an attempt to connect to a hidden service just
|
||||
* succeeded. Tell rendclient.c. */
|
||||
rend_client_note_connection_attempt_ended(
|
||||
ENTRY_TO_EDGE_CONN(apconn)->rend_data);
|
||||
hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
|
||||
}
|
||||
|
||||
if (cpath) { /* we were given one; use it */
|
||||
|
@ -381,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
|
||||
created_cell.handshake_len = len;
|
||||
|
||||
if (onionskin_answer(circ, &created_cell,
|
||||
(const char *)keys, rend_circ_nonce)<0) {
|
||||
(const char *)keys, sizeof(keys),
|
||||
rend_circ_nonce)<0) {
|
||||
log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing.");
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
|
||||
return;
|
||||
|
@ -84,6 +84,7 @@
|
||||
#include "geoip.h"
|
||||
#include "main.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_ident.h"
|
||||
#include "nodelist.h"
|
||||
#include "policies.h"
|
||||
#include "reasons.h"
|
||||
@ -605,6 +606,7 @@ connection_free_(connection_t *conn)
|
||||
}
|
||||
if (CONN_IS_EDGE(conn)) {
|
||||
rend_data_free(TO_EDGE_CONN(conn)->rend_data);
|
||||
hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident);
|
||||
}
|
||||
if (conn->type == CONN_TYPE_CONTROL) {
|
||||
control_connection_t *control_conn = TO_CONTROL_CONN(conn);
|
||||
@ -636,6 +638,7 @@ connection_free_(connection_t *conn)
|
||||
}
|
||||
|
||||
rend_data_free(dir_conn->rend_data);
|
||||
hs_ident_dir_conn_free(dir_conn->hs_ident);
|
||||
if (dir_conn->guard_state) {
|
||||
/* Cancel before freeing, if it's still there. */
|
||||
entry_guard_cancel(&dir_conn->guard_state);
|
||||
|
@ -2455,8 +2455,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn)
|
||||
*
|
||||
* If ap_conn is broken, mark it for close and return -1. Else return 0.
|
||||
*/
|
||||
int
|
||||
connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
|
||||
MOCK_IMPL(int,
|
||||
connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
|
||||
{
|
||||
char payload[CELL_PAYLOAD_SIZE];
|
||||
int payload_len;
|
||||
@ -3566,8 +3566,12 @@ int
|
||||
connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
|
||||
{
|
||||
tor_assert(conn);
|
||||
if (conn->rend_data)
|
||||
/* It should not be possible to set both of these structs */
|
||||
tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident));
|
||||
|
||||
if (conn->rend_data || conn->hs_ident) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn);
|
||||
void connection_ap_about_to_close(entry_connection_t *edge_conn);
|
||||
void connection_exit_about_to_close(edge_connection_t *edge_conn);
|
||||
|
||||
int connection_ap_handshake_send_begin(entry_connection_t *ap_conn);
|
||||
MOCK_DECL(int,
|
||||
connection_ap_handshake_send_begin,(entry_connection_t *ap_conn));
|
||||
int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn);
|
||||
|
||||
entry_connection_t *connection_ap_make_link(connection_t *partner,
|
||||
|
@ -372,7 +372,7 @@ cpuworker_onion_handshake_replyfn(void *work_)
|
||||
|
||||
if (onionskin_answer(circ,
|
||||
&rpl.created_cell,
|
||||
(const char*)rpl.keys,
|
||||
(const char*)rpl.keys, sizeof(rpl.keys),
|
||||
rpl.rend_auth_material) < 0) {
|
||||
log_warn(LD_OR,"onionskin_answer failed. Closing.");
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
|
||||
|
224
src/or/hs_circuit.c
Normal file
224
src/or/hs_circuit.c
Normal file
@ -0,0 +1,224 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_circuit.c
|
||||
**/
|
||||
|
||||
#include "or.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "config.h"
|
||||
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_ntor.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
|
||||
* circuit purpose is properly set, otherwise return false. */
|
||||
static int
|
||||
circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
|
||||
int is_service_side)
|
||||
{
|
||||
if (is_service_side) {
|
||||
if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
|
||||
log_warn(LD_BUG,
|
||||
"HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_service_side) {
|
||||
if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
|
||||
circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
|
||||
log_warn(LD_BUG,
|
||||
"Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
|
||||
* circuit. Initialize the crypt path crypto using the output material from the
|
||||
* ntor key exchange at <b>ntor_key_seed</b>.
|
||||
*
|
||||
* If <b>is_service_side</b> is set, we are the hidden service and the final
|
||||
* hop of the rendezvous circuit is the client on the other side. */
|
||||
static crypt_path_t *
|
||||
create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
|
||||
int is_service_side)
|
||||
{
|
||||
uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
|
||||
crypt_path_t *cpath = NULL;
|
||||
|
||||
/* Do the key expansion */
|
||||
if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
|
||||
keys, sizeof(keys)) < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Setup the cpath */
|
||||
cpath = tor_malloc_zero(sizeof(crypt_path_t));
|
||||
cpath->magic = CRYPT_PATH_MAGIC;
|
||||
|
||||
if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
|
||||
is_service_side, 1) < 0) {
|
||||
tor_free(cpath);
|
||||
goto err;
|
||||
}
|
||||
|
||||
err:
|
||||
memwipe(keys, 0, sizeof(keys));
|
||||
return cpath;
|
||||
}
|
||||
|
||||
/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
|
||||
* service on the other side of the rendezvous circuit <b>circ</b>. Initialize
|
||||
* the crypt path crypto using the body of the RENDEZVOUS1 cell at
|
||||
* <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes).
|
||||
*/
|
||||
static crypt_path_t *
|
||||
create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
|
||||
{
|
||||
crypt_path_t *hop = NULL;
|
||||
char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
|
||||
|
||||
/* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
|
||||
* handshake...*/
|
||||
tor_assert(circ->build_state);
|
||||
tor_assert(circ->build_state->pending_final_cpath);
|
||||
hop = circ->build_state->pending_final_cpath;
|
||||
|
||||
tor_assert(hop->rend_dh_handshake_state);
|
||||
if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
|
||||
(char*)rend_cell_body, DH_KEY_LEN,
|
||||
keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
|
||||
log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
|
||||
goto err;
|
||||
}
|
||||
/* ... and set up cpath. */
|
||||
if (circuit_init_cpath_crypto(hop,
|
||||
keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
|
||||
0, 0) < 0)
|
||||
goto err;
|
||||
|
||||
/* Check whether the digest is right... */
|
||||
if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) {
|
||||
log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* clean up the crypto stuff we just made */
|
||||
crypto_dh_free(hop->rend_dh_handshake_state);
|
||||
hop->rend_dh_handshake_state = NULL;
|
||||
|
||||
goto done;
|
||||
|
||||
err:
|
||||
hop = NULL;
|
||||
|
||||
done:
|
||||
memwipe(keys, 0, sizeof(keys));
|
||||
return hop;
|
||||
}
|
||||
|
||||
/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
|
||||
* <b>circ</b> ready for use to transfer HS relay cells. */
|
||||
static void
|
||||
finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
|
||||
int is_service_side)
|
||||
{
|
||||
tor_assert(circ);
|
||||
tor_assert(hop);
|
||||
|
||||
/* Notify the circuit state machine that we are splicing this circuit */
|
||||
int new_circ_purpose = is_service_side ?
|
||||
CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
|
||||
|
||||
/* All is well. Extend the circuit. */
|
||||
hop->state = CPATH_STATE_OPEN;
|
||||
/* Set the windows to default. */
|
||||
hop->package_window = circuit_initial_package_window();
|
||||
hop->deliver_window = CIRCWINDOW_START;
|
||||
|
||||
/* Now that this circuit has finished connecting to its destination,
|
||||
* make sure circuit_get_open_circ_or_launch is willing to return it
|
||||
* so we can actually use it. */
|
||||
circ->hs_circ_has_timed_out = 0;
|
||||
|
||||
/* Append the hop to the cpath of this circuit */
|
||||
onion_append_to_cpath(&circ->cpath, hop);
|
||||
|
||||
/* In legacy code, 'pending_final_cpath' points to the final hop we just
|
||||
* appended to the cpath. We set the original pointer to NULL so that we
|
||||
* don't double free it. */
|
||||
if (circ->build_state) {
|
||||
circ->build_state->pending_final_cpath = NULL;
|
||||
}
|
||||
|
||||
/* Finally, mark circuit as ready to be used for client streams */
|
||||
if (!is_service_side) {
|
||||
circuit_try_attaching_streams(circ);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
* service. If <b>is_service_side</b> is set, then we are the hidden service
|
||||
* and the other side is the client.
|
||||
*
|
||||
* Return 0 if the operation went well; in case of error return -1. */
|
||||
int
|
||||
hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
|
||||
const uint8_t *ntor_key_seed, size_t seed_len,
|
||||
int is_service_side)
|
||||
{
|
||||
if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
|
||||
is_service_side))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
|
||||
is_service_side);
|
||||
if (!hop) {
|
||||
log_warn(LD_REND, "Couldn't get v3 %s cpath!",
|
||||
is_service_side ? "service-side" : "client-side");
|
||||
return -1;
|
||||
}
|
||||
|
||||
finalize_rend_circuit(circ, hop, is_service_side);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
|
||||
* <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
|
||||
* extend the crypt path of <b>circ</b> so that the hidden service is on the
|
||||
* other side. */
|
||||
int
|
||||
hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
|
||||
const uint8_t *rend_cell_body)
|
||||
{
|
||||
|
||||
if (BUG(!circuit_purpose_is_correct_for_rend(
|
||||
TO_CIRCUIT(circ)->purpose, 0))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
|
||||
if (!hop) {
|
||||
log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
finalize_rend_circuit(circ, hop, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
24
src/or/hs_circuit.h
Normal file
24
src/or/hs_circuit.h
Normal file
@ -0,0 +1,24 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_circuit.h
|
||||
* \brief Header file containing circuit data for the whole HS subsytem.
|
||||
**/
|
||||
|
||||
#ifndef TOR_HS_CIRCUIT_H
|
||||
#define TOR_HS_CIRCUIT_H
|
||||
|
||||
#include "or.h"
|
||||
|
||||
/* e2e circuit API. */
|
||||
|
||||
int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ,
|
||||
const uint8_t *ntor_key_seed,
|
||||
size_t seed_len,
|
||||
int is_service_side);
|
||||
int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
|
||||
const uint8_t *rend_cell_body);
|
||||
|
||||
#endif /* TOR_HS_CIRCUIT_H */
|
||||
|
47
src/or/hs_client.c
Normal file
47
src/or/hs_client.c
Normal file
@ -0,0 +1,47 @@
|
||||
/* Copyright (c) 2016-2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_service.c
|
||||
* \brief Implement next generation hidden service client functionality
|
||||
**/
|
||||
|
||||
#include "or.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "connection_edge.h"
|
||||
#include "rendclient.h"
|
||||
|
||||
#include "hs_client.h"
|
||||
|
||||
/** A prop224 v3 HS circuit successfully connected to the hidden
|
||||
* service. Update the stream state at <b>hs_conn_ident</b> appropriately. */
|
||||
static void
|
||||
hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
|
||||
{
|
||||
(void) hs_conn_ident;
|
||||
|
||||
/* TODO: When implementing client side */
|
||||
return;
|
||||
}
|
||||
|
||||
/** A circuit just finished connecting to a hidden service that the stream
|
||||
* <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
|
||||
void
|
||||
hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
|
||||
{
|
||||
tor_assert(connection_edge_is_rendezvous_stream(conn));
|
||||
|
||||
if (BUG(conn->rend_data && conn->hs_ident)) {
|
||||
log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
|
||||
"Prioritizing hs_ident");
|
||||
}
|
||||
|
||||
if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
|
||||
hs_client_attempt_succeeded(conn->hs_ident);
|
||||
return;
|
||||
} else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
|
||||
rend_client_note_connection_attempt_ended(conn->rend_data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
16
src/or/hs_client.h
Normal file
16
src/or/hs_client.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_client.h
|
||||
* \brief Header file containing client data for the HS subsytem.
|
||||
**/
|
||||
|
||||
#ifndef TOR_HS_CLIENT_H
|
||||
#define TOR_HS_CLIENT_H
|
||||
|
||||
void hs_client_note_connection_attempt_succeeded(
|
||||
const edge_connection_t *conn);
|
||||
|
||||
#endif /* TOR_HS_CLIENT_H */
|
||||
|
@ -49,6 +49,12 @@
|
||||
/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */
|
||||
#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */
|
||||
|
||||
/* 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;
|
||||
|
||||
int hs_check_service_private_dir(const char *username, const char *path,
|
||||
unsigned int dir_group_readable,
|
||||
unsigned int create);
|
||||
|
81
src/or/hs_ident.c
Normal file
81
src/or/hs_ident.c
Normal file
@ -0,0 +1,81 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_ident.c
|
||||
* \brief Contains circuit and connection identifier code for the whole HS
|
||||
* subsytem.
|
||||
**/
|
||||
|
||||
#include "hs_ident.h"
|
||||
|
||||
/* Return a newly allocated circuit identifier. The given public key is copied
|
||||
* identity_pk into the identifier. */
|
||||
hs_ident_circuit_t *
|
||||
hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
|
||||
hs_ident_circuit_type_t circuit_type)
|
||||
{
|
||||
tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
|
||||
circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
|
||||
hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
|
||||
ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
|
||||
ident->circuit_type = circuit_type;
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* Free the given circuit identifier. */
|
||||
void
|
||||
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 directory connection identifier src, return a newly allocated
|
||||
* copy of it. This can't fail. */
|
||||
hs_ident_dir_conn_t *
|
||||
hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
|
||||
{
|
||||
hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
|
||||
memcpy(ident, src, sizeof(*ident));
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* Free the given directory connection identifier. */
|
||||
void
|
||||
hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident)
|
||||
{
|
||||
if (ident == NULL) {
|
||||
return;
|
||||
}
|
||||
memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
|
||||
tor_free(ident);
|
||||
}
|
||||
|
||||
/* Return a newly allocated edge connection identifier. The given public key
|
||||
* identity_pk is copied into the identifier. */
|
||||
hs_ident_edge_conn_t *
|
||||
hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
|
||||
{
|
||||
hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
|
||||
ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
|
||||
return ident;
|
||||
}
|
||||
|
||||
/* Free the given edge connection identifier. */
|
||||
void
|
||||
hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
|
||||
{
|
||||
if (ident == NULL) {
|
||||
return;
|
||||
}
|
||||
memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
|
||||
tor_free(ident);
|
||||
}
|
||||
|
124
src/or/hs_ident.h
Normal file
124
src/or/hs_ident.h
Normal file
@ -0,0 +1,124 @@
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_ident.h
|
||||
* \brief Header file containing circuit and connection identifier data for
|
||||
* the whole HS subsytem.
|
||||
*
|
||||
* \details
|
||||
* This interface is used to uniquely identify a hidden service on a circuit
|
||||
* or connection using the service identity public key. Once the circuit or
|
||||
* connection subsystem calls in the hidden service one, we use those
|
||||
* identifiers to lookup the corresponding objects like service, intro point
|
||||
* and descriptor.
|
||||
*
|
||||
* Furthermore, the circuit identifier holds cryptographic material needed for
|
||||
* the e2e encryption on the rendezvous circuit which is set once the
|
||||
* rendezvous circuit has opened and ready to be used.
|
||||
**/
|
||||
|
||||
#ifndef TOR_HS_IDENT_H
|
||||
#define TOR_HS_IDENT_H
|
||||
|
||||
#include "crypto.h"
|
||||
#include "crypto_ed25519.h"
|
||||
|
||||
#include "hs_common.h"
|
||||
|
||||
/* Length of the rendezvous cookie that is used to connect circuits at the
|
||||
* rendezvous point. */
|
||||
#define HS_REND_COOKIE_LEN DIGEST_LEN
|
||||
|
||||
/* Type of circuit an hs_ident_t object is associated with. */
|
||||
typedef enum {
|
||||
HS_IDENT_CIRCUIT_INTRO = 1,
|
||||
HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
|
||||
} hs_ident_circuit_type_t;
|
||||
|
||||
/* Client and service side circuit identifier that is used for hidden service
|
||||
* circuit establishment. Not all fields contain data, it depends on the
|
||||
* circuit purpose. This is attached to an origin_circuit_t. All fields are
|
||||
* used by both client and service. */
|
||||
typedef struct hs_ident_circuit_t {
|
||||
/* (All circuit) The public key used to uniquely identify the service. It is
|
||||
* the one found in the onion address. */
|
||||
ed25519_public_key_t identity_pk;
|
||||
|
||||
/* (All circuit) The type of circuit this identifier is attached to.
|
||||
* Accessors of the fields in this object assert non fatal on this circuit
|
||||
* type. In other words, if a rendezvous field is being accessed, the
|
||||
* circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
|
||||
* 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;
|
||||
|
||||
/* (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 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:
|
||||
* SERVER_PK [32 bytes]
|
||||
* AUTH_MAC [32 bytes]
|
||||
*/
|
||||
uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN];
|
||||
|
||||
/* (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];
|
||||
|
||||
/* (Only rendezvous circuit) Number of streams associated with this
|
||||
* rendezvous circuit. We track this because there is a check on a maximum
|
||||
* value. */
|
||||
uint64_t num_rdv_streams;
|
||||
} hs_ident_circuit_t;
|
||||
|
||||
/* Client and service side directory connection identifier used for a
|
||||
* directory connection to identify which service is being queried. This is
|
||||
* attached to a dir_connection_t. */
|
||||
typedef struct hs_ident_dir_conn_t {
|
||||
/* The public key used to uniquely identify the service. It is the one found
|
||||
* in the onion address. */
|
||||
ed25519_public_key_t identity_pk;
|
||||
|
||||
/* XXX: Client authorization. */
|
||||
} hs_ident_dir_conn_t;
|
||||
|
||||
/* Client and service side edge connection identifier used for an edge
|
||||
* connection to identify which service is being queried. This is attached to
|
||||
* a edge_connection_t. */
|
||||
typedef struct hs_ident_edge_conn_t {
|
||||
/* The public key used to uniquely identify the service. It is the one found
|
||||
* in the onion address. */
|
||||
ed25519_public_key_t identity_pk;
|
||||
|
||||
/* XXX: Client authorization. */
|
||||
} hs_ident_edge_conn_t;
|
||||
|
||||
/* Circuit identifier API. */
|
||||
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);
|
||||
|
||||
/* Directory connection identifier API. */
|
||||
hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src);
|
||||
void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident);
|
||||
|
||||
/* Edge connection identifier API. */
|
||||
hs_ident_edge_conn_t *hs_ident_edge_conn_new(
|
||||
const ed25519_public_key_t *identity_pk);
|
||||
void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
|
||||
|
||||
#endif /* TOR_HS_IDENT_H */
|
||||
|
@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good(
|
||||
|
||||
/* Input length to KDF for key expansion */
|
||||
#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN)
|
||||
/* Output length of KDF for key expansion */
|
||||
#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2)
|
||||
|
||||
/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the
|
||||
* circuit key expansion as specified by section '4.2.1. Key expansion' and
|
||||
* return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */
|
||||
hs_ntor_rend_circuit_keys_t *
|
||||
hs_ntor_circuit_key_expansion(
|
||||
const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys)
|
||||
/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size
|
||||
* DIGEST256_LEN), do the circuit key expansion as specified by section
|
||||
* '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be
|
||||
* of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN).
|
||||
*
|
||||
* Return 0 if things went well, else return -1. */
|
||||
int
|
||||
hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len,
|
||||
uint8_t *keys_out, size_t keys_out_len)
|
||||
{
|
||||
uint8_t *ptr;
|
||||
uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN];
|
||||
uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN];
|
||||
crypto_xof_t *xof;
|
||||
hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL;
|
||||
|
||||
/* Sanity checks on lengths to make sure we are good */
|
||||
if (BUG(seed_len != DIGEST256_LEN)) {
|
||||
return -1;
|
||||
}
|
||||
if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Let's build the input to the KDF */
|
||||
ptr = kdf_input;
|
||||
APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN);
|
||||
APPEND(ptr, ntor_key_seed, DIGEST256_LEN);
|
||||
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
|
||||
tor_assert(ptr == kdf_input + sizeof(kdf_input));
|
||||
|
||||
/* Generate the keys */
|
||||
xof = crypto_xof_new();
|
||||
crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
|
||||
crypto_xof_squeeze_bytes(xof, keys, sizeof(keys));
|
||||
crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN);
|
||||
crypto_xof_free(xof);
|
||||
|
||||
/* Generate keys structure and assign keys to it */
|
||||
rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t));
|
||||
ptr = keys;
|
||||
memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN);
|
||||
ptr += DIGEST256_LEN;;
|
||||
memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN);
|
||||
ptr += DIGEST256_LEN;
|
||||
memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN);
|
||||
ptr += DIGEST256_LEN;
|
||||
memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN);
|
||||
ptr += CIPHER256_KEY_LEN;
|
||||
memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN);
|
||||
ptr += CIPHER256_KEY_LEN;
|
||||
tor_assert(ptr == keys + sizeof(keys));
|
||||
|
||||
return rend_circuit_keys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
#include "or.h"
|
||||
|
||||
/* Output length of KDF for key expansion */
|
||||
#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \
|
||||
(DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2)
|
||||
|
||||
/* Key material needed to encode/decode INTRODUCE1 cells */
|
||||
typedef struct {
|
||||
/* Key used for encryption of encrypted INTRODUCE1 blob */
|
||||
@ -23,21 +27,6 @@ typedef struct {
|
||||
uint8_t ntor_key_seed[DIGEST256_LEN];
|
||||
} hs_ntor_rend_cell_keys_t;
|
||||
|
||||
/* Key material resulting from key expansion as detailed in section "4.2.1. Key
|
||||
* expansion" of rend-spec-ng.txt. */
|
||||
typedef struct {
|
||||
/* Per-circuit key material used in ESTABLISH_INTRO cell */
|
||||
uint8_t KH[DIGEST256_LEN];
|
||||
/* Authentication key for outgoing RELAY cells */
|
||||
uint8_t Df[DIGEST256_LEN];
|
||||
/* Authentication key for incoming RELAY cells */
|
||||
uint8_t Db[DIGEST256_LEN];
|
||||
/* Encryption key for outgoing RELAY cells */
|
||||
uint8_t Kf[CIPHER256_KEY_LEN];
|
||||
/* Decryption key for incoming RELAY cells */
|
||||
uint8_t Kb[CIPHER256_KEY_LEN];
|
||||
} hs_ntor_rend_circuit_keys_t;
|
||||
|
||||
int hs_ntor_client_get_introduce1_keys(
|
||||
const ed25519_public_key_t *intro_auth_pubkey,
|
||||
const curve25519_public_key_t *intro_enc_pubkey,
|
||||
@ -66,8 +55,9 @@ int hs_ntor_service_get_rendezvous1_keys(
|
||||
const curve25519_public_key_t *client_ephemeral_enc_pubkey,
|
||||
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
|
||||
|
||||
hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion(
|
||||
const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys);
|
||||
int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed,
|
||||
size_t seed_len,
|
||||
uint8_t *keys_out, size_t keys_out_len);
|
||||
|
||||
int hs_ntor_client_rendezvous2_mac_is_good(
|
||||
const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,
|
||||
|
@ -50,16 +50,19 @@ LIBTOR_A_SOURCES = \
|
||||
src/or/dnsserv.c \
|
||||
src/or/fp_pair.c \
|
||||
src/or/geoip.c \
|
||||
src/or/hs_intropoint.c \
|
||||
src/or/hs_circuitmap.c \
|
||||
src/or/hs_ntor.c \
|
||||
src/or/hs_service.c \
|
||||
src/or/hs_cache.c \
|
||||
src/or/hs_circuitmap.c \
|
||||
src/or/hs_common.c \
|
||||
src/or/hs_circuit.c \
|
||||
src/or/hs_descriptor.c \
|
||||
src/or/hs_ident.c \
|
||||
src/or/hs_intropoint.c \
|
||||
src/or/hs_ntor.c \
|
||||
src/or/hs_service.c \
|
||||
src/or/hs_client.c \
|
||||
src/or/entrynodes.c \
|
||||
src/or/ext_orport.c \
|
||||
src/or/hibernate.c \
|
||||
src/or/hs_cache.c \
|
||||
src/or/hs_common.c \
|
||||
src/or/hs_descriptor.c \
|
||||
src/or/keypin.c \
|
||||
src/or/main.c \
|
||||
src/or/microdesc.c \
|
||||
@ -181,11 +184,14 @@ ORHEADERS = \
|
||||
src/or/hibernate.h \
|
||||
src/or/hs_cache.h \
|
||||
src/or/hs_common.h \
|
||||
src/or/hs_circuit.h \
|
||||
src/or/hs_descriptor.h \
|
||||
src/or/hs_ident.h \
|
||||
src/or/hs_intropoint.h \
|
||||
src/or/hs_circuitmap.h \
|
||||
src/or/hs_ntor.h \
|
||||
src/or/hs_service.h \
|
||||
src/or/hs_client.h \
|
||||
src/or/keypin.h \
|
||||
src/or/main.h \
|
||||
src/or/microdesc.h \
|
||||
|
19
src/or/or.h
19
src/or/or.h
@ -846,6 +846,11 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
|
||||
return DOWNCAST(rend_data_v2_t, d);
|
||||
}
|
||||
|
||||
/* Stub because we can't include hs_ident.h. */
|
||||
typedef struct hs_ident_edge_conn_t hs_ident_edge_conn_t;
|
||||
typedef struct hs_ident_dir_conn_t hs_ident_dir_conn_t;
|
||||
typedef struct hs_ident_circuit_t hs_ident_circuit_t;
|
||||
|
||||
/** Time interval for tracking replays of DH public keys received in
|
||||
* INTRODUCE2 cells. Used only to avoid launching multiple
|
||||
* simultaneous attempts to connect to the same rendezvous point. */
|
||||
@ -1633,6 +1638,11 @@ typedef struct edge_connection_t {
|
||||
* an exit)? */
|
||||
rend_data_t *rend_data;
|
||||
|
||||
/* Hidden service connection identifier for edge connections. Used by the HS
|
||||
* client-side code to identify client SOCKS connections and by the
|
||||
* service-side code to match HS circuits with their streams. */
|
||||
hs_ident_edge_conn_t *hs_ident;
|
||||
|
||||
uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit
|
||||
* connection. Exit connections only. */
|
||||
uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell
|
||||
@ -1783,6 +1793,11 @@ typedef struct dir_connection_t {
|
||||
/** What rendezvous service are we querying for? */
|
||||
rend_data_t *rend_data;
|
||||
|
||||
/* Hidden service connection identifier for dir connections: Used by HS
|
||||
client-side code to fetch HS descriptors, and by the service-side code to
|
||||
upload descriptors. */
|
||||
hs_ident_dir_conn_t *hs_ident;
|
||||
|
||||
/** If this is a one-hop connection, tracks the state of the directory guard
|
||||
* for this connection (if any). */
|
||||
struct circuit_guard_state_t *guard_state;
|
||||
@ -3186,6 +3201,10 @@ typedef struct origin_circuit_t {
|
||||
/** Holds all rendezvous data on either client or service side. */
|
||||
rend_data_t *rend_data;
|
||||
|
||||
/** Holds hidden service identifier on either client or service side. This
|
||||
* is for both introduction and rendezvous circuit. */
|
||||
hs_ident_circuit_t *hs_ident;
|
||||
|
||||
/** Holds the data that the entry guard system uses to track the
|
||||
* status of the guard this circuit is using, and thereby to determine
|
||||
* whether this circuit can be used. */
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "connection_edge.h"
|
||||
#include "directory.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "main.h"
|
||||
#include "networkstatus.h"
|
||||
#include "nodelist.h"
|
||||
@ -1150,9 +1151,6 @@ int
|
||||
rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
|
||||
size_t request_len)
|
||||
{
|
||||
crypt_path_t *hop;
|
||||
char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
|
||||
|
||||
if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
|
||||
circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
|
||||
|| !circ->build_state->pending_final_cpath) {
|
||||
@ -1170,55 +1168,13 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
|
||||
|
||||
log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
|
||||
|
||||
/* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
|
||||
* handshake...*/
|
||||
tor_assert(circ->build_state);
|
||||
tor_assert(circ->build_state->pending_final_cpath);
|
||||
hop = circ->build_state->pending_final_cpath;
|
||||
tor_assert(hop->rend_dh_handshake_state);
|
||||
if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
|
||||
hop->rend_dh_handshake_state, (char*)request,
|
||||
DH_KEY_LEN,
|
||||
keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
|
||||
log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
|
||||
if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
|
||||
log_warn(LD_GENERAL, "Failed to setup circ");
|
||||
goto err;
|
||||
}
|
||||
/* ... and set up cpath. */
|
||||
if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0)
|
||||
goto err;
|
||||
|
||||
/* Check whether the digest is right... */
|
||||
if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) {
|
||||
log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
crypto_dh_free(hop->rend_dh_handshake_state);
|
||||
hop->rend_dh_handshake_state = NULL;
|
||||
|
||||
/* All is well. Extend the circuit. */
|
||||
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED);
|
||||
hop->state = CPATH_STATE_OPEN;
|
||||
/* set the windows to default. these are the windows
|
||||
* that the client thinks the service has.
|
||||
*/
|
||||
hop->package_window = circuit_initial_package_window();
|
||||
hop->deliver_window = CIRCWINDOW_START;
|
||||
|
||||
/* Now that this circuit has finished connecting to its destination,
|
||||
* make sure circuit_get_open_circ_or_launch is willing to return it
|
||||
* so we can actually use it. */
|
||||
circ->hs_circ_has_timed_out = 0;
|
||||
|
||||
onion_append_to_cpath(&circ->cpath, hop);
|
||||
circ->build_state->pending_final_cpath = NULL; /* prevent double-free */
|
||||
|
||||
circuit_try_attaching_streams(circ);
|
||||
|
||||
memwipe(keys, 0, sizeof(keys));
|
||||
return 0;
|
||||
|
||||
err:
|
||||
memwipe(keys, 0, sizeof(keys));
|
||||
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
|
||||
return -1;
|
||||
}
|
||||
|
@ -2195,7 +2195,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
|
||||
|
||||
cpath->rend_dh_handshake_state = dh;
|
||||
dh = NULL;
|
||||
if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
|
||||
if (circuit_init_cpath_crypto(cpath,
|
||||
keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
|
||||
1, 0)<0)
|
||||
goto err;
|
||||
memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN);
|
||||
|
||||
|
@ -115,6 +115,7 @@ src_test_test_SOURCES = \
|
||||
src/test/test_extorport.c \
|
||||
src/test/test_hs.c \
|
||||
src/test/test_hs_service.c \
|
||||
src/test/test_hs_client.c \
|
||||
src/test/test_hs_intropoint.c \
|
||||
src/test/test_handles.c \
|
||||
src/test/test_hs_cache.c \
|
||||
@ -270,6 +271,7 @@ noinst_HEADERS+= \
|
||||
src/test/test.h \
|
||||
src/test/test_helpers.h \
|
||||
src/test/test_dir_common.h \
|
||||
src/test/test_connection.h \
|
||||
src/test/test_descriptors.inc \
|
||||
src/test/example_extrainfo.inc \
|
||||
src/test/failing_routerdescs.inc \
|
||||
|
@ -71,3 +71,19 @@ create_descriptor(rend_service_descriptor_t **generated, char **service_id,
|
||||
crypto_pk_free(pk2);
|
||||
}
|
||||
|
||||
rend_data_t *
|
||||
mock_rend_data(const char *onion_address)
|
||||
{
|
||||
rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
|
||||
rend_data_t *rend_query = &v2_data->base_;
|
||||
rend_query->version = 2;
|
||||
|
||||
strlcpy(v2_data->onion_address, onion_address,
|
||||
sizeof(v2_data->onion_address));
|
||||
v2_data->auth_type = REND_NO_AUTH;
|
||||
rend_query->hsdirs_fp = smartlist_new();
|
||||
smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
DIGEST_LEN));
|
||||
return rend_query;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ void generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc,
|
||||
char **service_id, int intro_points);
|
||||
void create_descriptor(rend_service_descriptor_t **generated,
|
||||
char **service_id, int intro_points);
|
||||
rend_data_t *mock_rend_data(const char *onion_address);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -1215,6 +1215,7 @@ struct testgroup_t testgroups[] = {
|
||||
{ "hs_cache/", hs_cache },
|
||||
{ "hs_descriptor/", hs_descriptor },
|
||||
{ "hs_service/", hs_service_tests },
|
||||
{ "hs_client/", hs_client_tests },
|
||||
{ "hs_intropoint/", hs_intropoint_tests },
|
||||
{ "introduce/", introduce_tests },
|
||||
{ "keypin/", keypin_tests },
|
||||
|
@ -209,6 +209,7 @@ extern struct testcase_t hs_tests[];
|
||||
extern struct testcase_t hs_cache[];
|
||||
extern struct testcase_t hs_descriptor[];
|
||||
extern struct testcase_t hs_service_tests[];
|
||||
extern struct testcase_t hs_client_tests[];
|
||||
extern struct testcase_t hs_intropoint_tests[];
|
||||
extern struct testcase_t introduce_tests[];
|
||||
extern struct testcase_t keypin_tests[];
|
||||
|
@ -17,9 +17,8 @@
|
||||
#include "rendcache.h"
|
||||
#include "directory.h"
|
||||
|
||||
static void test_conn_lookup_addr_helper(const char *address,
|
||||
int family,
|
||||
tor_addr_t *addr);
|
||||
#include "test_connection.h"
|
||||
#include "test_helpers.h"
|
||||
|
||||
static void * test_conn_get_basic_setup(const struct testcase_t *tc);
|
||||
static int test_conn_get_basic_teardown(const struct testcase_t *tc,
|
||||
@ -62,48 +61,7 @@ static int test_conn_get_rsrc_teardown(const struct testcase_t *tc,
|
||||
#define TEST_CONN_UNATTACHED_STATE (AP_CONN_STATE_CIRCUIT_WAIT)
|
||||
#define TEST_CONN_ATTACHED_STATE (AP_CONN_STATE_CONNECT_WAIT)
|
||||
|
||||
#define TEST_CONN_FD_INIT 50
|
||||
static int mock_connection_connect_sockaddr_called = 0;
|
||||
static int fake_socket_number = TEST_CONN_FD_INIT;
|
||||
|
||||
static int
|
||||
mock_connection_connect_sockaddr(connection_t *conn,
|
||||
const struct sockaddr *sa,
|
||||
socklen_t sa_len,
|
||||
const struct sockaddr *bindaddr,
|
||||
socklen_t bindaddr_len,
|
||||
int *socket_error)
|
||||
{
|
||||
(void)sa_len;
|
||||
(void)bindaddr;
|
||||
(void)bindaddr_len;
|
||||
|
||||
tor_assert(conn);
|
||||
tor_assert(sa);
|
||||
tor_assert(socket_error);
|
||||
|
||||
mock_connection_connect_sockaddr_called++;
|
||||
|
||||
conn->s = fake_socket_number++;
|
||||
tt_assert(SOCKET_OK(conn->s));
|
||||
/* We really should call tor_libevent_initialize() here. Because we don't,
|
||||
* we are relying on other parts of the code not checking if the_event_base
|
||||
* (and therefore event->ev_base) is NULL. */
|
||||
tt_assert(connection_add_connecting(conn) == 0);
|
||||
|
||||
done:
|
||||
/* Fake "connected" status */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
fake_close_socket(evutil_socket_t sock)
|
||||
{
|
||||
(void)sock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
|
||||
{
|
||||
int rv = 0;
|
||||
@ -122,51 +80,6 @@ test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
|
||||
tor_addr_make_null(addr, TEST_CONN_FAMILY);
|
||||
}
|
||||
|
||||
static connection_t *
|
||||
test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
|
||||
{
|
||||
connection_t *conn = NULL;
|
||||
tor_addr_t addr;
|
||||
int socket_err = 0;
|
||||
int in_progress = 0;
|
||||
|
||||
MOCK(connection_connect_sockaddr,
|
||||
mock_connection_connect_sockaddr);
|
||||
MOCK(tor_close_socket, fake_close_socket);
|
||||
|
||||
init_connection_lists();
|
||||
|
||||
conn = connection_new(type, TEST_CONN_FAMILY);
|
||||
tt_assert(conn);
|
||||
|
||||
test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
|
||||
tt_assert(!tor_addr_is_null(&addr));
|
||||
|
||||
tor_addr_copy_tight(&conn->addr, &addr);
|
||||
conn->port = TEST_CONN_PORT;
|
||||
mock_connection_connect_sockaddr_called = 0;
|
||||
in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
|
||||
TEST_CONN_PORT, &socket_err);
|
||||
tt_assert(mock_connection_connect_sockaddr_called == 1);
|
||||
tt_assert(!socket_err);
|
||||
tt_assert(in_progress == 0 || in_progress == 1);
|
||||
|
||||
/* fake some of the attributes so the connection looks OK */
|
||||
conn->state = state;
|
||||
conn->purpose = purpose;
|
||||
assert_connection_ok(conn, time(NULL));
|
||||
|
||||
UNMOCK(connection_connect_sockaddr);
|
||||
UNMOCK(tor_close_socket);
|
||||
return conn;
|
||||
|
||||
/* On failure */
|
||||
done:
|
||||
UNMOCK(connection_connect_sockaddr);
|
||||
UNMOCK(tor_close_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
test_conn_get_basic_setup(const struct testcase_t *tc)
|
||||
{
|
||||
|
13
src/test/test_connection.h
Normal file
13
src/test/test_connection.h
Normal file
@ -0,0 +1,13 @@
|
||||
/* Copyright (c) 2014-2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/** Some constants used by test_connection and helpers */
|
||||
#define TEST_CONN_FAMILY (AF_INET)
|
||||
#define TEST_CONN_ADDRESS "127.0.0.1"
|
||||
#define TEST_CONN_PORT (12345)
|
||||
#define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345"
|
||||
#define TEST_CONN_FD_INIT 50
|
||||
|
||||
void test_conn_lookup_addr_helper(const char *address,
|
||||
int family, tor_addr_t *addr);
|
||||
|
@ -7,9 +7,14 @@
|
||||
*/
|
||||
|
||||
#define ROUTERLIST_PRIVATE
|
||||
#define CONNECTION_PRIVATE
|
||||
#define MAIN_PRIVATE
|
||||
|
||||
#include "orconfig.h"
|
||||
#include "or.h"
|
||||
|
||||
#include "connection.h"
|
||||
#include "main.h"
|
||||
#include "relay.h"
|
||||
#include "routerlist.h"
|
||||
#include "nodelist.h"
|
||||
@ -17,6 +22,7 @@
|
||||
|
||||
#include "test.h"
|
||||
#include "test_helpers.h"
|
||||
#include "test_connection.h"
|
||||
|
||||
#ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS
|
||||
DISABLE_GCC_WARNING(overlength-strings)
|
||||
@ -143,3 +149,93 @@ mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
|
||||
return tor_addr_lookup__real(name, family, out);
|
||||
}
|
||||
|
||||
/*********** Helper funcs for making new connections/streams *****************/
|
||||
|
||||
/* Helper for test_conn_get_connection() */
|
||||
static int
|
||||
fake_close_socket(evutil_socket_t sock)
|
||||
{
|
||||
(void)sock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mock_connection_connect_sockaddr_called = 0;
|
||||
static int fake_socket_number = TEST_CONN_FD_INIT;
|
||||
|
||||
/* Helper for test_conn_get_connection() */
|
||||
static int
|
||||
mock_connection_connect_sockaddr(connection_t *conn,
|
||||
const struct sockaddr *sa,
|
||||
socklen_t sa_len,
|
||||
const struct sockaddr *bindaddr,
|
||||
socklen_t bindaddr_len,
|
||||
int *socket_error)
|
||||
{
|
||||
(void)sa_len;
|
||||
(void)bindaddr;
|
||||
(void)bindaddr_len;
|
||||
|
||||
tor_assert(conn);
|
||||
tor_assert(sa);
|
||||
tor_assert(socket_error);
|
||||
|
||||
mock_connection_connect_sockaddr_called++;
|
||||
|
||||
conn->s = fake_socket_number++;
|
||||
tt_assert(SOCKET_OK(conn->s));
|
||||
/* We really should call tor_libevent_initialize() here. Because we don't,
|
||||
* we are relying on other parts of the code not checking if the_event_base
|
||||
* (and therefore event->ev_base) is NULL. */
|
||||
tt_assert(connection_add_connecting(conn) == 0);
|
||||
|
||||
done:
|
||||
/* Fake "connected" status */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Create and return a new connection/stream */
|
||||
connection_t *
|
||||
test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
|
||||
{
|
||||
connection_t *conn = NULL;
|
||||
tor_addr_t addr;
|
||||
int socket_err = 0;
|
||||
int in_progress = 0;
|
||||
|
||||
MOCK(connection_connect_sockaddr,
|
||||
mock_connection_connect_sockaddr);
|
||||
MOCK(tor_close_socket, fake_close_socket);
|
||||
|
||||
init_connection_lists();
|
||||
|
||||
conn = connection_new(type, TEST_CONN_FAMILY);
|
||||
tt_assert(conn);
|
||||
|
||||
test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
|
||||
tt_assert(!tor_addr_is_null(&addr));
|
||||
|
||||
tor_addr_copy_tight(&conn->addr, &addr);
|
||||
conn->port = TEST_CONN_PORT;
|
||||
mock_connection_connect_sockaddr_called = 0;
|
||||
in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
|
||||
TEST_CONN_PORT, &socket_err);
|
||||
tt_assert(mock_connection_connect_sockaddr_called == 1);
|
||||
tt_assert(!socket_err);
|
||||
tt_assert(in_progress == 0 || in_progress == 1);
|
||||
|
||||
/* fake some of the attributes so the connection looks OK */
|
||||
conn->state = state;
|
||||
conn->purpose = purpose;
|
||||
assert_connection_ok(conn, time(NULL));
|
||||
|
||||
UNMOCK(connection_connect_sockaddr);
|
||||
UNMOCK(tor_close_socket);
|
||||
return conn;
|
||||
|
||||
/* On failure */
|
||||
done:
|
||||
UNMOCK(connection_connect_sockaddr);
|
||||
UNMOCK(tor_close_socket);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
/* Copyright (c) 2014-2017, The Tor Project, Inc. */
|
||||
/* Copyright (c) 2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
#ifndef TOR_TEST_HELPERS_H
|
||||
#define TOR_TEST_HELPERS_H
|
||||
|
||||
#include "or.h"
|
||||
|
||||
const char *get_yesterday_date_str(void);
|
||||
|
||||
circuit_t * dummy_origin_circuit_new(int num_cells);
|
||||
@ -20,7 +22,10 @@ void connection_write_to_buf_mock(const char *string, size_t len,
|
||||
int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
|
||||
uint16_t family, tor_addr_t *out);
|
||||
|
||||
connection_t *test_conn_get_connection(uint8_t state,
|
||||
uint8_t type, uint8_t purpose);
|
||||
|
||||
extern const char TEST_DESCRIPTORS[];
|
||||
|
||||
#endif
|
||||
#endif /* TOR_TEST_HELPERS_H */
|
||||
|
||||
|
286
src/test/test_hs_client.c
Normal file
286
src/test/test_hs_client.c
Normal file
@ -0,0 +1,286 @@
|
||||
/* Copyright (c) 2016-2017, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file test_hs_client.c
|
||||
* \brief Test prop224 HS client functionality.
|
||||
*/
|
||||
|
||||
#define CRYPTO_PRIVATE
|
||||
#define MAIN_PRIVATE
|
||||
#define TOR_CHANNEL_INTERNAL_
|
||||
#define CIRCUITBUILD_PRIVATE
|
||||
#define CIRCUITLIST_PRIVATE
|
||||
#define CONNECTION_PRIVATE
|
||||
|
||||
#include "test.h"
|
||||
#include "test_helpers.h"
|
||||
#include "log_test_helpers.h"
|
||||
#include "rend_test_helpers.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "crypto.h"
|
||||
#include "channeltls.h"
|
||||
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_ident.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "connection.h"
|
||||
#include "connection_edge.h"
|
||||
|
||||
static int
|
||||
mock_connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
|
||||
{
|
||||
(void) ap_conn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test helper function: Setup a circuit and a stream with the same hidden
|
||||
* service destination, and put them in <b>circ_out</b> and
|
||||
* <b>conn_out</b>. Make the stream wait for circuits to be established to the
|
||||
* hidden service. */
|
||||
static int
|
||||
helper_get_circ_and_stream_for_test(origin_circuit_t **circ_out,
|
||||
connection_t **conn_out,
|
||||
int is_legacy)
|
||||
{
|
||||
int retval;
|
||||
channel_tls_t *n_chan=NULL;
|
||||
rend_data_t *conn_rend_data = NULL;
|
||||
origin_circuit_t *or_circ = NULL;
|
||||
connection_t *conn = NULL;
|
||||
ed25519_public_key_t service_pk;
|
||||
|
||||
/* Make a dummy connection stream and make it wait for our circuit */
|
||||
conn = test_conn_get_connection(AP_CONN_STATE_CIRCUIT_WAIT,
|
||||
CONN_TYPE_AP /* ??? */,
|
||||
0);
|
||||
if (is_legacy) {
|
||||
/* Legacy: Setup rend_data of stream */
|
||||
char service_id[REND_SERVICE_ID_LEN_BASE32+1] = {0};
|
||||
TO_EDGE_CONN(conn)->rend_data = mock_rend_data(service_id);
|
||||
conn_rend_data = TO_EDGE_CONN(conn)->rend_data;
|
||||
} else {
|
||||
/* prop224: Setup hs conn identifier on the stream */
|
||||
ed25519_secret_key_t sk;
|
||||
tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
|
||||
tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
|
||||
|
||||
/* Setup hs_conn_identifier of stream */
|
||||
TO_EDGE_CONN(conn)->hs_ident = hs_ident_edge_conn_new(&service_pk);
|
||||
}
|
||||
|
||||
/* Make it wait for circuit */
|
||||
connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn));
|
||||
|
||||
/* This is needed to silence a BUG warning from
|
||||
connection_edge_update_circuit_isolation() */
|
||||
TO_ENTRY_CONN(conn)->original_dest_address =
|
||||
tor_strdup(TO_ENTRY_CONN(conn)->socks_request->address);
|
||||
|
||||
/****************************************************/
|
||||
|
||||
/* Now make dummy circuit */
|
||||
or_circ = origin_circuit_new();
|
||||
|
||||
or_circ->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
|
||||
|
||||
or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
|
||||
or_circ->build_state->is_internal = 1;
|
||||
|
||||
if (is_legacy) {
|
||||
/* Legacy: Setup rend data and final cpath */
|
||||
or_circ->build_state->pending_final_cpath =
|
||||
tor_malloc_zero(sizeof(crypt_path_t));
|
||||
or_circ->build_state->pending_final_cpath->magic = CRYPT_PATH_MAGIC;
|
||||
or_circ->build_state->pending_final_cpath->rend_dh_handshake_state =
|
||||
crypto_dh_new(DH_TYPE_REND);
|
||||
tt_assert(
|
||||
or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
|
||||
retval = crypto_dh_generate_public(
|
||||
or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
|
||||
tt_int_op(retval, ==, 0);
|
||||
or_circ->rend_data = rend_data_dup(conn_rend_data);
|
||||
} else {
|
||||
/* prop224: Setup hs ident on the circuit */
|
||||
or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
|
||||
HS_IDENT_CIRCUIT_RENDEZVOUS);
|
||||
}
|
||||
|
||||
TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
|
||||
|
||||
/* fake n_chan */
|
||||
n_chan = tor_malloc_zero(sizeof(channel_tls_t));
|
||||
n_chan->base_.global_identifier = 1;
|
||||
or_circ->base_.n_chan = &(n_chan->base_);
|
||||
|
||||
*circ_out = or_circ;
|
||||
*conn_out = conn;
|
||||
|
||||
return 0;
|
||||
|
||||
done:
|
||||
/* something failed */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Test: Ensure that setting up legacy e2e rendezvous circuits works
|
||||
* correctly. */
|
||||
static void
|
||||
test_e2e_rend_circuit_setup_legacy(void *arg)
|
||||
{
|
||||
int retval;
|
||||
origin_circuit_t *or_circ = NULL;
|
||||
connection_t *conn = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/** In this test we create a v2 legacy HS stream and a circuit with the same
|
||||
* hidden service destination. We make the stream wait for circuits to be
|
||||
* established to the hidden service, and then we complete the circuit using
|
||||
* the hs_circuit_setup_e2e_rend_circ_legacy_client() function. We then
|
||||
* check that the end-to-end cpath was setup correctly and that the stream
|
||||
* was attached to the circuit as expected. */
|
||||
|
||||
MOCK(connection_ap_handshake_send_begin,
|
||||
mock_connection_ap_handshake_send_begin);
|
||||
|
||||
/* Setup */
|
||||
retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 1);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(or_circ);
|
||||
tt_assert(conn);
|
||||
|
||||
/* Check number of hops */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, ==, 0);
|
||||
|
||||
/* Check that our stream is not attached on any circuits */
|
||||
tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
|
||||
|
||||
/********************************************** */
|
||||
|
||||
/* Make a good RENDEZVOUS1 cell body because it needs to pass key exchange
|
||||
* digest verification... */
|
||||
uint8_t rend_cell_body[DH_KEY_LEN+DIGEST_LEN] = {2};
|
||||
{
|
||||
char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
|
||||
crypto_dh_t *dh_state =
|
||||
or_circ->build_state->pending_final_cpath->rend_dh_handshake_state;
|
||||
/* compute and overwrite digest of cell body with the right value */
|
||||
retval = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh_state,
|
||||
(char*)rend_cell_body, DH_KEY_LEN,
|
||||
keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN);
|
||||
tt_int_op(retval, OP_GT, 0);
|
||||
memcpy(rend_cell_body+DH_KEY_LEN, keys, DIGEST_LEN);
|
||||
}
|
||||
|
||||
/* Setup the circuit */
|
||||
retval = hs_circuit_setup_e2e_rend_circ_legacy_client(or_circ,
|
||||
rend_cell_body);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/**********************************************/
|
||||
|
||||
/* See that a hop was added to the circuit's cpath */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, OP_EQ, 1);
|
||||
|
||||
/* Check the digest algo */
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
|
||||
OP_EQ, DIGEST_SHA1);
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
|
||||
OP_EQ, DIGEST_SHA1);
|
||||
tt_assert(or_circ->cpath->f_crypto);
|
||||
tt_assert(or_circ->cpath->b_crypto);
|
||||
|
||||
/* Ensure that circ purpose was changed */
|
||||
tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
|
||||
|
||||
/* Test that stream got attached */
|
||||
tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
|
||||
|
||||
done:
|
||||
connection_free_(conn);
|
||||
tor_free(TO_CIRCUIT(or_circ)->n_chan);
|
||||
circuit_free(TO_CIRCUIT(or_circ));
|
||||
}
|
||||
|
||||
/* Test: Ensure that setting up v3 rendezvous circuits works correctly. */
|
||||
static void
|
||||
test_e2e_rend_circuit_setup(void *arg)
|
||||
{
|
||||
uint8_t ntor_key_seed[DIGEST256_LEN] = {0};
|
||||
origin_circuit_t *or_circ;
|
||||
int retval;
|
||||
connection_t *conn = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
/** In this test we create a prop224 v3 HS stream and a circuit with the same
|
||||
* hidden service destination. We make the stream wait for circuits to be
|
||||
* established to the hidden service, and then we complete the circuit using
|
||||
* the hs_circuit_setup_e2e_rend_circ() function. We then check that the
|
||||
* end-to-end cpath was setup correctly and that the stream was attached to
|
||||
* the circuit as expected. */
|
||||
|
||||
MOCK(connection_ap_handshake_send_begin,
|
||||
mock_connection_ap_handshake_send_begin);
|
||||
|
||||
/* Setup */
|
||||
retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 0);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(or_circ);
|
||||
tt_assert(conn);
|
||||
|
||||
/* Check number of hops: There should be no hops yet to this circ */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, ==, 0);
|
||||
tt_assert(!or_circ->cpath);
|
||||
|
||||
/* Check that our stream is not attached on any circuits */
|
||||
tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
|
||||
|
||||
/**********************************************/
|
||||
|
||||
/* Setup the circuit */
|
||||
retval = hs_circuit_setup_e2e_rend_circ(or_circ,
|
||||
ntor_key_seed, sizeof(ntor_key_seed),
|
||||
0);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/**********************************************/
|
||||
|
||||
/* See that a hop was added to the circuit's cpath */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, OP_EQ, 1);
|
||||
|
||||
/* Check that the crypt path has prop224 algorithm parameters */
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
|
||||
OP_EQ, DIGEST_SHA3_256);
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
|
||||
OP_EQ, DIGEST_SHA3_256);
|
||||
tt_assert(or_circ->cpath->f_crypto);
|
||||
tt_assert(or_circ->cpath->b_crypto);
|
||||
|
||||
/* Ensure that circ purpose was changed */
|
||||
tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
|
||||
|
||||
/* Test that stream got attached */
|
||||
tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
|
||||
|
||||
done:
|
||||
connection_free_(conn);
|
||||
tor_free(TO_CIRCUIT(or_circ)->n_chan);
|
||||
circuit_free(TO_CIRCUIT(or_circ));
|
||||
}
|
||||
|
||||
struct testcase_t hs_client_tests[] = {
|
||||
{ "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy,
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup,
|
||||
TT_FORK, NULL, NULL },
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -6,20 +6,34 @@
|
||||
* \brief Test hidden service functionality.
|
||||
*/
|
||||
|
||||
#define CIRCUITBUILD_PRIVATE
|
||||
#define CIRCUITLIST_PRIVATE
|
||||
#define CONNECTION_PRIVATE
|
||||
#define CRYPTO_PRIVATE
|
||||
#define HS_COMMON_PRIVATE
|
||||
#define HS_SERVICE_PRIVATE
|
||||
#define HS_INTROPOINT_PRIVATE
|
||||
#define MAIN_PRIVATE
|
||||
#define TOR_CHANNEL_INTERNAL_
|
||||
|
||||
#include "test.h"
|
||||
#include "log_test_helpers.h"
|
||||
#include "crypto.h"
|
||||
#include "rend_test_helpers.h"
|
||||
|
||||
#include "hs/cell_establish_intro.h"
|
||||
#include "or.h"
|
||||
#include "channeltls.h"
|
||||
#include "circuitbuild.h"
|
||||
#include "circuitlist.h"
|
||||
#include "circuituse.h"
|
||||
#include "config.h"
|
||||
#include "connection.h"
|
||||
#include "hs_circuit.h"
|
||||
#include "hs_common.h"
|
||||
#include "hs_service.h"
|
||||
#include "hs_ident.h"
|
||||
#include "hs_intropoint.h"
|
||||
|
||||
#include "hs_ntor.h"
|
||||
#include "hs_service.h"
|
||||
#include "main.h"
|
||||
#include "rendservice.h"
|
||||
|
||||
/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we
|
||||
* parse it from the receiver side. */
|
||||
@ -235,6 +249,72 @@ test_time_period(void *arg)
|
||||
;
|
||||
}
|
||||
|
||||
/* Test: Ensure that setting up rendezvous circuits works correctly. */
|
||||
static void
|
||||
test_e2e_rend_circuit_setup(void *arg)
|
||||
{
|
||||
ed25519_public_key_t service_pk;
|
||||
origin_circuit_t *or_circ;
|
||||
int retval;
|
||||
|
||||
/** In this test we create a v3 prop224 service-side rendezvous circuit.
|
||||
* We simulate an HS ntor key exchange with a client, and check that
|
||||
* the circuit was setup correctly and is ready to accept rendezvous data */
|
||||
|
||||
(void) arg;
|
||||
|
||||
/* Now make dummy circuit */
|
||||
{
|
||||
or_circ = origin_circuit_new();
|
||||
|
||||
or_circ->base_.purpose = CIRCUIT_PURPOSE_S_CONNECT_REND;
|
||||
|
||||
or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
|
||||
or_circ->build_state->is_internal = 1;
|
||||
|
||||
/* prop224: Setup hs conn identifier on the stream */
|
||||
ed25519_secret_key_t sk;
|
||||
tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
|
||||
tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
|
||||
|
||||
or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
|
||||
HS_IDENT_CIRCUIT_RENDEZVOUS);
|
||||
|
||||
TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
|
||||
}
|
||||
|
||||
/* Check number of hops */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* Setup the circuit: do the ntor key exchange */
|
||||
{
|
||||
uint8_t ntor_key_seed[DIGEST256_LEN] = {2};
|
||||
retval = hs_circuit_setup_e2e_rend_circ(or_circ,
|
||||
ntor_key_seed, sizeof(ntor_key_seed),
|
||||
1);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
}
|
||||
|
||||
/* See that a hop was added to the circuit's cpath */
|
||||
retval = cpath_get_n_hops(&or_circ->cpath);
|
||||
tt_int_op(retval, OP_EQ, 1);
|
||||
|
||||
/* Check the digest algo */
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
|
||||
OP_EQ, DIGEST_SHA3_256);
|
||||
tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
|
||||
OP_EQ, DIGEST_SHA3_256);
|
||||
tt_assert(or_circ->cpath->f_crypto);
|
||||
tt_assert(or_circ->cpath->b_crypto);
|
||||
|
||||
/* Ensure that circ purpose was changed */
|
||||
tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED);
|
||||
|
||||
done:
|
||||
circuit_free(TO_CIRCUIT(or_circ));
|
||||
}
|
||||
|
||||
struct testcase_t hs_service_tests[] = {
|
||||
{ "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
|
||||
NULL, NULL },
|
||||
@ -244,6 +324,8 @@ struct testcase_t hs_service_tests[] = {
|
||||
NULL, NULL },
|
||||
{ "time_period", test_time_period, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -21,22 +21,6 @@ static const int TIME_IN_THE_PAST = -(REND_CACHE_MAX_AGE + \
|
||||
REND_CACHE_MAX_SKEW + 60);
|
||||
static const int TIME_IN_THE_FUTURE = REND_CACHE_MAX_SKEW + 60;
|
||||
|
||||
static rend_data_t *
|
||||
mock_rend_data(const char *onion_address)
|
||||
{
|
||||
rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
|
||||
rend_data_t *rend_query = &v2_data->base_;
|
||||
rend_query->version = 2;
|
||||
|
||||
strlcpy(v2_data->onion_address, onion_address,
|
||||
sizeof(v2_data->onion_address));
|
||||
v2_data->auth_type = REND_NO_AUTH;
|
||||
rend_query->hsdirs_fp = smartlist_new();
|
||||
smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
DIGEST_LEN));
|
||||
return rend_query;
|
||||
}
|
||||
|
||||
static void
|
||||
test_rend_cache_lookup_entry(void *data)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user