mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-30 15:43:32 +01:00
Merge remote-tracking branch 'tor-github/pr/1685/head'
This commit is contained in:
commit
caa392a73a
4
changes/bug32709
Normal file
4
changes/bug32709
Normal file
@ -0,0 +1,4 @@
|
||||
o Major features (v3 onion services):
|
||||
- Allow v3 onion services to act as OnionBalance backend instances using
|
||||
the HiddenServiceOnionBalanceInstance torrc option. Closes ticket 32709.
|
||||
|
@ -3193,6 +3193,19 @@ The next section describes the per service options that can only be set
|
||||
The HAProxy version 1 protocol is described in detail at
|
||||
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
|
||||
[[HiddenServiceOnionBalanceInstance]] **HiddenServiceOnionBalanceInstance** **0**|**1**::
|
||||
|
||||
If set to 1, this onion service becomes an OnionBalance instance and will
|
||||
accept client connections destined to an OnionBalance frontend. In this
|
||||
case, Tor expects to find a file named "ob_config" inside the
|
||||
**HiddenServiceDir** directory with content:
|
||||
+
|
||||
MasterOnionAddress <frontend_onion_address>
|
||||
+
|
||||
where <frontend_onion_address> is the onion address of the OnionBalance
|
||||
frontend (e.g. wrxdvcaqpuzakbfww5sxs6r2uybczwijzfn2ezy2osaj7iox7kl7nhad.onion).
|
||||
|
||||
|
||||
[[HiddenServiceMaxStreams]] **HiddenServiceMaxStreams** __N__::
|
||||
The maximum number of simultaneous streams (connections) per rendezvous
|
||||
circuit. The maximum value allowed is 65535. (Setting this to 0 will allow
|
||||
|
@ -245,7 +245,7 @@ problem function-size /src/feature/hs/hs_cell.c:hs_cell_build_establish_intro()
|
||||
problem function-size /src/feature/hs/hs_cell.c:hs_cell_parse_introduce2() 152
|
||||
problem function-size /src/feature/hs/hs_client.c:send_introduce1() 103
|
||||
problem function-size /src/feature/hs/hs_common.c:hs_get_responsible_hsdirs() 102
|
||||
problem function-size /src/feature/hs/hs_config.c:config_service_v3() 107
|
||||
problem function-size /src/feature/hs/hs_config.c:config_service_v3() 128
|
||||
problem function-size /src/feature/hs/hs_config.c:config_generic_service() 138
|
||||
problem function-size /src/feature/hs/hs_descriptor.c:desc_encode_v3() 101
|
||||
problem function-size /src/feature/hs/hs_descriptor.c:decrypt_desc_layer() 111
|
||||
|
@ -510,6 +510,8 @@ static const config_var_t option_vars_[] = {
|
||||
LINELIST_S, RendConfigLines, NULL),
|
||||
VAR("HiddenServiceEnableIntroDoSBurstPerSec",
|
||||
LINELIST_S, RendConfigLines, NULL),
|
||||
VAR("HiddenServiceOnionBalanceInstance",
|
||||
LINELIST_S, RendConfigLines, NULL),
|
||||
VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
|
||||
V(HidServAuth, LINELIST, NULL),
|
||||
V(ClientOnionAuthDir, FILENAME, NULL),
|
||||
|
@ -170,7 +170,7 @@ get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input,
|
||||
* necessary key material, and return 0. */
|
||||
static void
|
||||
get_introduce1_key_material(const uint8_t *secret_input,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
|
||||
{
|
||||
uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN];
|
||||
@ -181,7 +181,7 @@ get_introduce1_key_material(const uint8_t *secret_input,
|
||||
/* Let's build info */
|
||||
ptr = info_blob;
|
||||
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
|
||||
APPEND(ptr, subcredential, DIGEST256_LEN);
|
||||
APPEND(ptr, subcredential->subcred, SUBCRED_LEN);
|
||||
tor_assert(ptr == info_blob + sizeof(info_blob));
|
||||
|
||||
/* Let's build the input to the KDF */
|
||||
@ -317,7 +317,7 @@ hs_ntor_client_get_introduce1_keys(
|
||||
const ed25519_public_key_t *intro_auth_pubkey,
|
||||
const curve25519_public_key_t *intro_enc_pubkey,
|
||||
const curve25519_keypair_t *client_ephemeral_enc_keypair,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
|
||||
{
|
||||
int bad = 0;
|
||||
@ -450,8 +450,30 @@ hs_ntor_service_get_introduce1_keys(
|
||||
const ed25519_public_key_t *intro_auth_pubkey,
|
||||
const curve25519_keypair_t *intro_enc_keypair,
|
||||
const curve25519_public_key_t *client_ephemeral_enc_pubkey,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
|
||||
{
|
||||
return hs_ntor_service_get_introduce1_keys_multi(
|
||||
intro_auth_pubkey,
|
||||
intro_enc_keypair,
|
||||
client_ephemeral_enc_pubkey,
|
||||
1,
|
||||
subcredential,
|
||||
hs_ntor_intro_cell_keys_out);
|
||||
}
|
||||
|
||||
/**
|
||||
* As hs_ntor_service_get_introduce1_keys(), but take multiple subcredentials
|
||||
* as input, and yield multiple sets of keys as output.
|
||||
**/
|
||||
int
|
||||
hs_ntor_service_get_introduce1_keys_multi(
|
||||
const struct ed25519_public_key_t *intro_auth_pubkey,
|
||||
const struct curve25519_keypair_t *intro_enc_keypair,
|
||||
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
|
||||
size_t n_subcredentials,
|
||||
const hs_subcredential_t *subcredentials,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
|
||||
{
|
||||
int bad = 0;
|
||||
uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN];
|
||||
@ -460,7 +482,8 @@ hs_ntor_service_get_introduce1_keys(
|
||||
tor_assert(intro_auth_pubkey);
|
||||
tor_assert(intro_enc_keypair);
|
||||
tor_assert(client_ephemeral_enc_pubkey);
|
||||
tor_assert(subcredential);
|
||||
tor_assert(n_subcredentials >= 1);
|
||||
tor_assert(subcredentials);
|
||||
tor_assert(hs_ntor_intro_cell_keys_out);
|
||||
|
||||
/* Compute EXP(X, b) */
|
||||
@ -476,13 +499,16 @@ hs_ntor_service_get_introduce1_keys(
|
||||
secret_input);
|
||||
bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN);
|
||||
|
||||
/* Get ENC_KEY and MAC_KEY! */
|
||||
get_introduce1_key_material(secret_input, subcredential,
|
||||
hs_ntor_intro_cell_keys_out);
|
||||
for (unsigned i = 0; i < n_subcredentials; ++i) {
|
||||
/* Get ENC_KEY and MAC_KEY! */
|
||||
get_introduce1_key_material(secret_input, &subcredentials[i],
|
||||
&hs_ntor_intro_cell_keys_out[i]);
|
||||
}
|
||||
|
||||
memwipe(secret_input, 0, sizeof(secret_input));
|
||||
if (bad) {
|
||||
memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t));
|
||||
memwipe(hs_ntor_intro_cell_keys_out, 0,
|
||||
sizeof(hs_ntor_intro_cell_keys_t) * n_subcredentials);
|
||||
}
|
||||
|
||||
return bad ? -1 : 0;
|
||||
|
@ -35,11 +35,20 @@ typedef struct hs_ntor_rend_cell_keys_t {
|
||||
uint8_t ntor_key_seed[DIGEST256_LEN];
|
||||
} hs_ntor_rend_cell_keys_t;
|
||||
|
||||
#define SUBCRED_LEN DIGEST256_LEN
|
||||
|
||||
/**
|
||||
* A 'subcredential' used to prove knowledge of a hidden service.
|
||||
**/
|
||||
typedef struct hs_subcredential_t {
|
||||
uint8_t subcred[SUBCRED_LEN];
|
||||
} hs_subcredential_t;
|
||||
|
||||
int hs_ntor_client_get_introduce1_keys(
|
||||
const struct ed25519_public_key_t *intro_auth_pubkey,
|
||||
const struct curve25519_public_key_t *intro_enc_pubkey,
|
||||
const struct curve25519_keypair_t *client_ephemeral_enc_keypair,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
|
||||
|
||||
int hs_ntor_client_get_rendezvous1_keys(
|
||||
@ -49,11 +58,19 @@ int hs_ntor_client_get_rendezvous1_keys(
|
||||
const struct curve25519_public_key_t *service_ephemeral_rend_pubkey,
|
||||
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
|
||||
|
||||
int hs_ntor_service_get_introduce1_keys_multi(
|
||||
const struct ed25519_public_key_t *intro_auth_pubkey,
|
||||
const struct curve25519_keypair_t *intro_enc_keypair,
|
||||
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
|
||||
size_t n_subcredentials,
|
||||
const hs_subcredential_t *subcredentials,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
|
||||
|
||||
int hs_ntor_service_get_introduce1_keys(
|
||||
const struct ed25519_public_key_t *intro_auth_pubkey,
|
||||
const struct curve25519_keypair_t *intro_enc_keypair,
|
||||
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
|
||||
|
||||
int hs_ntor_service_get_rendezvous1_keys(
|
||||
|
@ -2819,8 +2819,8 @@ extend_info_dup(extend_info_t *info)
|
||||
* If there is no chosen exit, or if we don't know the node_t for
|
||||
* the chosen exit, return NULL.
|
||||
*/
|
||||
const node_t *
|
||||
build_state_get_exit_node(cpath_build_state_t *state)
|
||||
MOCK_IMPL(const node_t *,
|
||||
build_state_get_exit_node,(cpath_build_state_t *state))
|
||||
{
|
||||
if (!state || !state->chosen_exit)
|
||||
return NULL;
|
||||
|
@ -66,7 +66,8 @@ int circuit_can_use_tap(const origin_circuit_t *circ);
|
||||
int circuit_has_usable_onion_key(const origin_circuit_t *circ);
|
||||
int extend_info_has_preferred_onion_key(const extend_info_t* ei);
|
||||
const uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state);
|
||||
const node_t *build_state_get_exit_node(cpath_build_state_t *state);
|
||||
MOCK_DECL(const node_t *,
|
||||
build_state_get_exit_node,(cpath_build_state_t *state));
|
||||
const char *build_state_get_exit_nickname(cpath_build_state_t *state);
|
||||
|
||||
struct circuit_guard_state_t;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "feature/hs_common/replaycache.h"
|
||||
|
||||
#include "feature/hs/hs_cell.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "core/crypto/hs_ntor.h"
|
||||
|
||||
#include "core/or/origin_circuit_st.h"
|
||||
@ -67,14 +68,17 @@ compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_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. */
|
||||
/**
|
||||
* From a set of keys, a list of subcredentials, and the ENCRYPTED section of
|
||||
* an INTRODUCE2 cell, return an array of newly allocated intro cell keys
|
||||
* structures. 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,
|
||||
size_t n_subcredentials,
|
||||
const hs_subcredential_t *subcredentials,
|
||||
const uint8_t *encrypted_section,
|
||||
curve25519_public_key_t *client_pk)
|
||||
{
|
||||
@ -82,17 +86,19 @@ get_introduce2_key_material(const ed25519_public_key_t *auth_key,
|
||||
|
||||
tor_assert(auth_key);
|
||||
tor_assert(enc_key);
|
||||
tor_assert(subcredential);
|
||||
tor_assert(n_subcredentials > 0);
|
||||
tor_assert(subcredentials);
|
||||
tor_assert(encrypted_section);
|
||||
tor_assert(client_pk);
|
||||
|
||||
keys = tor_malloc_zero(sizeof(*keys));
|
||||
keys = tor_calloc(n_subcredentials, sizeof(hs_ntor_intro_cell_keys_t));
|
||||
|
||||
/* 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) {
|
||||
if (hs_ntor_service_get_introduce1_keys_multi(auth_key, enc_key, client_pk,
|
||||
n_subcredentials,
|
||||
subcredentials, 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);
|
||||
@ -747,6 +753,74 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** For the encrypted INTRO2 cell in <b>encrypted_section</b>, use the crypto
|
||||
* material in <b>data</b> to compute the right ntor keys. Also validate the
|
||||
* INTRO2 MAC to ensure that the keys are the right ones.
|
||||
*
|
||||
* Return NULL on failure to either produce the key material or on MAC
|
||||
* valication. Else a newly allocated intro keys object. */
|
||||
static hs_ntor_intro_cell_keys_t *
|
||||
get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
|
||||
const uint8_t *encrypted_section,
|
||||
size_t encrypted_section_len)
|
||||
{
|
||||
hs_ntor_intro_cell_keys_t *intro_keys = NULL;
|
||||
hs_ntor_intro_cell_keys_t *intro_keys_result = NULL;
|
||||
|
||||
/* 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->n_subcredentials,
|
||||
data->subcredentials,
|
||||
encrypted_section,
|
||||
&data->client_pk);
|
||||
if (intro_keys == NULL) {
|
||||
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
|
||||
"compute key material");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make sure we are not about to underflow. */
|
||||
if (BUG(encrypted_section_len < DIGEST256_LEN)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Validate MAC from the cell and our computed key material. The MAC field
|
||||
* in the cell is at the end of the encrypted section. */
|
||||
intro_keys_result = tor_malloc_zero(sizeof(*intro_keys_result));
|
||||
for (unsigned i = 0; i < data->n_subcredentials; ++i) {
|
||||
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[i].mac_key,
|
||||
sizeof(intro_keys[i].mac_key),
|
||||
mac, sizeof(mac));
|
||||
/* Time-invariant conditional copy: if the MAC is what we expected, then
|
||||
* set intro_keys_result to intro_keys[i]. Otherwise, don't: but don't
|
||||
* leak which one it was! */
|
||||
bool equal = tor_memeq(mac, encrypted_section + mac_offset, sizeof(mac));
|
||||
memcpy_if_true_timei(equal, intro_keys_result, &intro_keys[i],
|
||||
sizeof(*intro_keys_result));
|
||||
}
|
||||
|
||||
/* We no longer need intro_keys. */
|
||||
memwipe(intro_keys, 0,
|
||||
sizeof(hs_ntor_intro_cell_keys_t) * data->n_subcredentials);
|
||||
tor_free(intro_keys);
|
||||
|
||||
if (safe_mem_is_zero(intro_keys_result, sizeof(*intro_keys_result))) {
|
||||
log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell");
|
||||
tor_free(intro_keys_result); /* sets intro_keys_result to NULL */
|
||||
}
|
||||
|
||||
return intro_keys_result;
|
||||
}
|
||||
|
||||
/** Parse 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
|
||||
@ -795,47 +869,29 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
|
||||
/* 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"
|
||||
log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the "
|
||||
"same ENCRYPTED section was seen %ld seconds ago. "
|
||||
"Dropping cell.", (long int) 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,
|
||||
/* First bytes of the ENCRYPTED section are the client public key (they are
|
||||
* guaranteed to exist because of the length check above). We are gonna use
|
||||
* the client public key to compute the ntor keys and decrypt the payload:
|
||||
*/
|
||||
memcpy(&data->client_pk.public_key, encrypted_section,
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
|
||||
/* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
|
||||
intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
|
||||
encrypted_section_len);
|
||||
if (!intro_keys) {
|
||||
log_warn(LD_REND, "Could not get valid INTRO2 keys 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 =
|
||||
|
@ -16,6 +16,8 @@
|
||||
* 3.2.2 of the specification). Below this value, the cell must be padded. */
|
||||
#define HS_CELL_INTRODUCE1_MIN_SIZE 246
|
||||
|
||||
struct hs_subcredential_t;
|
||||
|
||||
/** This data structure contains data that we need to build an INTRODUCE1 cell
|
||||
* used by the INTRODUCE1 build function. */
|
||||
typedef struct hs_cell_introduce1_data_t {
|
||||
@ -29,7 +31,7 @@ typedef struct hs_cell_introduce1_data_t {
|
||||
/** Introduction point encryption public key. */
|
||||
const curve25519_public_key_t *enc_pk;
|
||||
/** Subcredentials of the service. */
|
||||
const uint8_t *subcredential;
|
||||
const struct hs_subcredential_t *subcredential;
|
||||
/** Onion public key for the ntor handshake. */
|
||||
const curve25519_public_key_t *onion_pk;
|
||||
/** Rendezvous cookie. */
|
||||
@ -55,9 +57,14 @@ typedef struct hs_cell_introduce2_data_t {
|
||||
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;
|
||||
/**
|
||||
* Length of the subcredentials array below.
|
||||
**/
|
||||
size_t n_subcredentials;
|
||||
/** Array of <b>n_subcredentials</b> subcredentials for the service. Pointer
|
||||
* owned by the descriptor that owns the introduction point through which we
|
||||
* received the INTRO2 cell. */
|
||||
const struct hs_subcredential_t *subcredentials;
|
||||
/** Payload of the received encoded cell. */
|
||||
const uint8_t *payload;
|
||||
/** Size of the payload of the received encoded cell. */
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "feature/client/circpathbias.h"
|
||||
#include "feature/hs/hs_cell.h"
|
||||
#include "feature/hs/hs_circuit.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "feature/hs/hs_circuitmap.h"
|
||||
#include "feature/hs/hs_client.h"
|
||||
#include "feature/hs/hs_ident.h"
|
||||
@ -367,10 +368,10 @@ get_service_anonymity_string(const hs_service_t *service)
|
||||
* 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)
|
||||
MOCK_IMPL(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);
|
||||
@ -578,7 +579,7 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
|
||||
static int
|
||||
setup_introduce1_data(const hs_desc_intro_point_t *ip,
|
||||
const node_t *rp_node,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
hs_cell_introduce1_data_t *intro1_data)
|
||||
{
|
||||
int ret = -1;
|
||||
@ -958,6 +959,42 @@ hs_circ_handle_intro_established(const hs_service_t *service,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go into <b>data</b> and add the right subcredential to be able to handle
|
||||
* this incoming cell.
|
||||
*
|
||||
* <b>desc_subcred</b> is the subcredential of the descriptor that corresponds
|
||||
* to the intro point that received this intro request. This subcredential
|
||||
* should be used if we are not an onionbalance instance.
|
||||
*
|
||||
* Return 0 if everything went well, or -1 in case of internal error.
|
||||
*/
|
||||
static int
|
||||
get_subcredential_for_handling_intro2_cell(const hs_service_t *service,
|
||||
hs_cell_introduce2_data_t *data,
|
||||
const hs_subcredential_t *desc_subcred)
|
||||
{
|
||||
/* Handle the simple case first: We are not an onionbalance instance and we
|
||||
* should just use the regular descriptor subcredential */
|
||||
if (!hs_ob_service_is_instance(service)) {
|
||||
data->n_subcredentials = 1;
|
||||
data->subcredentials = desc_subcred;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This should not happen since we should have made onionbalance
|
||||
* subcredentials when we created our descriptors. */
|
||||
if (BUG(!service->ob_subcreds)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We are an onionbalance instance: */
|
||||
data->n_subcredentials = service->n_ob_subcreds;
|
||||
data->subcredentials = service->ob_subcreds;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** 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
|
||||
@ -966,7 +1003,7 @@ 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 hs_subcredential_t *subcredential,
|
||||
const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
int ret = -1;
|
||||
@ -983,12 +1020,16 @@ hs_circ_handle_introduce2(const hs_service_t *service,
|
||||
* 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 (get_subcredential_for_handling_intro2_cell(service,
|
||||
&data, subcredential)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
|
||||
goto done;
|
||||
}
|
||||
@ -1092,7 +1133,7 @@ int
|
||||
hs_circ_send_introduce1(origin_circuit_t *intro_circ,
|
||||
origin_circuit_t *rend_circ,
|
||||
const hs_desc_intro_point_t *ip,
|
||||
const uint8_t *subcredential)
|
||||
const hs_subcredential_t *subcredential)
|
||||
{
|
||||
int ret = -1;
|
||||
ssize_t payload_len;
|
||||
|
@ -46,15 +46,16 @@ int hs_circ_handle_intro_established(const hs_service_t *service,
|
||||
origin_circuit_t *circ,
|
||||
const uint8_t *payload,
|
||||
size_t payload_len);
|
||||
struct hs_subcredential_t;
|
||||
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 struct hs_subcredential_t *subcredential,
|
||||
const uint8_t *payload, size_t payload_len);
|
||||
int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
|
||||
origin_circuit_t *rend_circ,
|
||||
const hs_desc_intro_point_t *ip,
|
||||
const uint8_t *subcredential);
|
||||
const struct hs_subcredential_t *subcredential);
|
||||
int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
|
||||
|
||||
/* e2e circuit API. */
|
||||
@ -78,6 +79,12 @@ create_rp_circuit_identifier(const hs_service_t *service,
|
||||
const curve25519_public_key_t *server_pk,
|
||||
const struct hs_ntor_rend_cell_keys_t *keys);
|
||||
|
||||
struct hs_cell_introduce2_data_t;
|
||||
MOCK_DECL(STATIC void,
|
||||
launch_rendezvous_point_circuit,(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
const struct hs_cell_introduce2_data_t *data));
|
||||
|
||||
#endif /* defined(HS_CIRCUIT_PRIVATE) */
|
||||
|
||||
#endif /* !defined(TOR_HS_CIRCUIT_H) */
|
||||
|
@ -646,7 +646,7 @@ send_introduce1(origin_circuit_t *intro_circ,
|
||||
|
||||
/* Send the INTRODUCE1 cell. */
|
||||
if (hs_circ_send_introduce1(intro_circ, rend_circ, ip,
|
||||
desc->subcredential) < 0) {
|
||||
&desc->subcredential) < 0) {
|
||||
if (TO_CIRCUIT(intro_circ)->marked_for_close) {
|
||||
/* If the introduction circuit was closed, we were unable to send the
|
||||
* cell for some reasons. In any case, the intro circuit has to be
|
||||
@ -1845,7 +1845,7 @@ hs_client_decode_descriptor(const char *desc_str,
|
||||
hs_descriptor_t **desc)
|
||||
{
|
||||
hs_desc_decode_status_t ret;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
ed25519_public_key_t blinded_pubkey;
|
||||
hs_client_service_authorization_t *client_auth = NULL;
|
||||
curve25519_secret_key_t *client_auht_sk = NULL;
|
||||
@ -1865,13 +1865,13 @@ hs_client_decode_descriptor(const char *desc_str,
|
||||
uint64_t current_time_period = hs_get_time_period_num(0);
|
||||
hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
|
||||
&blinded_pubkey);
|
||||
hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential);
|
||||
hs_get_subcredential(service_identity_pk, &blinded_pubkey, &subcredential);
|
||||
}
|
||||
|
||||
/* Parse descriptor */
|
||||
ret = hs_desc_decode_descriptor(desc_str, subcredential,
|
||||
ret = hs_desc_decode_descriptor(desc_str, &subcredential,
|
||||
client_auht_sk, desc);
|
||||
memwipe(subcredential, 0, sizeof(subcredential));
|
||||
memwipe(&subcredential, 0, sizeof(subcredential));
|
||||
if (ret != HS_DESC_DECODE_OK) {
|
||||
goto err;
|
||||
}
|
||||
@ -2486,4 +2486,3 @@ set_hs_client_auths_map(digest256map_t *map)
|
||||
}
|
||||
|
||||
#endif /* defined(TOR_UNIT_TESTS) */
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "feature/hs/hs_client.h"
|
||||
#include "feature/hs/hs_common.h"
|
||||
#include "feature/hs/hs_dos.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "feature/hs/hs_ident.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/hs_common/shared_random_client.h"
|
||||
@ -808,12 +809,12 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
|
||||
}
|
||||
|
||||
/** 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).
|
||||
* subcredential and put it in subcred_out.
|
||||
* 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)
|
||||
hs_subcredential_t *subcred_out)
|
||||
{
|
||||
uint8_t credential[DIGEST256_LEN];
|
||||
crypto_digest_t *digest;
|
||||
@ -841,7 +842,8 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk,
|
||||
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_get_digest(digest, (char *) subcred_out->subcred,
|
||||
SUBCRED_LEN);
|
||||
crypto_digest_free(digest);
|
||||
|
||||
memwipe(credential, 0, sizeof(credential));
|
||||
@ -909,30 +911,35 @@ hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
|
||||
* case the caller would want only one field. checksum_out MUST at least be 2
|
||||
* bytes long.
|
||||
*
|
||||
* Return 0 if parsing went well; return -1 in case of error. */
|
||||
* Return 0 if parsing went well; return -1 in case of error and if errmsg is
|
||||
* non NULL, a human readable string message is set. */
|
||||
int
|
||||
hs_parse_address(const char *address, ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out)
|
||||
hs_parse_address_no_log(const char *address, ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out,
|
||||
const char **errmsg)
|
||||
{
|
||||
char decoded[HS_SERVICE_ADDR_LEN];
|
||||
|
||||
tor_assert(address);
|
||||
|
||||
if (errmsg) {
|
||||
*errmsg = NULL;
|
||||
}
|
||||
|
||||
/* Obvious length check. */
|
||||
if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
|
||||
log_warn(LD_REND, "Service address %s has an invalid length. "
|
||||
"Expected %lu but got %lu.",
|
||||
escaped_safe_str(address),
|
||||
(unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
|
||||
(unsigned long) strlen(address));
|
||||
if (errmsg) {
|
||||
*errmsg = "Invalid length";
|
||||
}
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
/* Decode address so we can extract needed fields. */
|
||||
if (base32_decode(decoded, sizeof(decoded), address, strlen(address))
|
||||
!= sizeof(decoded)) {
|
||||
log_warn(LD_REND, "Service address %s can't be decoded.",
|
||||
escaped_safe_str(address));
|
||||
if (errmsg) {
|
||||
*errmsg = "Unable to base32 decode";
|
||||
}
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
@ -944,6 +951,22 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Same has hs_parse_address_no_log() but emits a log warning on parsing
|
||||
* failure. */
|
||||
int
|
||||
hs_parse_address(const char *address, ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out)
|
||||
{
|
||||
const char *errmsg = NULL;
|
||||
int ret = hs_parse_address_no_log(address, key_out, checksum_out,
|
||||
version_out, &errmsg);
|
||||
if (ret < 0) {
|
||||
log_warn(LD_REND, "Service address %s failed to be parsed: %s",
|
||||
escaped_safe_str(address), errmsg);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Validate a given onion address. The length, the base32 decoding, and
|
||||
* checksum are validated. Return 1 if valid else 0. */
|
||||
int
|
||||
@ -1807,6 +1830,7 @@ hs_free_all(void)
|
||||
hs_service_free_all();
|
||||
hs_cache_free_all();
|
||||
hs_client_free_all();
|
||||
hs_ob_free_all();
|
||||
}
|
||||
|
||||
/** For the given origin circuit circ, decrement the number of rendezvous
|
||||
|
@ -179,6 +179,10 @@ void hs_build_address(const struct ed25519_public_key_t *key, uint8_t version,
|
||||
int hs_address_is_valid(const char *address);
|
||||
int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out);
|
||||
int hs_parse_address_no_log(const char *address,
|
||||
struct ed25519_public_key_t *key_out,
|
||||
uint8_t *checksum_out, uint8_t *version_out,
|
||||
const char **errmsg);
|
||||
|
||||
void hs_build_blinded_pubkey(const struct ed25519_public_key_t *pubkey,
|
||||
const uint8_t *secret, size_t secret_len,
|
||||
@ -210,9 +214,10 @@ const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
|
||||
|
||||
routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32);
|
||||
|
||||
struct hs_subcredential_t;
|
||||
void hs_get_subcredential(const struct ed25519_public_key_t *identity_pk,
|
||||
const struct ed25519_public_key_t *blinded_pk,
|
||||
uint8_t *subcred_out);
|
||||
struct hs_subcredential_t *subcred_out);
|
||||
|
||||
uint64_t hs_get_previous_time_period_num(time_t now);
|
||||
uint64_t hs_get_time_period_num(time_t now);
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "feature/hs/hs_common.h"
|
||||
#include "feature/hs/hs_config.h"
|
||||
#include "feature/hs/hs_client.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/rend/rendclient.h"
|
||||
#include "feature/rend/rendservice.h"
|
||||
@ -219,6 +220,7 @@ config_has_invalid_options(const config_line_t *line_,
|
||||
"HiddenServiceEnableIntroDoSDefense",
|
||||
"HiddenServiceEnableIntroDoSRatePerSec",
|
||||
"HiddenServiceEnableIntroDoSBurstPerSec",
|
||||
"HiddenServiceOnionBalanceInstance",
|
||||
NULL /* End marker. */
|
||||
};
|
||||
|
||||
@ -317,7 +319,7 @@ config_service_v3(const config_line_t *line_,
|
||||
int have_num_ip = 0;
|
||||
bool export_circuit_id = false; /* just to detect duplicate options */
|
||||
bool dos_enabled = false, dos_rate_per_sec = false;
|
||||
bool dos_burst_per_sec = false;
|
||||
bool dos_burst_per_sec = false, ob_instance = false;
|
||||
const char *dup_opt_seen = NULL;
|
||||
const config_line_t *line;
|
||||
|
||||
@ -402,6 +404,27 @@ config_service_v3(const config_line_t *line_,
|
||||
config->intro_dos_burst_per_sec);
|
||||
continue;
|
||||
}
|
||||
if (!strcasecmp(line->key, "HiddenServiceOnionBalanceInstance")) {
|
||||
bool enabled = !!helper_parse_uint64(line->key, line->value,
|
||||
0, 1, &ok);
|
||||
if (!ok || ob_instance) {
|
||||
if (ob_instance) {
|
||||
dup_opt_seen = line->key;
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
ob_instance = true;
|
||||
if (!enabled) {
|
||||
/* Skip if this is disabled. */
|
||||
continue;
|
||||
}
|
||||
/* Option is enabled, parse config file. */
|
||||
ok = hs_ob_parse_config_file(config);
|
||||
if (!ok) {
|
||||
goto err;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* We do not load the key material for the service at this stage. This is
|
||||
|
@ -211,7 +211,7 @@ build_secret_input(const hs_descriptor_t *desc,
|
||||
memcpy(secret_input, secret_data, secret_data_len);
|
||||
offset += secret_data_len;
|
||||
/* Copy subcredential. */
|
||||
memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN);
|
||||
memcpy(secret_input + offset, desc->subcredential.subcred, DIGEST256_LEN);
|
||||
offset += DIGEST256_LEN;
|
||||
/* Copy revision counter value. */
|
||||
set_uint64(secret_input + offset,
|
||||
@ -1018,10 +1018,6 @@ 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;
|
||||
@ -1366,8 +1362,7 @@ encrypted_data_length_is_valid(size_t len)
|
||||
* and return the buffer's length. The caller should wipe and free its content
|
||||
* once done with it. This function can't fail. */
|
||||
static size_t
|
||||
build_descriptor_cookie_keys(const uint8_t *subcredential,
|
||||
size_t subcredential_len,
|
||||
build_descriptor_cookie_keys(const hs_subcredential_t *subcredential,
|
||||
const curve25519_secret_key_t *sk,
|
||||
const curve25519_public_key_t *pk,
|
||||
uint8_t **keys_out)
|
||||
@ -1389,7 +1384,7 @@ build_descriptor_cookie_keys(const uint8_t *subcredential,
|
||||
|
||||
/* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */
|
||||
xof = crypto_xof_new();
|
||||
crypto_xof_add_bytes(xof, subcredential, subcredential_len);
|
||||
crypto_xof_add_bytes(xof, subcredential->subcred, SUBCRED_LEN);
|
||||
crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed));
|
||||
crypto_xof_squeeze_bytes(xof, keystream, keystream_len);
|
||||
crypto_xof_free(xof);
|
||||
@ -1426,11 +1421,12 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc,
|
||||
sizeof(desc->superencrypted_data.auth_ephemeral_pubkey)));
|
||||
tor_assert(!fast_mem_is_zero((char *) client_auth_sk,
|
||||
sizeof(*client_auth_sk)));
|
||||
tor_assert(!fast_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN));
|
||||
tor_assert(!fast_mem_is_zero((char *) desc->subcredential.subcred,
|
||||
DIGEST256_LEN));
|
||||
|
||||
/* Get the KEYS component to derive the CLIENT-ID and COOKIE-KEY. */
|
||||
keystream_length =
|
||||
build_descriptor_cookie_keys(desc->subcredential, DIGEST256_LEN,
|
||||
build_descriptor_cookie_keys(&desc->subcredential,
|
||||
client_auth_sk,
|
||||
&desc->superencrypted_data.auth_ephemeral_pubkey,
|
||||
&keystream);
|
||||
@ -2558,7 +2554,7 @@ hs_desc_decode_plaintext(const char *encoded,
|
||||
* set to NULL. */
|
||||
hs_desc_decode_status_t
|
||||
hs_desc_decode_descriptor(const char *encoded,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
const curve25519_secret_key_t *client_auth_sk,
|
||||
hs_descriptor_t **desc_out)
|
||||
{
|
||||
@ -2576,7 +2572,7 @@ hs_desc_decode_descriptor(const char *encoded,
|
||||
goto err;
|
||||
}
|
||||
|
||||
memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
|
||||
memcpy(&desc->subcredential, subcredential, sizeof(desc->subcredential));
|
||||
|
||||
ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
|
||||
if (ret != HS_DESC_DECODE_OK) {
|
||||
@ -2666,7 +2662,7 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc,
|
||||
* symmetric only if the client auth is disabled. That is, the descriptor
|
||||
* cookie will be NULL. */
|
||||
if (!descriptor_cookie) {
|
||||
ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential,
|
||||
ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential,
|
||||
NULL, NULL);
|
||||
if (BUG(ret != HS_DESC_DECODE_OK)) {
|
||||
ret = -1;
|
||||
@ -2870,7 +2866,7 @@ hs_desc_build_fake_authorized_client(void)
|
||||
* key, and descriptor cookie, build the auth client so we can then encode the
|
||||
* descriptor for publication. client_out must be already allocated. */
|
||||
void
|
||||
hs_desc_build_authorized_client(const uint8_t *subcredential,
|
||||
hs_desc_build_authorized_client(const hs_subcredential_t *subcredential,
|
||||
const curve25519_public_key_t *client_auth_pk,
|
||||
const curve25519_secret_key_t *
|
||||
auth_ephemeral_sk,
|
||||
@ -2898,7 +2894,7 @@ hs_desc_build_authorized_client(const uint8_t *subcredential,
|
||||
|
||||
/* Get the KEYS part so we can derive the CLIENT-ID and COOKIE-KEY. */
|
||||
keystream_length =
|
||||
build_descriptor_cookie_keys(subcredential, DIGEST256_LEN,
|
||||
build_descriptor_cookie_keys(subcredential,
|
||||
auth_ephemeral_sk, client_auth_pk,
|
||||
&keystream);
|
||||
tor_assert(keystream_length > 0);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "core/or/or.h"
|
||||
#include "trunnel/ed25519_cert.h" /* needed for trunnel */
|
||||
#include "feature/nodelist/torcert.h"
|
||||
#include "core/crypto/hs_ntor.h" /* for hs_subcredential_t */
|
||||
|
||||
/* Trunnel */
|
||||
struct link_specifier_t;
|
||||
@ -238,7 +239,7 @@ typedef struct hs_descriptor_t {
|
||||
|
||||
/** Subcredentials of a service, used by the client and service to decrypt
|
||||
* the encrypted data. */
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
} hs_descriptor_t;
|
||||
|
||||
/** Return true iff the given descriptor format version is supported. */
|
||||
@ -277,7 +278,7 @@ MOCK_DECL(int,
|
||||
char **encoded_out));
|
||||
|
||||
int hs_desc_decode_descriptor(const char *encoded,
|
||||
const uint8_t *subcredential,
|
||||
const hs_subcredential_t *subcredential,
|
||||
const curve25519_secret_key_t *client_auth_sk,
|
||||
hs_descriptor_t **desc_out);
|
||||
int hs_desc_decode_plaintext(const char *encoded,
|
||||
@ -302,7 +303,7 @@ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client);
|
||||
|
||||
hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void);
|
||||
|
||||
void hs_desc_build_authorized_client(const uint8_t *subcredential,
|
||||
void hs_desc_build_authorized_client(const hs_subcredential_t *subcredential,
|
||||
const curve25519_public_key_t *
|
||||
client_auth_pk,
|
||||
const curve25519_secret_key_t *
|
||||
|
408
src/feature/hs/hs_ob.c
Normal file
408
src/feature/hs/hs_ob.c
Normal file
@ -0,0 +1,408 @@
|
||||
/* Copyright (c) 2017-2020, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_ob.c
|
||||
* \brief Implement Onion Balance specific code.
|
||||
**/
|
||||
|
||||
#define HS_OB_PRIVATE
|
||||
|
||||
#include "feature/hs/hs_service.h"
|
||||
|
||||
#include "feature/nodelist/networkstatus.h"
|
||||
#include "feature/nodelist/networkstatus_st.h"
|
||||
|
||||
#include "lib/confmgt/confmgt.h"
|
||||
#include "lib/encoding/confline.h"
|
||||
|
||||
#include "hs_ob.h"
|
||||
|
||||
/* Options config magic number. */
|
||||
#define OB_OPTIONS_MAGIC 0x631DE7EA
|
||||
|
||||
/* Helper macros. */
|
||||
#define VAR(varname, conftype, member, initvalue) \
|
||||
CONFIG_VAR_ETYPE(ob_options_t, varname, conftype, member, 0, initvalue)
|
||||
#define V(member,conftype,initvalue) \
|
||||
VAR(#member, conftype, member, initvalue)
|
||||
|
||||
/* Dummy instance of ob_options_t, used for type-checking its members with
|
||||
* CONF_CHECK_VAR_TYPE. */
|
||||
DUMMY_TYPECHECK_INSTANCE(ob_options_t);
|
||||
|
||||
/* Array of variables for the config file options. */
|
||||
static const config_var_t config_vars[] = {
|
||||
V(MasterOnionAddress, LINELIST, NULL),
|
||||
|
||||
END_OF_CONFIG_VARS
|
||||
};
|
||||
|
||||
/* "Extra" variable in the state that receives lines we can't parse. This
|
||||
* lets us preserve options from versions of Tor newer than us. */
|
||||
static const struct_member_t config_extra_vars = {
|
||||
.name = "__extra",
|
||||
.type = CONFIG_TYPE_LINELIST,
|
||||
.offset = offsetof(ob_options_t, ExtraLines),
|
||||
};
|
||||
|
||||
/* Configuration format of ob_options_t. */
|
||||
static const config_format_t config_format = {
|
||||
.size = sizeof(ob_options_t),
|
||||
.magic = {
|
||||
"ob_options_t",
|
||||
OB_OPTIONS_MAGIC,
|
||||
offsetof(ob_options_t, magic_),
|
||||
},
|
||||
.vars = config_vars,
|
||||
.extra = &config_extra_vars,
|
||||
};
|
||||
|
||||
/* Global configuration manager for the config file. */
|
||||
static config_mgr_t *config_options_mgr = NULL;
|
||||
|
||||
/* Return the configuration manager for the config file. */
|
||||
static const config_mgr_t *
|
||||
get_config_options_mgr(void)
|
||||
{
|
||||
if (PREDICT_UNLIKELY(config_options_mgr == NULL)) {
|
||||
config_options_mgr = config_mgr_new(&config_format);
|
||||
config_mgr_freeze(config_options_mgr);
|
||||
}
|
||||
return config_options_mgr;
|
||||
}
|
||||
|
||||
#define ob_option_free(val) \
|
||||
FREE_AND_NULL(ob_options_t, ob_option_free_, (val))
|
||||
|
||||
/** Helper: Free a config options object. */
|
||||
static void
|
||||
ob_option_free_(ob_options_t *opts)
|
||||
{
|
||||
if (opts == NULL) {
|
||||
return;
|
||||
}
|
||||
config_free(get_config_options_mgr(), opts);
|
||||
}
|
||||
|
||||
/** Return an allocated config options object. */
|
||||
static ob_options_t *
|
||||
ob_option_new(void)
|
||||
{
|
||||
ob_options_t *opts = config_new(get_config_options_mgr());
|
||||
config_init(get_config_options_mgr(), opts);
|
||||
return opts;
|
||||
}
|
||||
|
||||
/** Helper function: From the configuration line value which is an onion
|
||||
* address with the ".onion" extension, find the public key and put it in
|
||||
* pkey_out.
|
||||
*
|
||||
* On success, true is returned. Else, false and pkey is untouched. */
|
||||
static bool
|
||||
get_onion_public_key(const char *value, ed25519_public_key_t *pkey_out)
|
||||
{
|
||||
char address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
|
||||
|
||||
tor_assert(value);
|
||||
tor_assert(pkey_out);
|
||||
|
||||
if (strcmpend(value, ".onion")) {
|
||||
/* Not a .onion extension, bad format. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Length validation. The -1 is because sizeof() counts the NUL byte. */
|
||||
if (strlen(value) >
|
||||
(HS_SERVICE_ADDR_LEN_BASE32 + sizeof(".onion") - 1)) {
|
||||
/* Too long, bad format. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We don't want the .onion so we add 2 because size - 1 is copied with
|
||||
* strlcpy() in order to accomodate the NUL byte and sizeof() counts the NUL
|
||||
* byte so we need to remove them from the equation. */
|
||||
strlcpy(address, value, strlen(value) - sizeof(".onion") + 2);
|
||||
|
||||
if (hs_parse_address_no_log(address, pkey_out, NULL, NULL, NULL) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Success. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Parse the given ob options in opts and set the service config object
|
||||
* accordingly.
|
||||
*
|
||||
* Return 1 on success else 0. */
|
||||
static int
|
||||
ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
|
||||
{
|
||||
int ret = 0;
|
||||
config_line_t *line;
|
||||
|
||||
tor_assert(config);
|
||||
tor_assert(opts);
|
||||
|
||||
for (line = opts->MasterOnionAddress; line; line = line->next) {
|
||||
/* Allocate config list if need be. */
|
||||
if (!config->ob_master_pubkeys) {
|
||||
config->ob_master_pubkeys = smartlist_new();
|
||||
}
|
||||
ed25519_public_key_t *pubkey = tor_malloc_zero(sizeof(*pubkey));
|
||||
|
||||
if (!get_onion_public_key(line->value, pubkey)) {
|
||||
log_warn(LD_REND, "OnionBalance: MasterOnionAddress %s is invalid",
|
||||
line->value);
|
||||
tor_free(pubkey);
|
||||
goto end;
|
||||
}
|
||||
smartlist_add(config->ob_master_pubkeys, pubkey);
|
||||
log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
|
||||
line->value);
|
||||
}
|
||||
/* Success. */
|
||||
ret = 1;
|
||||
|
||||
end:
|
||||
/* No keys added, we free the list since no list means no onion balance
|
||||
* support for this tor instance. */
|
||||
if (smartlist_len(config->ob_master_pubkeys) == 0) {
|
||||
smartlist_free(config->ob_master_pubkeys);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** For the given master public key and time period, compute the subcredential
|
||||
* and put them into subcredential. The subcredential parameter needs to be at
|
||||
* least DIGEST256_LEN in size. */
|
||||
static void
|
||||
build_subcredential(const ed25519_public_key_t *pkey, uint64_t tp,
|
||||
hs_subcredential_t *subcredential)
|
||||
{
|
||||
ed25519_public_key_t blinded_pubkey;
|
||||
|
||||
tor_assert(pkey);
|
||||
tor_assert(subcredential);
|
||||
|
||||
hs_build_blinded_pubkey(pkey, NULL, 0, tp, &blinded_pubkey);
|
||||
hs_get_subcredential(pkey, &blinded_pubkey, subcredential);
|
||||
}
|
||||
|
||||
/*
|
||||
* Public API.
|
||||
*/
|
||||
|
||||
/** Return true iff the given service is configured as an onion balance
|
||||
* instance. To satisfy that condition, there must at least be one master
|
||||
* ed25519 public key configured. */
|
||||
bool
|
||||
hs_ob_service_is_instance(const hs_service_t *service)
|
||||
{
|
||||
if (BUG(service == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No list, we are not an instance. */
|
||||
if (!service->config.ob_master_pubkeys) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return smartlist_len(service->config.ob_master_pubkeys) > 0;
|
||||
}
|
||||
|
||||
/** Read and parse the config file at fname on disk. The service config object
|
||||
* is populated with the options if any.
|
||||
*
|
||||
* Return 1 on success else 0. This is to follow the "ok" convention in
|
||||
* hs_config.c. */
|
||||
int
|
||||
hs_ob_parse_config_file(hs_service_config_t *config)
|
||||
{
|
||||
static const char *fname = "ob_config";
|
||||
int ret = 0;
|
||||
char *content = NULL, *errmsg = NULL, *config_file_path = NULL;
|
||||
ob_options_t *options = NULL;
|
||||
config_line_t *lines = NULL;
|
||||
|
||||
tor_assert(config);
|
||||
|
||||
/* Read file from disk. */
|
||||
config_file_path = hs_path_from_filename(config->directory_path, fname);
|
||||
content = read_file_to_str(config_file_path, 0, NULL);
|
||||
if (!content) {
|
||||
log_warn(LD_FS, "OnionBalance: Unable to read config file %s",
|
||||
escaped(config_file_path));
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Parse lines. */
|
||||
if (config_get_lines(content, &lines, 0) < 0) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
options = ob_option_new();
|
||||
config_assign(get_config_options_mgr(), options, lines, 0, &errmsg);
|
||||
if (errmsg) {
|
||||
log_warn(LD_REND, "OnionBalance: Unable to parse config file: %s",
|
||||
errmsg);
|
||||
tor_free(errmsg);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Parse the options and set the service config object with the details. */
|
||||
ret = ob_option_parse(config, options);
|
||||
|
||||
end:
|
||||
config_free_lines(lines);
|
||||
ob_option_free(options);
|
||||
tor_free(content);
|
||||
tor_free(config_file_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Compute all possible subcredentials for every onion master key in the given
|
||||
* service config object. subcredentials_out is allocated and set as an
|
||||
* continous array containing all possible values.
|
||||
*
|
||||
* On success, return the number of subcredential put in the array which will
|
||||
* correspond to an arry of size: n * DIGEST256_LEN where DIGEST256_LEN is the
|
||||
* length of a single subcredential.
|
||||
*
|
||||
* If the given configuration object has no OB master keys configured, 0 is
|
||||
* returned and subcredentials_out is set to NULL.
|
||||
*
|
||||
* Otherwise, this can't fail. */
|
||||
STATIC size_t
|
||||
compute_subcredentials(const hs_service_t *service,
|
||||
hs_subcredential_t **subcredentials_out)
|
||||
{
|
||||
unsigned int num_pkeys, idx = 0;
|
||||
hs_subcredential_t *subcreds = NULL;
|
||||
const int steps[3] = {0, -1, 1};
|
||||
const unsigned int num_steps = ARRAY_LENGTH(steps);
|
||||
const uint64_t tp = hs_get_time_period_num(0);
|
||||
|
||||
tor_assert(service);
|
||||
tor_assert(subcredentials_out);
|
||||
/* Our caller has checked these too */
|
||||
tor_assert(service->desc_current);
|
||||
tor_assert(service->desc_next);
|
||||
|
||||
/* Make sure we are an OB instance, or bail out. */
|
||||
num_pkeys = smartlist_len(service->config.ob_master_pubkeys);
|
||||
if (!num_pkeys) {
|
||||
*subcredentials_out = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Time to build all the subcredentials for each time period: two for each
|
||||
* instance descriptor plus three for the onionbalance frontend service: the
|
||||
* previous one (-1), the current one (0) and the next one (1) for each
|
||||
* configured key in order to accomodate client and service consensus skew.
|
||||
*
|
||||
* If the client consensus after_time is at 23:00 but the service one is at
|
||||
* 01:00, the client will be using the previous time period where the
|
||||
* service will think it is the client next time period. Thus why we have
|
||||
* to try them all.
|
||||
*
|
||||
* The normal use case works because the service gets the descriptor object
|
||||
* that corresponds to the intro point's request, and because each
|
||||
* descriptor corresponds to a specific subcredential, we get the right
|
||||
* subcredential out of it, and use that to do the decryption.
|
||||
*
|
||||
* As a slight optimization, statistically, the current time period (0) will
|
||||
* be the one to work first so we'll put them first in the array to maximize
|
||||
* our chance of success. */
|
||||
|
||||
/* We use a flat array, not a smartlist_t, in order to minimize memory
|
||||
* allocation.
|
||||
*
|
||||
* Size of array is: length of a single subcredential multiplied by the
|
||||
* number of time period we need to compute and finally multiplied by the
|
||||
* total number of keys we are about to process. In other words, for each
|
||||
* key, we allocate 3 subcredential slots. Then in the end we also add two
|
||||
* subcredentials for this instance's active descriptors. */
|
||||
subcreds =
|
||||
tor_calloc((num_steps * num_pkeys) + 2, sizeof(hs_subcredential_t));
|
||||
|
||||
/* For each master pubkey we add 3 subcredentials: */
|
||||
for (unsigned int i = 0; i < num_steps; i++) {
|
||||
SMARTLIST_FOREACH_BEGIN(service->config.ob_master_pubkeys,
|
||||
const ed25519_public_key_t *, pkey) {
|
||||
build_subcredential(pkey, tp + steps[i], &subcreds[idx]);
|
||||
idx++;
|
||||
} SMARTLIST_FOREACH_END(pkey);
|
||||
}
|
||||
|
||||
/* And then in the end we add the two subcredentials of the current active
|
||||
* instance descriptors */
|
||||
memcpy(&subcreds[idx++], &service->desc_current->desc->subcredential,
|
||||
sizeof(hs_subcredential_t));
|
||||
memcpy(&subcreds[idx++], &service->desc_next->desc->subcredential,
|
||||
sizeof(hs_subcredential_t));
|
||||
|
||||
log_info(LD_REND, "Refreshing %u onionbalance keys (TP #%d).",
|
||||
idx, (int)tp);
|
||||
|
||||
*subcredentials_out = subcreds;
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are an Onionbalance instance, refresh our keys.
|
||||
*
|
||||
* If we are not an Onionbalance instance or we are not ready to do so, this
|
||||
* is a NOP.
|
||||
*
|
||||
* This function is called everytime we build a new descriptor. That's because
|
||||
* we want our Onionbalance keys to always use up-to-date subcredentials both
|
||||
* for the instance (ourselves) and for the onionbalance frontend.
|
||||
*/
|
||||
void
|
||||
hs_ob_refresh_keys(hs_service_t *service)
|
||||
{
|
||||
hs_subcredential_t *ob_subcreds = NULL;
|
||||
size_t num_subcreds;
|
||||
|
||||
tor_assert(service);
|
||||
|
||||
/* Don't do any of this if we are not configured as an OB instance */
|
||||
if (!hs_ob_service_is_instance(service)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need both service descriptors created to make onionbalance keys.
|
||||
*
|
||||
* That's because we fetch our own (the instance's) subcredentials from our
|
||||
* own descriptors which should always include the latest subcredentials that
|
||||
* clients would use.
|
||||
*
|
||||
* This function is called with each descriptor build, so we will be
|
||||
* eventually be called when both descriptors are created. */
|
||||
if (!service->desc_current || !service->desc_next) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get a new set of subcreds */
|
||||
num_subcreds = compute_subcredentials(service, &ob_subcreds);
|
||||
if (BUG(!num_subcreds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Delete old subcredentials if any */
|
||||
if (service->ob_subcreds) {
|
||||
tor_free(service->ob_subcreds);
|
||||
}
|
||||
|
||||
service->ob_subcreds = ob_subcreds;
|
||||
service->n_ob_subcreds = num_subcreds;
|
||||
}
|
||||
|
||||
/** Free any memory allocated by the onionblance subsystem. */
|
||||
void
|
||||
hs_ob_free_all(void)
|
||||
{
|
||||
config_mgr_free(config_options_mgr);
|
||||
}
|
40
src/feature/hs/hs_ob.h
Normal file
40
src/feature/hs/hs_ob.h
Normal file
@ -0,0 +1,40 @@
|
||||
/* Copyright (c) 2020, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file hs_ob.h
|
||||
* \brief Header file for the specific code for onion balance.
|
||||
**/
|
||||
|
||||
#ifndef TOR_HS_OB_H
|
||||
#define TOR_HS_OB_H
|
||||
|
||||
#include "hs_service.h"
|
||||
|
||||
bool hs_ob_service_is_instance(const hs_service_t *service);
|
||||
|
||||
int hs_ob_parse_config_file(hs_service_config_t *config);
|
||||
|
||||
struct hs_subcredential_t;
|
||||
|
||||
void hs_ob_free_all(void);
|
||||
|
||||
void hs_ob_refresh_keys(hs_service_t *service);
|
||||
|
||||
#ifdef HS_OB_PRIVATE
|
||||
|
||||
STATIC size_t compute_subcredentials(const hs_service_t *service,
|
||||
struct hs_subcredential_t **subcredentials);
|
||||
|
||||
typedef struct ob_options_t {
|
||||
/** Magic number to identify the structure in memory. */
|
||||
uint32_t magic_;
|
||||
/** Master Onion Address(es). */
|
||||
struct config_line_t *MasterOnionAddress;
|
||||
/** Extra Lines for configuration we might not know. */
|
||||
struct config_line_t *ExtraLines;
|
||||
} ob_options_t;
|
||||
|
||||
#endif /* defined(HS_OB_PRIVATE) */
|
||||
|
||||
#endif /* !defined(TOR_HS_OB_H) */
|
@ -41,6 +41,7 @@
|
||||
#include "feature/hs/hs_intropoint.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/hs/hs_stats.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
|
||||
#include "feature/dircommon/dir_connection_st.h"
|
||||
#include "core/or/edge_connection_st.h"
|
||||
@ -267,6 +268,11 @@ service_clear_config(hs_service_config_t *config)
|
||||
service_authorized_client_free(p));
|
||||
smartlist_free(config->clients);
|
||||
}
|
||||
if (config->ob_master_pubkeys) {
|
||||
SMARTLIST_FOREACH(config->ob_master_pubkeys, ed25519_public_key_t *, k,
|
||||
tor_free(k));
|
||||
smartlist_free(config->ob_master_pubkeys);
|
||||
}
|
||||
memset(config, 0, sizeof(*config));
|
||||
}
|
||||
|
||||
@ -1764,7 +1770,8 @@ build_service_desc_superencrypted(const hs_service_t *service,
|
||||
sizeof(curve25519_public_key_t));
|
||||
|
||||
/* Test that subcred is not zero because we might use it below */
|
||||
if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) {
|
||||
if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential.subcred,
|
||||
DIGEST256_LEN))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1781,7 +1788,7 @@ build_service_desc_superencrypted(const hs_service_t *service,
|
||||
|
||||
/* Prepare the client for descriptor and then add to the list in the
|
||||
* superencrypted part of the descriptor */
|
||||
hs_desc_build_authorized_client(desc->desc->subcredential,
|
||||
hs_desc_build_authorized_client(&desc->desc->subcredential,
|
||||
&client->client_pk,
|
||||
&desc->auth_ephemeral_kp.seckey,
|
||||
desc->descriptor_cookie, desc_client);
|
||||
@ -1837,7 +1844,7 @@ build_service_desc_plaintext(const hs_service_t *service,
|
||||
|
||||
/* Set the subcredential. */
|
||||
hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
|
||||
desc->desc->subcredential);
|
||||
&desc->desc->subcredential);
|
||||
|
||||
plaintext = &desc->desc->plaintext_data;
|
||||
|
||||
@ -1980,9 +1987,15 @@ build_service_descriptor(hs_service_t *service, uint64_t time_period_num,
|
||||
|
||||
/* Assign newly built descriptor to the next slot. */
|
||||
*desc_out = desc;
|
||||
|
||||
/* Fire a CREATED control port event. */
|
||||
hs_control_desc_event_created(service->onion_address,
|
||||
&desc->blinded_kp.pubkey);
|
||||
|
||||
/* If we are an onionbalance instance, we refresh our keys when we rotate
|
||||
* descriptors. */
|
||||
hs_ob_refresh_keys(service);
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
@ -3369,7 +3382,7 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload,
|
||||
|
||||
/* The following will parse, decode and launch the rendezvous point circuit.
|
||||
* Both current and legacy cells are handled. */
|
||||
if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential,
|
||||
if (hs_circ_handle_introduce2(service, circ, ip, &desc->desc->subcredential,
|
||||
payload, payload_len) < 0) {
|
||||
goto err;
|
||||
}
|
||||
@ -4042,6 +4055,11 @@ hs_service_free_(hs_service_t *service)
|
||||
replaycache_free(service->state.replay_cache_rend_cookie);
|
||||
}
|
||||
|
||||
/* Free onionbalance subcredentials (if any) */
|
||||
if (service->ob_subcreds) {
|
||||
tor_free(service->ob_subcreds);
|
||||
}
|
||||
|
||||
/* Wipe service keys. */
|
||||
memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));
|
||||
|
||||
|
@ -248,10 +248,14 @@ typedef struct hs_service_config_t {
|
||||
/** Does this service export the circuit ID of its clients? */
|
||||
hs_circuit_id_protocol_t circuit_id_protocol;
|
||||
|
||||
/* DoS defenses. For the ESTABLISH_INTRO cell extension. */
|
||||
/** DoS defenses. For the ESTABLISH_INTRO cell extension. */
|
||||
unsigned int has_dos_defense_enabled : 1;
|
||||
uint32_t intro_dos_rate_per_sec;
|
||||
uint32_t intro_dos_burst_per_sec;
|
||||
|
||||
/** If set, contains the Onion Balance master ed25519 public key (taken from
|
||||
* an .onion addresses) that this tor instance serves as backend. */
|
||||
smartlist_t *ob_master_pubkeys;
|
||||
} hs_service_config_t;
|
||||
|
||||
/** Service state. */
|
||||
@ -301,8 +305,13 @@ typedef struct hs_service_t {
|
||||
/** Next descriptor. */
|
||||
hs_service_descriptor_t *desc_next;
|
||||
|
||||
/* XXX: Credential (client auth.) #20700. */
|
||||
|
||||
/* If this is an onionbalance instance, this is an array of subcredentials
|
||||
* that should be used when decrypting an INTRO2 cell. If this is not an
|
||||
* onionbalance instance, this is NULL.
|
||||
* See [ONIONBALANCE] section in rend-spec-v3.txt for more details . */
|
||||
hs_subcredential_t *ob_subcreds;
|
||||
/* Number of OB subcredentials */
|
||||
size_t n_ob_subcreds;
|
||||
} hs_service_t;
|
||||
|
||||
/** For the service global hash map, we define a specific type for it which
|
||||
|
@ -13,6 +13,7 @@ LIBTOR_APP_A_SOURCES += \
|
||||
src/feature/hs/hs_dos.c \
|
||||
src/feature/hs/hs_ident.c \
|
||||
src/feature/hs/hs_intropoint.c \
|
||||
src/feature/hs/hs_ob.c \
|
||||
src/feature/hs/hs_service.c \
|
||||
src/feature/hs/hs_stats.c
|
||||
|
||||
@ -30,6 +31,7 @@ noinst_HEADERS += \
|
||||
src/feature/hs/hs_dos.h \
|
||||
src/feature/hs/hs_ident.h \
|
||||
src/feature/hs/hs_intropoint.h \
|
||||
src/feature/hs/hs_ob.h \
|
||||
src/feature/hs/hs_service.h \
|
||||
src/feature/hs/hs_stats.h \
|
||||
src/feature/hs/hsdir_index_st.h
|
||||
|
@ -1240,8 +1240,8 @@ node_get_rsa_id_digest(const node_t *node)
|
||||
* If node is NULL, returns an empty smartlist.
|
||||
*
|
||||
* The smartlist must be freed using link_specifier_smartlist_free(). */
|
||||
smartlist_t *
|
||||
node_get_link_specifier_smartlist(const node_t *node, bool direct_conn)
|
||||
MOCK_IMPL(smartlist_t *,
|
||||
node_get_link_specifier_smartlist,(const node_t *node, bool direct_conn))
|
||||
{
|
||||
link_specifier_t *ls;
|
||||
tor_addr_port_t ap;
|
||||
|
@ -80,8 +80,8 @@ int node_supports_ed25519_hs_intro(const node_t *node);
|
||||
int node_supports_v3_rendezvous_point(const node_t *node);
|
||||
int node_supports_establish_intro_dos_extension(const node_t *node);
|
||||
const uint8_t *node_get_rsa_id_digest(const node_t *node);
|
||||
smartlist_t *node_get_link_specifier_smartlist(const node_t *node,
|
||||
bool direct_conn);
|
||||
MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
|
||||
bool direct_conn));
|
||||
void link_specifier_smartlist_free_(smartlist_t *ls_list);
|
||||
#define link_specifier_smartlist_free(ls_list) \
|
||||
FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list))
|
||||
|
@ -279,3 +279,30 @@ select_array_member_cumulative_timei(const uint64_t *entries, int n_entries,
|
||||
|
||||
return i_chosen;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <b>s</b> is true, then copy <b>n</b> bytes from <b>src</b> to
|
||||
* <b>dest</b>. Otherwise leave <b>dest</b> alone.
|
||||
*
|
||||
* This function behaves the same as
|
||||
*
|
||||
* if (s)
|
||||
* memcpy(dest, src, n);
|
||||
*
|
||||
* except that it tries to run in the same amount of time whether <b>s</b> is
|
||||
* true or not.
|
||||
**/
|
||||
void
|
||||
memcpy_if_true_timei(bool s, void *dest, const void *src, size_t n)
|
||||
{
|
||||
// If s is true, mask will be ~0. If s is false, mask will be 0.
|
||||
const char mask = (char) -(signed char)s;
|
||||
|
||||
char *destp = dest;
|
||||
const char *srcp = src;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
*destp = (*destp & ~mask) | (*srcp & mask);
|
||||
++destp;
|
||||
++srcp;
|
||||
}
|
||||
}
|
||||
|
@ -73,4 +73,6 @@ int select_array_member_cumulative_timei(const uint64_t *entries,
|
||||
int n_entries,
|
||||
uint64_t total, uint64_t rand_val);
|
||||
|
||||
void memcpy_if_true_timei(bool s, void *dest, const void *src, size_t n);
|
||||
|
||||
#endif /* !defined(TOR_DI_OPS_H) */
|
||||
|
@ -85,12 +85,12 @@ int
|
||||
fuzz_main(const uint8_t *data, size_t sz)
|
||||
{
|
||||
hs_descriptor_t *desc = NULL;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
char *fuzzing_data = tor_memdup_nulterm(data, sz);
|
||||
memset(subcredential, 'A', sizeof(subcredential));
|
||||
memset(&subcredential, 'A', sizeof(subcredential));
|
||||
|
||||
hs_desc_decode_descriptor(fuzzing_data, subcredential, NULL, &desc);
|
||||
hs_desc_decode_descriptor(fuzzing_data, &subcredential, NULL, &desc);
|
||||
if (desc) {
|
||||
log_debug(LD_GENERAL, "Decoding okay");
|
||||
hs_descriptor_free(desc);
|
||||
@ -101,4 +101,3 @@ fuzz_main(const uint8_t *data, size_t sz)
|
||||
tor_free(fuzzing_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,22 @@
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "test/hs_test_helpers.h"
|
||||
|
||||
/**
|
||||
* Create an introduction point taken straight out of an HSv3 descriptor.
|
||||
*
|
||||
* Use 'signing_kp' to sign the introduction point certificates.
|
||||
*
|
||||
* If 'intro_auth_kp' is provided use that as the introduction point
|
||||
* authentication keypair, otherwise generate one on the fly.
|
||||
*
|
||||
* If 'intro_enc_kp' is provided use that as the introduction point encryption
|
||||
* keypair, otherwise generate one on the fly.
|
||||
*/
|
||||
hs_desc_intro_point_t *
|
||||
hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
const char *addr, int legacy)
|
||||
const char *addr, int legacy,
|
||||
const ed25519_keypair_t *intro_auth_kp,
|
||||
const curve25519_keypair_t *intro_enc_kp)
|
||||
{
|
||||
int ret;
|
||||
ed25519_keypair_t auth_kp;
|
||||
@ -56,8 +69,12 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
smartlist_add(ip->link_specifiers, ls_ip);
|
||||
}
|
||||
|
||||
ret = ed25519_keypair_generate(&auth_kp, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
if (intro_auth_kp) {
|
||||
memcpy(&auth_kp, intro_auth_kp, sizeof(ed25519_keypair_t));
|
||||
} else {
|
||||
ret = ed25519_keypair_generate(&auth_kp, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
}
|
||||
ip->auth_key_cert = tor_cert_create(signing_kp, CERT_TYPE_AUTH_HS_IP_KEY,
|
||||
&auth_kp.pubkey, now,
|
||||
HS_DESC_CERT_LIFETIME,
|
||||
@ -85,8 +102,12 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
ed25519_keypair_t ed25519_kp;
|
||||
tor_cert_t *cross_cert;
|
||||
|
||||
ret = curve25519_keypair_generate(&curve25519_kp, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
if (intro_enc_kp) {
|
||||
memcpy(&curve25519_kp, intro_enc_kp, sizeof(curve25519_keypair_t));
|
||||
} else {
|
||||
ret = curve25519_keypair_generate(&curve25519_kp, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
}
|
||||
ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit,
|
||||
&curve25519_kp);
|
||||
cross_cert = tor_cert_create(signing_kp, CERT_TYPE_CROSS_HS_IP_KEYS,
|
||||
@ -95,6 +116,8 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
CERT_FLAG_INCLUDE_SIGNING_KEY);
|
||||
tt_assert(cross_cert);
|
||||
ip->enc_key_cert = cross_cert;
|
||||
memcpy(ip->enc_key.public_key, curve25519_kp.pubkey.public_key,
|
||||
CURVE25519_PUBKEY_LEN);
|
||||
}
|
||||
|
||||
intro_point = ip;
|
||||
@ -140,7 +163,7 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
|
||||
|
||||
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
|
||||
desc->subcredential);
|
||||
&desc->subcredential);
|
||||
|
||||
/* Setup superencrypted data section. */
|
||||
ret = curve25519_keypair_generate(&auth_ephemeral_kp, 0);
|
||||
@ -165,13 +188,17 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
if (!no_ip) {
|
||||
/* Add four intro points. */
|
||||
smartlist_add(desc->encrypted_data.intro_points,
|
||||
hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0));
|
||||
hs_helper_build_intro_point(signing_kp, now, "1.2.3.4", 0,
|
||||
NULL, NULL));
|
||||
smartlist_add(desc->encrypted_data.intro_points,
|
||||
hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0));
|
||||
hs_helper_build_intro_point(signing_kp, now, "[2600::1]", 0,
|
||||
NULL, NULL));
|
||||
smartlist_add(desc->encrypted_data.intro_points,
|
||||
hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1));
|
||||
hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1,
|
||||
NULL, NULL));
|
||||
smartlist_add(desc->encrypted_data.intro_points,
|
||||
hs_helper_build_intro_point(signing_kp, now, "5.6.7.8", 1));
|
||||
hs_helper_build_intro_point(signing_kp, now, "5.6.7.8", 1,
|
||||
NULL, NULL));
|
||||
}
|
||||
|
||||
descp = desc;
|
||||
@ -186,7 +213,7 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
|
||||
* 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)
|
||||
hs_subcredential_t *subcred_out)
|
||||
{
|
||||
ed25519_keypair_t blinded_kp;
|
||||
uint64_t current_time_period = hs_get_time_period_num(approx_time());
|
||||
@ -233,7 +260,7 @@ hs_helper_build_hs_desc_with_client_auth(
|
||||
memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey,
|
||||
&auth_ephemeral_kp.pubkey, sizeof(curve25519_public_key_t));
|
||||
|
||||
hs_desc_build_authorized_client(desc->subcredential, client_pk,
|
||||
hs_desc_build_authorized_client(&desc->subcredential, client_pk,
|
||||
&auth_ephemeral_kp.seckey,
|
||||
descriptor_cookie, desc_client);
|
||||
smartlist_add(desc->superencrypted_data.clients, desc_client);
|
||||
|
@ -8,9 +8,11 @@
|
||||
#include "feature/hs/hs_descriptor.h"
|
||||
|
||||
/* Set of functions to help build and test descriptors. */
|
||||
hs_desc_intro_point_t *hs_helper_build_intro_point(
|
||||
const ed25519_keypair_t *signing_kp, time_t now,
|
||||
const char *addr, int legacy);
|
||||
hs_desc_intro_point_t *
|
||||
hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now,
|
||||
const char *addr, int legacy,
|
||||
const ed25519_keypair_t *intro_auth_kp,
|
||||
const curve25519_keypair_t *intro_enc_kp);
|
||||
hs_descriptor_t *hs_helper_build_hs_desc_no_ip(
|
||||
const ed25519_keypair_t *signing_kp);
|
||||
hs_descriptor_t *hs_helper_build_hs_desc_with_ip(
|
||||
@ -21,12 +23,11 @@ hs_descriptor_t *hs_helper_build_hs_desc_with_client_auth(
|
||||
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);
|
||||
struct hs_subcredential_t;
|
||||
void hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
|
||||
struct hs_subcredential_t *subcred_out);
|
||||
|
||||
void hs_helper_add_client_auth(const ed25519_public_key_t *service_pk,
|
||||
const curve25519_secret_key_t *client_sk);
|
||||
|
||||
#endif /* !defined(TOR_HS_TEST_HELPERS_H) */
|
||||
|
||||
|
@ -179,6 +179,7 @@ src_test_test_SOURCES += \
|
||||
src/test/test_hs_client.c \
|
||||
src/test/test_hs_intropoint.c \
|
||||
src/test/test_hs_control.c \
|
||||
src/test/test_hs_ob.c \
|
||||
src/test/test_handles.c \
|
||||
src/test/test_hs_cache.c \
|
||||
src/test/test_hs_descriptor.c \
|
||||
|
@ -721,6 +721,7 @@ struct testgroup_t testgroups[] = {
|
||||
{ "hs_dos/", hs_dos_tests },
|
||||
{ "hs_intropoint/", hs_intropoint_tests },
|
||||
{ "hs_ntor/", hs_ntor_tests },
|
||||
{ "hs_ob/", hs_ob_tests },
|
||||
{ "hs_service/", hs_service_tests },
|
||||
{ "introduce/", introduce_tests },
|
||||
{ "keypin/", keypin_tests },
|
||||
|
@ -141,6 +141,7 @@ extern struct testcase_t hs_descriptor[];
|
||||
extern struct testcase_t hs_dos_tests[];
|
||||
extern struct testcase_t hs_intropoint_tests[];
|
||||
extern struct testcase_t hs_ntor_tests[];
|
||||
extern struct testcase_t hs_ob_tests[];
|
||||
extern struct testcase_t hs_service_tests[];
|
||||
extern struct testcase_t hs_tests[];
|
||||
extern struct testcase_t introduce_tests[];
|
||||
|
@ -370,7 +370,7 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
hs_descriptor_t *published_desc = NULL;
|
||||
char *published_desc_str = NULL;
|
||||
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
char *received_desc_str = NULL;
|
||||
hs_descriptor_t *received_desc = NULL;
|
||||
|
||||
@ -407,11 +407,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);
|
||||
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,
|
||||
subcredential, NULL, &received_desc);
|
||||
&subcredential, NULL, &received_desc);
|
||||
tt_int_op(retval, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(received_desc);
|
||||
|
||||
@ -444,7 +444,7 @@ test_hsdir_revision_counter_check(void *arg)
|
||||
received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
|
||||
|
||||
retval = hs_desc_decode_descriptor(received_desc_str,
|
||||
subcredential, NULL, &received_desc);
|
||||
&subcredential, NULL, &received_desc);
|
||||
tt_int_op(retval, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(received_desc);
|
||||
|
||||
@ -476,7 +476,7 @@ test_client_cache(void *arg)
|
||||
ed25519_keypair_t signing_kp;
|
||||
hs_descriptor_t *published_desc = NULL;
|
||||
char *published_desc_str = NULL;
|
||||
uint8_t wanted_subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t wanted_subcredential;
|
||||
response_handler_args_t *args = NULL;
|
||||
dir_connection_t *conn = NULL;
|
||||
|
||||
@ -505,8 +505,10 @@ test_client_cache(void *arg)
|
||||
retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
|
||||
NULL, &published_desc_str);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
memcpy(wanted_subcredential, published_desc->subcredential, DIGEST256_LEN);
|
||||
tt_assert(!fast_mem_is_zero((char*)wanted_subcredential, DIGEST256_LEN));
|
||||
memcpy(&wanted_subcredential, &published_desc->subcredential,
|
||||
sizeof(hs_subcredential_t));
|
||||
tt_assert(!fast_mem_is_zero((char*)wanted_subcredential.subcred,
|
||||
DIGEST256_LEN));
|
||||
}
|
||||
|
||||
/* Test handle_response_fetch_hsdesc_v3() */
|
||||
@ -540,8 +542,9 @@ test_client_cache(void *arg)
|
||||
const hs_descriptor_t *cached_desc = NULL;
|
||||
cached_desc = hs_cache_lookup_as_client(&signing_kp.pubkey);
|
||||
tt_assert(cached_desc);
|
||||
tt_mem_op(cached_desc->subcredential, OP_EQ, wanted_subcredential,
|
||||
DIGEST256_LEN);
|
||||
tt_mem_op(cached_desc->subcredential.subcred,
|
||||
OP_EQ, wanted_subcredential.subcred,
|
||||
SUBCRED_LEN);
|
||||
}
|
||||
|
||||
/* Progress time to next TP and check that desc was cleaned */
|
||||
|
@ -433,9 +433,10 @@ test_client_pick_intro(void *arg)
|
||||
const hs_descriptor_t *fetched_desc =
|
||||
hs_cache_lookup_as_client(&service_kp.pubkey);
|
||||
tt_assert(fetched_desc);
|
||||
tt_mem_op(fetched_desc->subcredential, OP_EQ, desc->subcredential,
|
||||
DIGEST256_LEN);
|
||||
tt_assert(!fast_mem_is_zero((char*)fetched_desc->subcredential,
|
||||
tt_mem_op(fetched_desc->subcredential.subcred,
|
||||
OP_EQ, desc->subcredential.subcred,
|
||||
SUBCRED_LEN);
|
||||
tt_assert(!fast_mem_is_zero((char*)fetched_desc->subcredential.subcred,
|
||||
DIGEST256_LEN));
|
||||
tor_free(encoded);
|
||||
}
|
||||
|
@ -53,14 +53,14 @@ test_validate_address(void *arg)
|
||||
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");
|
||||
expect_log_msg_containing("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");
|
||||
expect_log_msg_containing("Invalid length");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Invalid checksum (taken from prop224) */
|
||||
@ -83,7 +83,7 @@ test_validate_address(void *arg)
|
||||
ret = hs_address_is_valid(
|
||||
"????????????????????????????????????????????????????????");
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
expect_log_msg_containing("can't be decoded");
|
||||
expect_log_msg_containing("Unable to base32 decode");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Valid address. */
|
||||
|
@ -221,7 +221,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];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
(void) arg;
|
||||
|
||||
@ -230,10 +230,10 @@ test_decode_descriptor(void *arg)
|
||||
desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
|
||||
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp,
|
||||
subcredential);
|
||||
&subcredential);
|
||||
|
||||
/* Give some bad stuff to the decoding function. */
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential,
|
||||
ret = hs_desc_decode_descriptor("hladfjlkjadf", &subcredential,
|
||||
NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_PLAINTEXT_ERROR);
|
||||
|
||||
@ -241,7 +241,7 @@ test_decode_descriptor(void *arg)
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(encoded);
|
||||
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, &subcredential, NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(decoded);
|
||||
|
||||
@ -253,7 +253,7 @@ test_decode_descriptor(void *arg)
|
||||
ret = ed25519_keypair_generate(&signing_kp_no_ip, 0);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip,
|
||||
subcredential);
|
||||
&subcredential);
|
||||
desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip);
|
||||
tt_assert(desc_no_ip);
|
||||
tor_free(encoded);
|
||||
@ -262,7 +262,7 @@ test_decode_descriptor(void *arg)
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
tt_assert(encoded);
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential, NULL, &decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, &subcredential, NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(decoded);
|
||||
}
|
||||
@ -286,14 +286,14 @@ test_decode_descriptor(void *arg)
|
||||
&auth_ephemeral_kp.pubkey, CURVE25519_PUBKEY_LEN);
|
||||
|
||||
hs_helper_get_subcred_from_identity_keypair(&signing_kp,
|
||||
subcredential);
|
||||
&subcredential);
|
||||
|
||||
/* Build and add the auth client to the descriptor. */
|
||||
clients = desc->superencrypted_data.clients;
|
||||
if (!clients) {
|
||||
clients = smartlist_new();
|
||||
}
|
||||
hs_desc_build_authorized_client(subcredential,
|
||||
hs_desc_build_authorized_client(&subcredential,
|
||||
&client_kp.pubkey,
|
||||
&auth_ephemeral_kp.seckey,
|
||||
descriptor_cookie, client);
|
||||
@ -315,21 +315,21 @@ test_decode_descriptor(void *arg)
|
||||
|
||||
/* If we do not have the client secret key, the decoding must fail. */
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
ret = hs_desc_decode_descriptor(encoded, &subcredential,
|
||||
NULL, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_NEED_CLIENT_AUTH);
|
||||
tt_assert(!decoded);
|
||||
|
||||
/* If we have an invalid client secret key, the decoding must fail. */
|
||||
hs_descriptor_free(decoded);
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
ret = hs_desc_decode_descriptor(encoded, &subcredential,
|
||||
&invalid_client_kp.seckey, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_BAD_CLIENT_AUTH);
|
||||
tt_assert(!decoded);
|
||||
|
||||
/* If we have the client secret key, the decoding must succeed and the
|
||||
* decoded descriptor must be correct. */
|
||||
ret = hs_desc_decode_descriptor(encoded, subcredential,
|
||||
ret = hs_desc_decode_descriptor(encoded, &subcredential,
|
||||
&client_kp.seckey, &decoded);
|
||||
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
|
||||
tt_assert(decoded);
|
||||
@ -762,7 +762,7 @@ test_build_authorized_client(void *arg)
|
||||
"07d087f1d8c68393721f6e70316d3b29";
|
||||
const char client_pubkey_b16[] =
|
||||
"8c1298fa6050e372f8598f6deca32e27b0ad457741422c2629ebb132cf7fae37";
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
char *mem_op_hex_tmp=NULL;
|
||||
|
||||
(void) arg;
|
||||
@ -774,7 +774,7 @@ test_build_authorized_client(void *arg)
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
curve25519_public_key_generate(&client_auth_pk, &client_auth_sk);
|
||||
|
||||
memset(subcredential, 42, sizeof(subcredential));
|
||||
memset(subcredential.subcred, 42, sizeof(subcredential));
|
||||
|
||||
desc_client = tor_malloc_zero(sizeof(hs_desc_authorized_client_t));
|
||||
|
||||
@ -795,7 +795,7 @@ test_build_authorized_client(void *arg)
|
||||
|
||||
testing_enable_prefilled_rng("\x01", 1);
|
||||
|
||||
hs_desc_build_authorized_client(subcredential,
|
||||
hs_desc_build_authorized_client(&subcredential,
|
||||
&client_auth_pk, &auth_ephemeral_sk,
|
||||
descriptor_cookie, desc_client);
|
||||
|
||||
|
@ -23,7 +23,7 @@ test_hs_ntor(void *arg)
|
||||
{
|
||||
int retval;
|
||||
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
ed25519_keypair_t service_intro_auth_keypair;
|
||||
curve25519_keypair_t service_intro_enc_keypair;
|
||||
@ -42,7 +42,7 @@ test_hs_ntor(void *arg)
|
||||
/* Generate fake data for this unittest */
|
||||
{
|
||||
/* Generate fake subcredential */
|
||||
memset(subcredential, 'Z', DIGEST256_LEN);
|
||||
memset(subcredential.subcred, 'Z', DIGEST256_LEN);
|
||||
|
||||
/* service */
|
||||
curve25519_keypair_generate(&service_intro_enc_keypair, 0);
|
||||
@ -57,7 +57,7 @@ test_hs_ntor(void *arg)
|
||||
hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&service_intro_enc_keypair.pubkey,
|
||||
&client_ephemeral_enc_keypair,
|
||||
subcredential,
|
||||
&subcredential,
|
||||
&client_hs_ntor_intro_cell_keys);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
@ -66,7 +66,7 @@ test_hs_ntor(void *arg)
|
||||
hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
|
||||
&service_intro_enc_keypair,
|
||||
&client_ephemeral_enc_keypair.pubkey,
|
||||
subcredential,
|
||||
&subcredential,
|
||||
&service_hs_ntor_intro_cell_keys);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
|
@ -53,7 +53,7 @@ client1(int argc, char **argv)
|
||||
curve25519_public_key_t intro_enc_pubkey;
|
||||
ed25519_public_key_t intro_auth_pubkey;
|
||||
curve25519_keypair_t client_ephemeral_enc_keypair;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
/* Output */
|
||||
hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys;
|
||||
@ -65,7 +65,7 @@ client1(int argc, char **argv)
|
||||
BASE16(3, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN);
|
||||
BASE16(4, client_ephemeral_enc_keypair.seckey.secret_key,
|
||||
CURVE25519_SECKEY_LEN);
|
||||
BASE16(5, subcredential, DIGEST256_LEN);
|
||||
BASE16(5, subcredential.subcred, DIGEST256_LEN);
|
||||
|
||||
/* Generate keypair */
|
||||
curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey,
|
||||
@ -74,7 +74,7 @@ client1(int argc, char **argv)
|
||||
retval = hs_ntor_client_get_introduce1_keys(&intro_auth_pubkey,
|
||||
&intro_enc_pubkey,
|
||||
&client_ephemeral_enc_keypair,
|
||||
subcredential,
|
||||
&subcredential,
|
||||
&hs_ntor_intro_cell_keys);
|
||||
if (retval < 0) {
|
||||
goto done;
|
||||
@ -106,7 +106,7 @@ server1(int argc, char **argv)
|
||||
curve25519_keypair_t intro_enc_keypair;
|
||||
ed25519_public_key_t intro_auth_pubkey;
|
||||
curve25519_public_key_t client_ephemeral_enc_pubkey;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
/* Output */
|
||||
hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys;
|
||||
@ -119,7 +119,7 @@ server1(int argc, char **argv)
|
||||
BASE16(2, intro_auth_pubkey.pubkey, ED25519_PUBKEY_LEN);
|
||||
BASE16(3, intro_enc_keypair.seckey.secret_key, CURVE25519_SECKEY_LEN);
|
||||
BASE16(4, client_ephemeral_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN);
|
||||
BASE16(5, subcredential, DIGEST256_LEN);
|
||||
BASE16(5, subcredential.subcred, DIGEST256_LEN);
|
||||
|
||||
/* Generate keypair */
|
||||
curve25519_public_key_generate(&intro_enc_keypair.pubkey,
|
||||
@ -130,7 +130,7 @@ server1(int argc, char **argv)
|
||||
retval = hs_ntor_service_get_introduce1_keys(&intro_auth_pubkey,
|
||||
&intro_enc_keypair,
|
||||
&client_ephemeral_enc_pubkey,
|
||||
subcredential,
|
||||
&subcredential,
|
||||
&hs_ntor_intro_cell_keys);
|
||||
if (retval < 0) {
|
||||
goto done;
|
||||
@ -188,7 +188,7 @@ client2(int argc, char **argv)
|
||||
ed25519_public_key_t intro_auth_pubkey;
|
||||
curve25519_keypair_t client_ephemeral_enc_keypair;
|
||||
curve25519_public_key_t service_ephemeral_rend_pubkey;
|
||||
uint8_t subcredential[DIGEST256_LEN];
|
||||
hs_subcredential_t subcredential;
|
||||
|
||||
/* Output */
|
||||
hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys;
|
||||
@ -201,7 +201,7 @@ client2(int argc, char **argv)
|
||||
CURVE25519_SECKEY_LEN);
|
||||
BASE16(4, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN);
|
||||
BASE16(5, service_ephemeral_rend_pubkey.public_key, CURVE25519_PUBKEY_LEN);
|
||||
BASE16(6, subcredential, DIGEST256_LEN);
|
||||
BASE16(6, subcredential.subcred, DIGEST256_LEN);
|
||||
|
||||
/* Generate keypair */
|
||||
curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey,
|
||||
|
268
src/test/test_hs_ob.c
Normal file
268
src/test/test_hs_ob.c
Normal file
@ -0,0 +1,268 @@
|
||||
/* Copyright (c) 2020, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
/**
|
||||
* \file test_hs_ob.c
|
||||
* \brief Test hidden service onion balance functionality.
|
||||
*/
|
||||
|
||||
#define CONFIG_PRIVATE
|
||||
#define HS_SERVICE_PRIVATE
|
||||
#define HS_OB_PRIVATE
|
||||
|
||||
#include "test/test.h"
|
||||
#include "test/test_helpers.h"
|
||||
#include "test/log_test_helpers.h"
|
||||
|
||||
#include "app/config/config.h"
|
||||
#include "feature/hs/hs_config.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/nodelist/networkstatus.h"
|
||||
#include "feature/nodelist/networkstatus_st.h"
|
||||
|
||||
static ed25519_keypair_t onion_addr_kp_1;
|
||||
static char onion_addr_1[HS_SERVICE_ADDR_LEN_BASE32 + 1];
|
||||
|
||||
static ed25519_keypair_t onion_addr_kp_2;
|
||||
static char onion_addr_2[HS_SERVICE_ADDR_LEN_BASE32 + 1];
|
||||
|
||||
static bool config_is_good = true;
|
||||
|
||||
static int
|
||||
helper_tor_config(const char *conf)
|
||||
{
|
||||
int ret = -1;
|
||||
or_options_t *options = helper_parse_options(conf);
|
||||
tt_assert(options);
|
||||
ret = hs_config_service_all(options, 0);
|
||||
done:
|
||||
or_options_free(options);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static networkstatus_t mock_ns;
|
||||
|
||||
static networkstatus_t *
|
||||
mock_networkstatus_get_live_consensus(time_t now)
|
||||
{
|
||||
(void) now;
|
||||
return &mock_ns;
|
||||
}
|
||||
|
||||
static char *
|
||||
mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out)
|
||||
{
|
||||
char *ret = NULL;
|
||||
|
||||
(void) flags;
|
||||
(void) stat_out;
|
||||
|
||||
if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR "ob_config"))) {
|
||||
if (config_is_good) {
|
||||
tor_asprintf(&ret, "MasterOnionAddress %s.onion\n"
|
||||
"MasterOnionAddress %s.onion\n",
|
||||
onion_addr_1, onion_addr_2);
|
||||
} else {
|
||||
tor_asprintf(&ret, "MasterOnionAddress JUNKJUNKJUNK.onion\n"
|
||||
"UnknownOption BLAH\n");
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_config_file(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char *conf = NULL;
|
||||
const ed25519_public_key_t *pkey;
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
|
||||
MOCK(read_file_to_str, mock_read_file_to_str);
|
||||
|
||||
#define fmt_conf \
|
||||
"HiddenServiceDir %s\n" \
|
||||
"HiddenServicePort 22\n" \
|
||||
"HiddenServiceOnionBalanceInstance 1\n"
|
||||
tor_asprintf(&conf, fmt_conf, get_fname("hs3"));
|
||||
#undef fmt_conf
|
||||
|
||||
/* Build the OB frontend onion addresses. */
|
||||
ed25519_keypair_generate(&onion_addr_kp_1, 0);
|
||||
hs_build_address(&onion_addr_kp_1.pubkey, HS_VERSION_THREE, onion_addr_1);
|
||||
ed25519_keypair_generate(&onion_addr_kp_2, 0);
|
||||
hs_build_address(&onion_addr_kp_2.pubkey, HS_VERSION_THREE, onion_addr_2);
|
||||
|
||||
ret = helper_tor_config(conf);
|
||||
tor_free(conf);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
/* Load the keys for the service. After that, the v3 service should be
|
||||
* registered in the global map and we'll be able to access it. */
|
||||
tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
|
||||
hs_service_load_all_keys();
|
||||
tt_int_op(get_hs_service_map_size(), OP_EQ, 1);
|
||||
const hs_service_t *s = get_first_service();
|
||||
tt_assert(s);
|
||||
tt_assert(s->config.ob_master_pubkeys);
|
||||
tt_assert(hs_ob_service_is_instance(s));
|
||||
tt_assert(smartlist_len(s->config.ob_master_pubkeys) == 2);
|
||||
|
||||
/* Test the public keys we've added. */
|
||||
pkey = smartlist_get(s->config.ob_master_pubkeys, 0);
|
||||
tt_mem_op(&onion_addr_kp_1.pubkey, OP_EQ, pkey, ED25519_PUBKEY_LEN);
|
||||
pkey = smartlist_get(s->config.ob_master_pubkeys, 1);
|
||||
tt_mem_op(&onion_addr_kp_2.pubkey, OP_EQ, pkey, ED25519_PUBKEY_LEN);
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
|
||||
UNMOCK(read_file_to_str);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_config_file_bad(void *arg)
|
||||
{
|
||||
int ret;
|
||||
char *conf = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
hs_init();
|
||||
|
||||
MOCK(read_file_to_str, mock_read_file_to_str);
|
||||
|
||||
/* Indicate mock_read_file_to_str() to use the bad config. */
|
||||
config_is_good = false;
|
||||
|
||||
#define fmt_conf \
|
||||
"HiddenServiceDir %s\n" \
|
||||
"HiddenServicePort 22\n" \
|
||||
"HiddenServiceOnionBalanceInstance 1\n"
|
||||
tor_asprintf(&conf, fmt_conf, get_fname("hs3"));
|
||||
#undef fmt_conf
|
||||
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
ret = helper_tor_config(conf);
|
||||
tor_free(conf);
|
||||
tt_int_op(ret, OP_EQ, -1);
|
||||
expect_log_msg_containing("OnionBalance: MasterOnionAddress "
|
||||
"JUNKJUNKJUNK.onion is invalid");
|
||||
expect_log_msg_containing("Found unrecognized option \'UnknownOption\'; "
|
||||
"saving it.");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
done:
|
||||
hs_free_all();
|
||||
|
||||
UNMOCK(read_file_to_str);
|
||||
}
|
||||
|
||||
static void
|
||||
test_get_subcredentials(void *arg)
|
||||
{
|
||||
int ret;
|
||||
hs_service_t *service = NULL;
|
||||
hs_service_config_t config;
|
||||
hs_subcredential_t *subcreds = NULL;
|
||||
|
||||
(void) arg;
|
||||
|
||||
MOCK(networkstatus_get_live_consensus,
|
||||
mock_networkstatus_get_live_consensus);
|
||||
|
||||
/* Setup consensus with proper time so we can compute the time period. */
|
||||
ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC",
|
||||
&mock_ns.valid_after);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC",
|
||||
&mock_ns.fresh_until);
|
||||
tt_int_op(ret, OP_EQ, 0);
|
||||
|
||||
config.ob_master_pubkeys = smartlist_new();
|
||||
tt_assert(config.ob_master_pubkeys);
|
||||
|
||||
/* Set up an instance */
|
||||
service = tor_malloc_zero(sizeof(hs_service_t));
|
||||
service->config = config;
|
||||
/* Setup the service descriptors */
|
||||
service->desc_current = service_descriptor_new();
|
||||
service->desc_next = service_descriptor_new();
|
||||
|
||||
/* First try to compute subcredentials but with no OB keys. Make sure that
|
||||
* subcreds get NULLed. To do this check we first poison subcreds. */
|
||||
subcreds = (void*)999;
|
||||
tt_ptr_op(subcreds, OP_NE, NULL);
|
||||
size_t num = compute_subcredentials(service, &subcreds);
|
||||
tt_ptr_op(subcreds, OP_EQ, NULL);
|
||||
|
||||
/* Generate a keypair to add to the OB keys list. */
|
||||
ed25519_keypair_generate(&onion_addr_kp_1, 0);
|
||||
smartlist_add(config.ob_master_pubkeys, &onion_addr_kp_1.pubkey);
|
||||
|
||||
/* Set up the instance subcredentials */
|
||||
char current_subcred[SUBCRED_LEN];
|
||||
char next_subcred[SUBCRED_LEN];
|
||||
memset(current_subcred, 'C', SUBCRED_LEN);
|
||||
memset(next_subcred, 'N', SUBCRED_LEN);
|
||||
memcpy(service->desc_current->desc->subcredential.subcred, current_subcred,
|
||||
SUBCRED_LEN);
|
||||
memcpy(service->desc_next->desc->subcredential.subcred, next_subcred,
|
||||
SUBCRED_LEN);
|
||||
|
||||
/* See that subcreds are computed properly */
|
||||
num = compute_subcredentials(service, &subcreds);
|
||||
/* 5 subcredentials: 3 for the frontend, 2 for the instance */
|
||||
tt_uint_op(num, OP_EQ, 5);
|
||||
tt_ptr_op(subcreds, OP_NE, NULL);
|
||||
|
||||
/* Validate the subcredentials we just got. We'll build them oursevles with
|
||||
* the right time period steps and compare. */
|
||||
const uint64_t tp = hs_get_time_period_num(0);
|
||||
const int steps[3] = {0, -1, 1};
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
hs_subcredential_t subcredential;
|
||||
ed25519_public_key_t blinded_pubkey;
|
||||
hs_build_blinded_pubkey(&onion_addr_kp_1.pubkey, NULL, 0, tp + steps[i],
|
||||
&blinded_pubkey);
|
||||
hs_get_subcredential(&onion_addr_kp_1.pubkey, &blinded_pubkey,
|
||||
&subcredential);
|
||||
tt_mem_op(subcreds[i].subcred, OP_EQ, subcredential.subcred,
|
||||
SUBCRED_LEN);
|
||||
}
|
||||
|
||||
tt_mem_op(subcreds[i++].subcred, OP_EQ, current_subcred, SUBCRED_LEN);
|
||||
tt_mem_op(subcreds[i++].subcred, OP_EQ, next_subcred, SUBCRED_LEN);
|
||||
|
||||
done:
|
||||
tor_free(subcreds);
|
||||
|
||||
smartlist_free(config.ob_master_pubkeys);
|
||||
if (service) {
|
||||
memset(&service->config, 0, sizeof(hs_service_config_t));
|
||||
hs_service_free(service);
|
||||
}
|
||||
|
||||
UNMOCK(networkstatus_get_live_consensus);
|
||||
}
|
||||
|
||||
struct testcase_t hs_ob_tests[] = {
|
||||
{ "parse_config_file", test_parse_config_file, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "parse_config_file_bad", test_parse_config_file_bad, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
{ "get_subcredentials", test_get_subcredentials, TT_FORK,
|
||||
NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
@ -51,6 +51,8 @@
|
||||
#include "feature/hs/hs_common.h"
|
||||
#include "feature/hs/hs_config.h"
|
||||
#include "feature/hs/hs_ident.h"
|
||||
#include "feature/hs/hs_ob.h"
|
||||
#include "feature/hs/hs_cell.h"
|
||||
#include "feature/hs/hs_intropoint.h"
|
||||
#include "feature/hs/hs_service.h"
|
||||
#include "feature/nodelist/networkstatus.h"
|
||||
@ -109,6 +111,9 @@ mock_circuit_mark_for_close(circuit_t *circ, int reason, int line,
|
||||
return;
|
||||
}
|
||||
|
||||
static size_t relay_payload_len;
|
||||
static char relay_payload[RELAY_PAYLOAD_SIZE];
|
||||
|
||||
static int
|
||||
mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
|
||||
uint8_t relay_command, const char *payload,
|
||||
@ -124,6 +129,10 @@ mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
|
||||
(void) cpath_layer;
|
||||
(void) filename;
|
||||
(void) lineno;
|
||||
|
||||
memcpy(relay_payload, payload, payload_len);
|
||||
relay_payload_len = payload_len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1160,7 +1169,7 @@ test_closing_intro_circs(void *arg)
|
||||
|
||||
/** Test sending and receiving introduce2 cells */
|
||||
static void
|
||||
test_introduce2(void *arg)
|
||||
test_bad_introduce2(void *arg)
|
||||
{
|
||||
int ret;
|
||||
int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
|
||||
@ -2169,6 +2178,348 @@ test_export_client_circuit_id(void *arg)
|
||||
tor_free(cp2);
|
||||
}
|
||||
|
||||
static smartlist_t *
|
||||
mock_node_get_link_specifier_smartlist(const node_t *node, bool direct_conn)
|
||||
{
|
||||
(void) node;
|
||||
(void) direct_conn;
|
||||
|
||||
smartlist_t *lspecs = smartlist_new();
|
||||
link_specifier_t *ls_legacy = link_specifier_new();
|
||||
smartlist_add(lspecs, ls_legacy);
|
||||
|
||||
return lspecs;
|
||||
}
|
||||
|
||||
static node_t *fake_node = NULL;
|
||||
|
||||
static const node_t *
|
||||
mock_build_state_get_exit_node(cpath_build_state_t *state)
|
||||
{
|
||||
(void) state;
|
||||
|
||||
if (!fake_node) {
|
||||
curve25519_secret_key_t seckey;
|
||||
curve25519_secret_key_generate(&seckey, 0);
|
||||
|
||||
fake_node = tor_malloc_zero(sizeof(node_t));
|
||||
fake_node->ri = tor_malloc_zero(sizeof(routerinfo_t));
|
||||
fake_node->ri->onion_curve25519_pkey =
|
||||
tor_malloc_zero(sizeof(curve25519_public_key_t));
|
||||
curve25519_public_key_generate(fake_node->ri->onion_curve25519_pkey,
|
||||
&seckey);
|
||||
}
|
||||
|
||||
return fake_node;
|
||||
}
|
||||
|
||||
static void
|
||||
mock_launch_rendezvous_point_circuit(const hs_service_t *service,
|
||||
const hs_service_intro_point_t *ip,
|
||||
const hs_cell_introduce2_data_t *data)
|
||||
{
|
||||
(void) service;
|
||||
(void) ip;
|
||||
(void) data;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that INTRO2 cells are handled well by onion services in the normal
|
||||
* case and also when onionbalance is enabled.
|
||||
*/
|
||||
static void
|
||||
test_intro2_handling(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
MOCK(build_state_get_exit_node, mock_build_state_get_exit_node);
|
||||
MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
|
||||
MOCK(node_get_link_specifier_smartlist,
|
||||
mock_node_get_link_specifier_smartlist);
|
||||
MOCK(launch_rendezvous_point_circuit, mock_launch_rendezvous_point_circuit);
|
||||
|
||||
memset(relay_payload, 0, sizeof(relay_payload));
|
||||
|
||||
int retval;
|
||||
time_t now = 0101010101;
|
||||
update_approx_time(now);
|
||||
|
||||
/** OK this is the play:
|
||||
*
|
||||
* In Act I, we have a standalone onion service X (without onionbalance
|
||||
* enabled). We test that X can properly handle INTRO2 cells sent by a
|
||||
* client Alice.
|
||||
*
|
||||
* In Act II, we create an onionbalance setup with frontend being Z which
|
||||
* includes instances X and Y. We then setup onionbalance on X and test that
|
||||
* Alice who addresses Z can communicate with X through INTRO2 cells.
|
||||
*
|
||||
* In Act III, we test that Alice can also communicate with X
|
||||
* directly even tho onionbalance is enabled.
|
||||
*
|
||||
* And finally in Act IV, we check various cases where the INTRO2 cell
|
||||
* should not go through because the subcredentials don't line up
|
||||
* (e.g. Alice sends INTRO2 to X using Y's subcredential).
|
||||
*/
|
||||
|
||||
/** Let's start with some setup! Create the instances and the frontend
|
||||
service, create Alice, etc: */
|
||||
|
||||
/* Create instance X */
|
||||
hs_service_t x_service;
|
||||
memset(&x_service, 0, sizeof(hs_service_t));
|
||||
/* Disable onionbalance */
|
||||
x_service.config.ob_master_pubkeys = NULL;
|
||||
x_service.state.replay_cache_rend_cookie = replaycache_new(0,0);
|
||||
|
||||
/* Create subcredential for x: */
|
||||
ed25519_keypair_t x_identity_keypair;
|
||||
hs_subcredential_t x_subcred;
|
||||
ed25519_keypair_generate(&x_identity_keypair, 0);
|
||||
hs_helper_get_subcred_from_identity_keypair(&x_identity_keypair,
|
||||
&x_subcred);
|
||||
|
||||
/* Create the x instance's intro point */
|
||||
hs_service_intro_point_t *x_ip = NULL;
|
||||
{
|
||||
curve25519_secret_key_t seckey;
|
||||
curve25519_public_key_t pkey;
|
||||
curve25519_secret_key_generate(&seckey, 0);
|
||||
curve25519_public_key_generate(&pkey, &seckey);
|
||||
|
||||
node_t intro_node;
|
||||
memset(&intro_node, 0, sizeof(intro_node));
|
||||
routerinfo_t ri;
|
||||
memset(&ri, 0, sizeof(routerinfo_t));
|
||||
ri.onion_curve25519_pkey = &pkey;
|
||||
intro_node.ri = &ri;
|
||||
|
||||
x_ip = service_intro_point_new(&intro_node);
|
||||
}
|
||||
|
||||
/* Create z frontend's subcredential */
|
||||
ed25519_keypair_t z_identity_keypair;
|
||||
hs_subcredential_t z_subcred;
|
||||
ed25519_keypair_generate(&z_identity_keypair, 0);
|
||||
hs_helper_get_subcred_from_identity_keypair(&z_identity_keypair,
|
||||
&z_subcred);
|
||||
|
||||
/* Create y instance's subcredential */
|
||||
ed25519_keypair_t y_identity_keypair;
|
||||
hs_subcredential_t y_subcred;
|
||||
ed25519_keypair_generate(&y_identity_keypair, 0);
|
||||
hs_helper_get_subcred_from_identity_keypair(&y_identity_keypair,
|
||||
&y_subcred);
|
||||
|
||||
/* Create Alice's intro point */
|
||||
hs_desc_intro_point_t *alice_ip;
|
||||
ed25519_keypair_t signing_kp;
|
||||
ed25519_keypair_generate(&signing_kp, 0);
|
||||
alice_ip = hs_helper_build_intro_point(&signing_kp, now, "1.2.3.4", 0,
|
||||
&x_ip->auth_key_kp,
|
||||
&x_ip->enc_key_kp);
|
||||
|
||||
/* Create Alice's intro and rend circuits */
|
||||
origin_circuit_t *intro_circ = origin_circuit_new();
|
||||
intro_circ->cpath = tor_malloc_zero(sizeof(crypt_path_t));
|
||||
intro_circ->cpath->prev = intro_circ->cpath;
|
||||
intro_circ->hs_ident = tor_malloc_zero(sizeof(*intro_circ->hs_ident));
|
||||
origin_circuit_t rend_circ;
|
||||
rend_circ.hs_ident = tor_malloc_zero(sizeof(*rend_circ.hs_ident));
|
||||
curve25519_keypair_generate(&rend_circ.hs_ident->rendezvous_client_kp, 0);
|
||||
memset(rend_circ.hs_ident->rendezvous_cookie, 'r', HS_REND_COOKIE_LEN);
|
||||
|
||||
/* ************************************************************ */
|
||||
|
||||
/* Act I:
|
||||
*
|
||||
* Where Alice connects to X without onionbalance in the picture */
|
||||
|
||||
/* Create INTRODUCE1 */
|
||||
tt_assert(fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
|
||||
retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
|
||||
alice_ip, &x_subcred);
|
||||
|
||||
/* Check that the payload was written successfully */
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
|
||||
tt_int_op(relay_payload_len, OP_NE, 0);
|
||||
|
||||
/* Handle the cell */
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&x_subcred,
|
||||
(uint8_t*)relay_payload,relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* ************************************************************ */
|
||||
|
||||
/* Act II:
|
||||
*
|
||||
* We now create an onionbalance setup with Z being the frontend and X and Y
|
||||
* being the backend instances. Make sure that Alice can talk with the
|
||||
* backend instance X even tho she thinks she is talking to the frontend Z.
|
||||
*/
|
||||
|
||||
/* Now configure the X instance to do onionbalance with Z as the frontend */
|
||||
x_service.config.ob_master_pubkeys = smartlist_new();
|
||||
smartlist_add(x_service.config.ob_master_pubkeys,
|
||||
&z_identity_keypair.pubkey);
|
||||
|
||||
/* Create descriptors for x and load next descriptor with the x's
|
||||
* subcredential so that it can accept connections for itself. */
|
||||
x_service.desc_current = service_descriptor_new();
|
||||
memset(x_service.desc_current->desc->subcredential.subcred, 'C',SUBCRED_LEN);
|
||||
x_service.desc_next = service_descriptor_new();
|
||||
memcpy(&x_service.desc_next->desc->subcredential, &x_subcred, SUBCRED_LEN);
|
||||
|
||||
/* Refresh OB keys */
|
||||
hs_ob_refresh_keys(&x_service);
|
||||
|
||||
/* Create INTRODUCE1 from Alice to X through Z */
|
||||
memset(relay_payload, 0, sizeof(relay_payload));
|
||||
retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
|
||||
alice_ip, &z_subcred);
|
||||
|
||||
/* Check that the payload was written successfully */
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
|
||||
tt_int_op(relay_payload_len, OP_NE, 0);
|
||||
|
||||
/* Deliver INTRODUCE1 to X even tho it carries Z's subcredential */
|
||||
replaycache_free(x_service.state.replay_cache_rend_cookie);
|
||||
x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
|
||||
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&z_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
replaycache_free(x_ip->replay_cache);
|
||||
x_ip->replay_cache = replaycache_new(0, 0);
|
||||
|
||||
replaycache_free(x_service.state.replay_cache_rend_cookie);
|
||||
x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
|
||||
|
||||
/* ************************************************************ */
|
||||
|
||||
/* Act III:
|
||||
*
|
||||
* Now send a direct INTRODUCE cell from Alice to X using X's subcredential
|
||||
* and check that it succeeds even with onionbalance enabled.
|
||||
*/
|
||||
|
||||
/* Refresh OB keys (just to check for memleaks) */
|
||||
hs_ob_refresh_keys(&x_service);
|
||||
|
||||
/* Create INTRODUCE1 from Alice to X using X's subcred. */
|
||||
memset(relay_payload, 0, sizeof(relay_payload));
|
||||
retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
|
||||
alice_ip, &x_subcred);
|
||||
|
||||
/* Check that the payload was written successfully */
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
|
||||
tt_int_op(relay_payload_len, OP_NE, 0);
|
||||
|
||||
/* Send INTRODUCE1 to X with X's subcredential (should succeed) */
|
||||
replaycache_free(x_service.state.replay_cache_rend_cookie);
|
||||
x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
|
||||
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&x_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* ************************************************************ */
|
||||
|
||||
/* Act IV:
|
||||
*
|
||||
* Test cases where the INTRO2 cell should not be able to decode.
|
||||
*/
|
||||
|
||||
/* Try sending the exact same INTRODUCE2 cell again and see that the intro
|
||||
* point replay cache triggers: */
|
||||
setup_full_capture_of_logs(LOG_WARN);
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&x_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, -1);
|
||||
expect_log_msg_containing("with the same ENCRYPTED section");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Now cleanup the intro point replay cache but not the service replay cache
|
||||
and see that this one triggers this time. */
|
||||
replaycache_free(x_ip->replay_cache);
|
||||
x_ip->replay_cache = replaycache_new(0, 0);
|
||||
setup_full_capture_of_logs(LOG_INFO);
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&x_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, -1);
|
||||
expect_log_msg_containing("with same REND_COOKIE");
|
||||
teardown_capture_of_logs();
|
||||
|
||||
/* Now just to make sure cleanup both replay caches and make sure that the
|
||||
cell gets through */
|
||||
replaycache_free(x_ip->replay_cache);
|
||||
x_ip->replay_cache = replaycache_new(0, 0);
|
||||
replaycache_free(x_service.state.replay_cache_rend_cookie);
|
||||
x_service.state.replay_cache_rend_cookie = replaycache_new(0, 0);
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&x_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* As a final thing, create an INTRODUCE1 cell from Alice to X using Y's
|
||||
* subcred (should fail since Y is just another instance and not the frontend
|
||||
* service!) */
|
||||
memset(relay_payload, 0, sizeof(relay_payload));
|
||||
retval = hs_circ_send_introduce1(intro_circ, &rend_circ,
|
||||
alice_ip, &y_subcred);
|
||||
tt_int_op(retval, OP_EQ, 0);
|
||||
|
||||
/* Check that the payload was written successfully */
|
||||
tt_assert(!fast_mem_is_zero(relay_payload, sizeof(relay_payload)));
|
||||
tt_int_op(relay_payload_len, OP_NE, 0);
|
||||
|
||||
retval = hs_circ_handle_introduce2(&x_service,
|
||||
intro_circ, x_ip,
|
||||
&y_subcred,
|
||||
(uint8_t*)relay_payload, relay_payload_len);
|
||||
tt_int_op(retval, OP_EQ, -1);
|
||||
|
||||
done:
|
||||
/* Start cleaning up X */
|
||||
replaycache_free(x_service.state.replay_cache_rend_cookie);
|
||||
smartlist_free(x_service.config.ob_master_pubkeys);
|
||||
tor_free(x_service.ob_subcreds);
|
||||
service_descriptor_free(x_service.desc_current);
|
||||
service_descriptor_free(x_service.desc_next);
|
||||
service_intro_point_free(x_ip);
|
||||
|
||||
/* Clean up Alice */
|
||||
hs_desc_intro_point_free(alice_ip);
|
||||
tor_free(rend_circ.hs_ident);
|
||||
|
||||
if (fake_node) {
|
||||
tor_free(fake_node->ri->onion_curve25519_pkey);
|
||||
tor_free(fake_node->ri);
|
||||
tor_free(fake_node);
|
||||
}
|
||||
|
||||
UNMOCK(build_state_get_exit_node);
|
||||
UNMOCK(relay_send_command_from_edge_);
|
||||
UNMOCK(node_get_link_specifier_smartlist);
|
||||
UNMOCK(launch_rendezvous_point_circuit);
|
||||
}
|
||||
|
||||
struct testcase_t hs_service_tests[] = {
|
||||
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
|
||||
NULL, NULL },
|
||||
@ -2194,7 +2545,7 @@ struct testcase_t hs_service_tests[] = {
|
||||
NULL, NULL },
|
||||
{ "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "introduce2", test_introduce2, TT_FORK,
|
||||
{ "bad_introduce2", test_bad_introduce2, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "service_event", test_service_event, TT_FORK,
|
||||
NULL, NULL },
|
||||
@ -2212,6 +2563,7 @@ struct testcase_t hs_service_tests[] = {
|
||||
TT_FORK, NULL, NULL },
|
||||
{ "export_client_circuit_id", test_export_client_circuit_id, TT_FORK,
|
||||
NULL, NULL },
|
||||
{ "intro2_handling", test_intro2_handling, TT_FORK, NULL, NULL },
|
||||
|
||||
END_OF_TESTCASES
|
||||
};
|
||||
|
@ -4571,6 +4571,35 @@ test_util_di_ops(void *arg)
|
||||
;
|
||||
}
|
||||
|
||||
static void
|
||||
test_util_memcpy_iftrue_timei(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
char buf1[25];
|
||||
char buf2[25];
|
||||
char buf3[25];
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
crypto_rand(buf1, sizeof(buf1));
|
||||
crypto_rand(buf2, sizeof(buf2));
|
||||
memcpy(buf3, buf1, sizeof(buf1));
|
||||
|
||||
/* We just copied buf1 into buf3. Now we're going to copy buf2 into buf2,
|
||||
iff our coin flip comes up heads. */
|
||||
bool coinflip = crypto_rand_int(2) == 0;
|
||||
|
||||
memcpy_if_true_timei(coinflip, buf3, buf2, sizeof(buf3));
|
||||
|
||||
if (coinflip) {
|
||||
tt_mem_op(buf3, OP_EQ, buf2, sizeof(buf2));
|
||||
} else {
|
||||
tt_mem_op(buf3, OP_EQ, buf1, sizeof(buf1));
|
||||
}
|
||||
}
|
||||
done:
|
||||
;
|
||||
}
|
||||
|
||||
static void
|
||||
test_util_di_map(void *arg)
|
||||
{
|
||||
@ -6386,6 +6415,7 @@ struct testcase_t util_tests[] = {
|
||||
UTIL_LEGACY(path_is_relative),
|
||||
UTIL_LEGACY(strtok),
|
||||
UTIL_LEGACY(di_ops),
|
||||
UTIL_TEST(memcpy_iftrue_timei, 0),
|
||||
UTIL_TEST(di_map, 0),
|
||||
UTIL_TEST(round_to_next_multiple_of, 0),
|
||||
UTIL_TEST(laplace, 0),
|
||||
|
Loading…
Reference in New Issue
Block a user