Merge remote-tracking branch 'tor-github/pr/1685/head'

This commit is contained in:
Nick Mathewson 2020-02-24 07:45:20 -05:00
commit caa392a73a
42 changed files with 1598 additions and 186 deletions

4
changes/bug32709 Normal file
View 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.

View File

@ -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 The HAProxy version 1 protocol is described in detail at
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt 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__:: [[HiddenServiceMaxStreams]] **HiddenServiceMaxStreams** __N__::
The maximum number of simultaneous streams (connections) per rendezvous The maximum number of simultaneous streams (connections) per rendezvous
circuit. The maximum value allowed is 65535. (Setting this to 0 will allow circuit. The maximum value allowed is 65535. (Setting this to 0 will allow

View File

@ -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_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_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_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_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:desc_encode_v3() 101
problem function-size /src/feature/hs/hs_descriptor.c:decrypt_desc_layer() 111 problem function-size /src/feature/hs/hs_descriptor.c:decrypt_desc_layer() 111

View File

@ -510,6 +510,8 @@ static const config_var_t option_vars_[] = {
LINELIST_S, RendConfigLines, NULL), LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceEnableIntroDoSBurstPerSec", VAR("HiddenServiceEnableIntroDoSBurstPerSec",
LINELIST_S, RendConfigLines, NULL), LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceOnionBalanceInstance",
LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
V(HidServAuth, LINELIST, NULL), V(HidServAuth, LINELIST, NULL),
V(ClientOnionAuthDir, FILENAME, NULL), V(ClientOnionAuthDir, FILENAME, NULL),

View File

@ -170,7 +170,7 @@ get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input,
* necessary key material, and return 0. */ * necessary key material, and return 0. */
static void static void
get_introduce1_key_material(const uint8_t *secret_input, 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) hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{ {
uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN]; 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 */ /* Let's build info */
ptr = info_blob; ptr = info_blob;
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); 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)); tor_assert(ptr == info_blob + sizeof(info_blob));
/* Let's build the input to the KDF */ /* 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 ed25519_public_key_t *intro_auth_pubkey,
const curve25519_public_key_t *intro_enc_pubkey, const curve25519_public_key_t *intro_enc_pubkey,
const curve25519_keypair_t *client_ephemeral_enc_keypair, 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) hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{ {
int bad = 0; int bad = 0;
@ -450,7 +450,29 @@ hs_ntor_service_get_introduce1_keys(
const ed25519_public_key_t *intro_auth_pubkey, const ed25519_public_key_t *intro_auth_pubkey,
const curve25519_keypair_t *intro_enc_keypair, const curve25519_keypair_t *intro_enc_keypair,
const curve25519_public_key_t *client_ephemeral_enc_pubkey, 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) hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{ {
int bad = 0; int bad = 0;
@ -460,7 +482,8 @@ hs_ntor_service_get_introduce1_keys(
tor_assert(intro_auth_pubkey); tor_assert(intro_auth_pubkey);
tor_assert(intro_enc_keypair); tor_assert(intro_enc_keypair);
tor_assert(client_ephemeral_enc_pubkey); 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); tor_assert(hs_ntor_intro_cell_keys_out);
/* Compute EXP(X, b) */ /* Compute EXP(X, b) */
@ -476,13 +499,16 @@ hs_ntor_service_get_introduce1_keys(
secret_input); secret_input);
bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN); bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN);
for (unsigned i = 0; i < n_subcredentials; ++i) {
/* Get ENC_KEY and MAC_KEY! */ /* Get ENC_KEY and MAC_KEY! */
get_introduce1_key_material(secret_input, subcredential, get_introduce1_key_material(secret_input, &subcredentials[i],
hs_ntor_intro_cell_keys_out); &hs_ntor_intro_cell_keys_out[i]);
}
memwipe(secret_input, 0, sizeof(secret_input)); memwipe(secret_input, 0, sizeof(secret_input));
if (bad) { 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; return bad ? -1 : 0;

View File

@ -35,11 +35,20 @@ typedef struct hs_ntor_rend_cell_keys_t {
uint8_t ntor_key_seed[DIGEST256_LEN]; uint8_t ntor_key_seed[DIGEST256_LEN];
} hs_ntor_rend_cell_keys_t; } 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( int hs_ntor_client_get_introduce1_keys(
const struct ed25519_public_key_t *intro_auth_pubkey, const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_public_key_t *intro_enc_pubkey, const struct curve25519_public_key_t *intro_enc_pubkey,
const struct curve25519_keypair_t *client_ephemeral_enc_keypair, 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); hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
int hs_ntor_client_get_rendezvous1_keys( 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, const struct curve25519_public_key_t *service_ephemeral_rend_pubkey,
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); 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( int hs_ntor_service_get_introduce1_keys(
const struct ed25519_public_key_t *intro_auth_pubkey, const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_keypair_t *intro_enc_keypair, const struct curve25519_keypair_t *intro_enc_keypair,
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey, 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); hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);
int hs_ntor_service_get_rendezvous1_keys( int hs_ntor_service_get_rendezvous1_keys(

View File

@ -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 * If there is no chosen exit, or if we don't know the node_t for
* the chosen exit, return NULL. * the chosen exit, return NULL.
*/ */
const node_t * MOCK_IMPL(const node_t *,
build_state_get_exit_node(cpath_build_state_t *state) build_state_get_exit_node,(cpath_build_state_t *state))
{ {
if (!state || !state->chosen_exit) if (!state || !state->chosen_exit)
return NULL; return NULL;

View File

@ -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 circuit_has_usable_onion_key(const origin_circuit_t *circ);
int extend_info_has_preferred_onion_key(const extend_info_t* ei); 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 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); const char *build_state_get_exit_nickname(cpath_build_state_t *state);
struct circuit_guard_state_t; struct circuit_guard_state_t;

View File

@ -13,6 +13,7 @@
#include "feature/hs_common/replaycache.h" #include "feature/hs_common/replaycache.h"
#include "feature/hs/hs_cell.h" #include "feature/hs/hs_cell.h"
#include "feature/hs/hs_ob.h"
#include "core/crypto/hs_ntor.h" #include "core/crypto/hs_ntor.h"
#include "core/or/origin_circuit_st.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)); 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. * From a set of keys, a list of subcredentials, and the ENCRYPTED section of
* Finally, the client public key is copied in client_pk. On error, return * an INTRODUCE2 cell, return an array of newly allocated intro cell keys
* NULL. */ * structures. Finally, the client public key is copied in client_pk. On
* error, return NULL.
**/
static hs_ntor_intro_cell_keys_t * static hs_ntor_intro_cell_keys_t *
get_introduce2_key_material(const ed25519_public_key_t *auth_key, get_introduce2_key_material(const ed25519_public_key_t *auth_key,
const curve25519_keypair_t *enc_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, const uint8_t *encrypted_section,
curve25519_public_key_t *client_pk) 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(auth_key);
tor_assert(enc_key); tor_assert(enc_key);
tor_assert(subcredential); tor_assert(n_subcredentials > 0);
tor_assert(subcredentials);
tor_assert(encrypted_section); tor_assert(encrypted_section);
tor_assert(client_pk); 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. */ /* First bytes of the ENCRYPTED section are the client public key. */
memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);
if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, if (hs_ntor_service_get_introduce1_keys_multi(auth_key, enc_key, client_pk,
subcredential, keys) < 0) { n_subcredentials,
subcredentials, keys) < 0) {
/* Don't rely on the caller to wipe this on error. */ /* Don't rely on the caller to wipe this on error. */
memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
tor_free(keys); tor_free(keys);
@ -747,6 +753,74 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
return ret; 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 /** Parse the INTRODUCE2 cell using data which contains everything we need to
* do so and contains the destination buffers of information we extract and * 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 * compute from the cell. Return 0 on success else a negative value. The
@ -795,46 +869,28 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
/* Check our replay cache for this introduction point. */ /* Check our replay cache for this introduction point. */
if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
encrypted_section_len, &elapsed)) { 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. " "same ENCRYPTED section was seen %ld seconds ago. "
"Dropping cell.", (long int) elapsed); "Dropping cell.", (long int) elapsed);
goto done; goto done;
} }
/* Build the key material out of the key material found in the cell. */ /* First bytes of the ENCRYPTED section are the client public key (they are
intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, * guaranteed to exist because of the length check above). We are gonna use
data->subcredential, * the client public key to compute the ntor keys and decrypt the payload:
encrypted_section, */
&data->client_pk); memcpy(&data->client_pk.public_key, encrypted_section,
if (intro_keys == NULL) { CURVE25519_PUBKEY_LEN);
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
"compute key material on circuit %u for service %s",
TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
goto done;
}
/* Validate MAC from the cell and our computed key material. The MAC field /* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
* in the cell is at the end of the encrypted section. */ intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
{ encrypted_section_len);
uint8_t mac[DIGEST256_LEN]; if (!intro_keys) {
/* The MAC field is at the very end of the ENCRYPTED section. */ log_warn(LD_REND, "Could not get valid INTRO2 keys on circuit %u "
size_t mac_offset = encrypted_section_len - sizeof(mac); "for service %s", TO_CIRCUIT(circ)->n_circ_id,
/* 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)); safe_str_client(service->onion_address));
goto done; goto done;
} }
}
{ {
/* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */

View File

@ -16,6 +16,8 @@
* 3.2.2 of the specification). Below this value, the cell must be padded. */ * 3.2.2 of the specification). Below this value, the cell must be padded. */
#define HS_CELL_INTRODUCE1_MIN_SIZE 246 #define HS_CELL_INTRODUCE1_MIN_SIZE 246
struct hs_subcredential_t;
/** This data structure contains data that we need to build an INTRODUCE1 cell /** This data structure contains data that we need to build an INTRODUCE1 cell
* used by the INTRODUCE1 build function. */ * used by the INTRODUCE1 build function. */
typedef struct hs_cell_introduce1_data_t { typedef struct hs_cell_introduce1_data_t {
@ -29,7 +31,7 @@ typedef struct hs_cell_introduce1_data_t {
/** Introduction point encryption public key. */ /** Introduction point encryption public key. */
const curve25519_public_key_t *enc_pk; const curve25519_public_key_t *enc_pk;
/** Subcredentials of the service. */ /** Subcredentials of the service. */
const uint8_t *subcredential; const struct hs_subcredential_t *subcredential;
/** Onion public key for the ntor handshake. */ /** Onion public key for the ntor handshake. */
const curve25519_public_key_t *onion_pk; const curve25519_public_key_t *onion_pk;
/** Rendezvous cookie. */ /** Rendezvous cookie. */
@ -55,9 +57,14 @@ typedef struct hs_cell_introduce2_data_t {
owned by the introduction point object through which we received the owned by the introduction point object through which we received the
INTRO2 cell*/ INTRO2 cell*/
const curve25519_keypair_t *enc_kp; 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. */ * Length of the subcredentials array below.
const uint8_t *subcredential; **/
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. */ /** Payload of the received encoded cell. */
const uint8_t *payload; const uint8_t *payload;
/** Size of the payload of the received encoded cell. */ /** Size of the payload of the received encoded cell. */

View File

@ -19,6 +19,7 @@
#include "feature/client/circpathbias.h" #include "feature/client/circpathbias.h"
#include "feature/hs/hs_cell.h" #include "feature/hs/hs_cell.h"
#include "feature/hs/hs_circuit.h" #include "feature/hs/hs_circuit.h"
#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_circuitmap.h"
#include "feature/hs/hs_client.h" #include "feature/hs/hs_client.h"
#include "feature/hs/hs_ident.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 * 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 * data. This function will try to open a circuit for a maximum value of
* MAX_REND_FAILURES then it will give up. */ * MAX_REND_FAILURES then it will give up. */
static void MOCK_IMPL(STATIC void,
launch_rendezvous_point_circuit(const hs_service_t *service, launch_rendezvous_point_circuit,(const hs_service_t *service,
const hs_service_intro_point_t *ip, const hs_service_intro_point_t *ip,
const hs_cell_introduce2_data_t *data) const hs_cell_introduce2_data_t *data))
{ {
int circ_needs_uptime; int circ_needs_uptime;
time_t now = time(NULL); time_t now = time(NULL);
@ -578,7 +579,7 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
static int static int
setup_introduce1_data(const hs_desc_intro_point_t *ip, setup_introduce1_data(const hs_desc_intro_point_t *ip,
const node_t *rp_node, const node_t *rp_node,
const uint8_t *subcredential, const hs_subcredential_t *subcredential,
hs_cell_introduce1_data_t *intro1_data) hs_cell_introduce1_data_t *intro1_data)
{ {
int ret = -1; int ret = -1;
@ -958,6 +959,42 @@ hs_circ_handle_intro_established(const hs_service_t *service,
return ret; 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 /** We just received an INTRODUCE2 cell on the established introduction circuit
* circ. Handle the INTRODUCE2 payload of size payload_len for the given * 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 * 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, hs_circ_handle_introduce2(const hs_service_t *service,
const origin_circuit_t *circ, const origin_circuit_t *circ,
hs_service_intro_point_t *ip, hs_service_intro_point_t *ip,
const uint8_t *subcredential, const hs_subcredential_t *subcredential,
const uint8_t *payload, size_t payload_len) const uint8_t *payload, size_t payload_len)
{ {
int ret = -1; int ret = -1;
@ -983,12 +1020,16 @@ hs_circ_handle_introduce2(const hs_service_t *service,
* parsed, decrypted and key material computed correctly. */ * parsed, decrypted and key material computed correctly. */
data.auth_pk = &ip->auth_key_kp.pubkey; data.auth_pk = &ip->auth_key_kp.pubkey;
data.enc_kp = &ip->enc_key_kp; data.enc_kp = &ip->enc_key_kp;
data.subcredential = subcredential;
data.payload = payload; data.payload = payload;
data.payload_len = payload_len; data.payload_len = payload_len;
data.link_specifiers = smartlist_new(); data.link_specifiers = smartlist_new();
data.replay_cache = ip->replay_cache; 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) { if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
goto done; goto done;
} }
@ -1092,7 +1133,7 @@ int
hs_circ_send_introduce1(origin_circuit_t *intro_circ, hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ, origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip, const hs_desc_intro_point_t *ip,
const uint8_t *subcredential) const hs_subcredential_t *subcredential)
{ {
int ret = -1; int ret = -1;
ssize_t payload_len; ssize_t payload_len;

View File

@ -46,15 +46,16 @@ int hs_circ_handle_intro_established(const hs_service_t *service,
origin_circuit_t *circ, origin_circuit_t *circ,
const uint8_t *payload, const uint8_t *payload,
size_t payload_len); size_t payload_len);
struct hs_subcredential_t;
int hs_circ_handle_introduce2(const hs_service_t *service, int hs_circ_handle_introduce2(const hs_service_t *service,
const origin_circuit_t *circ, const origin_circuit_t *circ,
hs_service_intro_point_t *ip, hs_service_intro_point_t *ip,
const uint8_t *subcredential, const struct hs_subcredential_t *subcredential,
const uint8_t *payload, size_t payload_len); const uint8_t *payload, size_t payload_len);
int hs_circ_send_introduce1(origin_circuit_t *intro_circ, int hs_circ_send_introduce1(origin_circuit_t *intro_circ,
origin_circuit_t *rend_circ, origin_circuit_t *rend_circ,
const hs_desc_intro_point_t *ip, 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); int hs_circ_send_establish_rendezvous(origin_circuit_t *circ);
/* e2e circuit API. */ /* e2e circuit API. */
@ -78,6 +79,12 @@ create_rp_circuit_identifier(const hs_service_t *service,
const curve25519_public_key_t *server_pk, const curve25519_public_key_t *server_pk,
const struct hs_ntor_rend_cell_keys_t *keys); 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(HS_CIRCUIT_PRIVATE) */
#endif /* !defined(TOR_HS_CIRCUIT_H) */ #endif /* !defined(TOR_HS_CIRCUIT_H) */

View File

@ -646,7 +646,7 @@ send_introduce1(origin_circuit_t *intro_circ,
/* Send the INTRODUCE1 cell. */ /* Send the INTRODUCE1 cell. */
if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, 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 (TO_CIRCUIT(intro_circ)->marked_for_close) {
/* If the introduction circuit was closed, we were unable to send the /* 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 * 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_descriptor_t **desc)
{ {
hs_desc_decode_status_t ret; hs_desc_decode_status_t ret;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
ed25519_public_key_t blinded_pubkey; ed25519_public_key_t blinded_pubkey;
hs_client_service_authorization_t *client_auth = NULL; hs_client_service_authorization_t *client_auth = NULL;
curve25519_secret_key_t *client_auht_sk = 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); uint64_t current_time_period = hs_get_time_period_num(0);
hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period,
&blinded_pubkey); &blinded_pubkey);
hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); hs_get_subcredential(service_identity_pk, &blinded_pubkey, &subcredential);
} }
/* Parse descriptor */ /* Parse descriptor */
ret = hs_desc_decode_descriptor(desc_str, subcredential, ret = hs_desc_decode_descriptor(desc_str, &subcredential,
client_auht_sk, desc); client_auht_sk, desc);
memwipe(subcredential, 0, sizeof(subcredential)); memwipe(&subcredential, 0, sizeof(subcredential));
if (ret != HS_DESC_DECODE_OK) { if (ret != HS_DESC_DECODE_OK) {
goto err; goto err;
} }
@ -2486,4 +2486,3 @@ set_hs_client_auths_map(digest256map_t *map)
} }
#endif /* defined(TOR_UNIT_TESTS) */ #endif /* defined(TOR_UNIT_TESTS) */

View File

@ -22,6 +22,7 @@
#include "feature/hs/hs_client.h" #include "feature/hs/hs_client.h"
#include "feature/hs/hs_common.h" #include "feature/hs/hs_common.h"
#include "feature/hs/hs_dos.h" #include "feature/hs/hs_dos.h"
#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_ident.h" #include "feature/hs/hs_ident.h"
#include "feature/hs/hs_service.h" #include "feature/hs/hs_service.h"
#include "feature/hs_common/shared_random_client.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 /** 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. */ * This can't fail. */
void void
hs_get_subcredential(const ed25519_public_key_t *identity_pk, hs_get_subcredential(const ed25519_public_key_t *identity_pk,
const ed25519_public_key_t *blinded_pk, const ed25519_public_key_t *blinded_pk,
uint8_t *subcred_out) hs_subcredential_t *subcred_out)
{ {
uint8_t credential[DIGEST256_LEN]; uint8_t credential[DIGEST256_LEN];
crypto_digest_t *digest; crypto_digest_t *digest;
@ -841,7 +842,8 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk,
sizeof(credential)); sizeof(credential));
crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey,
ED25519_PUBKEY_LEN); 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); crypto_digest_free(digest);
memwipe(credential, 0, sizeof(credential)); 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 * case the caller would want only one field. checksum_out MUST at least be 2
* bytes long. * 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 int
hs_parse_address(const char *address, ed25519_public_key_t *key_out, hs_parse_address_no_log(const char *address, ed25519_public_key_t *key_out,
uint8_t *checksum_out, uint8_t *version_out) uint8_t *checksum_out, uint8_t *version_out,
const char **errmsg)
{ {
char decoded[HS_SERVICE_ADDR_LEN]; char decoded[HS_SERVICE_ADDR_LEN];
tor_assert(address); tor_assert(address);
if (errmsg) {
*errmsg = NULL;
}
/* Obvious length check. */ /* Obvious length check. */
if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
log_warn(LD_REND, "Service address %s has an invalid length. " if (errmsg) {
"Expected %lu but got %lu.", *errmsg = "Invalid length";
escaped_safe_str(address), }
(unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
(unsigned long) strlen(address));
goto invalid; goto invalid;
} }
/* Decode address so we can extract needed fields. */ /* Decode address so we can extract needed fields. */
if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) if (base32_decode(decoded, sizeof(decoded), address, strlen(address))
!= sizeof(decoded)) { != sizeof(decoded)) {
log_warn(LD_REND, "Service address %s can't be decoded.", if (errmsg) {
escaped_safe_str(address)); *errmsg = "Unable to base32 decode";
}
goto invalid; goto invalid;
} }
@ -944,6 +951,22 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out,
return -1; 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 /** Validate a given onion address. The length, the base32 decoding, and
* checksum are validated. Return 1 if valid else 0. */ * checksum are validated. Return 1 if valid else 0. */
int int
@ -1807,6 +1830,7 @@ hs_free_all(void)
hs_service_free_all(); hs_service_free_all();
hs_cache_free_all(); hs_cache_free_all();
hs_client_free_all(); hs_client_free_all();
hs_ob_free_all();
} }
/** For the given origin circuit circ, decrement the number of rendezvous /** For the given origin circuit circ, decrement the number of rendezvous

View File

@ -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_address_is_valid(const char *address);
int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out, int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out,
uint8_t *checksum_out, uint8_t *version_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, void hs_build_blinded_pubkey(const struct ed25519_public_key_t *pubkey,
const uint8_t *secret, size_t secret_len, 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); 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, void hs_get_subcredential(const struct ed25519_public_key_t *identity_pk,
const struct ed25519_public_key_t *blinded_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_previous_time_period_num(time_t now);
uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_time_period_num(time_t now);

View File

@ -26,6 +26,7 @@
#include "feature/hs/hs_common.h" #include "feature/hs/hs_common.h"
#include "feature/hs/hs_config.h" #include "feature/hs/hs_config.h"
#include "feature/hs/hs_client.h" #include "feature/hs/hs_client.h"
#include "feature/hs/hs_ob.h"
#include "feature/hs/hs_service.h" #include "feature/hs/hs_service.h"
#include "feature/rend/rendclient.h" #include "feature/rend/rendclient.h"
#include "feature/rend/rendservice.h" #include "feature/rend/rendservice.h"
@ -219,6 +220,7 @@ config_has_invalid_options(const config_line_t *line_,
"HiddenServiceEnableIntroDoSDefense", "HiddenServiceEnableIntroDoSDefense",
"HiddenServiceEnableIntroDoSRatePerSec", "HiddenServiceEnableIntroDoSRatePerSec",
"HiddenServiceEnableIntroDoSBurstPerSec", "HiddenServiceEnableIntroDoSBurstPerSec",
"HiddenServiceOnionBalanceInstance",
NULL /* End marker. */ NULL /* End marker. */
}; };
@ -317,7 +319,7 @@ config_service_v3(const config_line_t *line_,
int have_num_ip = 0; int have_num_ip = 0;
bool export_circuit_id = false; /* just to detect duplicate options */ bool export_circuit_id = false; /* just to detect duplicate options */
bool dos_enabled = false, dos_rate_per_sec = false; 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 char *dup_opt_seen = NULL;
const config_line_t *line; const config_line_t *line;
@ -402,6 +404,27 @@ config_service_v3(const config_line_t *line_,
config->intro_dos_burst_per_sec); config->intro_dos_burst_per_sec);
continue; 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 /* We do not load the key material for the service at this stage. This is

View File

@ -211,7 +211,7 @@ build_secret_input(const hs_descriptor_t *desc,
memcpy(secret_input, secret_data, secret_data_len); memcpy(secret_input, secret_data, secret_data_len);
offset += secret_data_len; offset += secret_data_len;
/* Copy subcredential. */ /* Copy subcredential. */
memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN); memcpy(secret_input + offset, desc->subcredential.subcred, DIGEST256_LEN);
offset += DIGEST256_LEN; offset += DIGEST256_LEN;
/* Copy revision counter value. */ /* Copy revision counter value. */
set_uint64(secret_input + offset, set_uint64(secret_input + offset,
@ -1018,10 +1018,6 @@ desc_encode_v3(const hs_descriptor_t *desc,
tor_assert(encoded_out); tor_assert(encoded_out);
tor_assert(desc->plaintext_data.version == 3); tor_assert(desc->plaintext_data.version == 3);
if (BUG(desc->subcredential == NULL)) {
goto err;
}
/* Build the non-encrypted values. */ /* Build the non-encrypted values. */
{ {
char *encoded_cert; 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 * and return the buffer's length. The caller should wipe and free its content
* once done with it. This function can't fail. */ * once done with it. This function can't fail. */
static size_t static size_t
build_descriptor_cookie_keys(const uint8_t *subcredential, build_descriptor_cookie_keys(const hs_subcredential_t *subcredential,
size_t subcredential_len,
const curve25519_secret_key_t *sk, const curve25519_secret_key_t *sk,
const curve25519_public_key_t *pk, const curve25519_public_key_t *pk,
uint8_t **keys_out) uint8_t **keys_out)
@ -1389,7 +1384,7 @@ build_descriptor_cookie_keys(const uint8_t *subcredential,
/* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */
xof = crypto_xof_new(); 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_add_bytes(xof, secret_seed, sizeof(secret_seed));
crypto_xof_squeeze_bytes(xof, keystream, keystream_len); crypto_xof_squeeze_bytes(xof, keystream, keystream_len);
crypto_xof_free(xof); crypto_xof_free(xof);
@ -1426,11 +1421,12 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc,
sizeof(desc->superencrypted_data.auth_ephemeral_pubkey))); sizeof(desc->superencrypted_data.auth_ephemeral_pubkey)));
tor_assert(!fast_mem_is_zero((char *) client_auth_sk, tor_assert(!fast_mem_is_zero((char *) client_auth_sk,
sizeof(*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. */ /* Get the KEYS component to derive the CLIENT-ID and COOKIE-KEY. */
keystream_length = keystream_length =
build_descriptor_cookie_keys(desc->subcredential, DIGEST256_LEN, build_descriptor_cookie_keys(&desc->subcredential,
client_auth_sk, client_auth_sk,
&desc->superencrypted_data.auth_ephemeral_pubkey, &desc->superencrypted_data.auth_ephemeral_pubkey,
&keystream); &keystream);
@ -2558,7 +2554,7 @@ hs_desc_decode_plaintext(const char *encoded,
* set to NULL. */ * set to NULL. */
hs_desc_decode_status_t hs_desc_decode_status_t
hs_desc_decode_descriptor(const char *encoded, hs_desc_decode_descriptor(const char *encoded,
const uint8_t *subcredential, const hs_subcredential_t *subcredential,
const curve25519_secret_key_t *client_auth_sk, const curve25519_secret_key_t *client_auth_sk,
hs_descriptor_t **desc_out) hs_descriptor_t **desc_out)
{ {
@ -2576,7 +2572,7 @@ hs_desc_decode_descriptor(const char *encoded,
goto err; 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); ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
if (ret != HS_DESC_DECODE_OK) { 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 * symmetric only if the client auth is disabled. That is, the descriptor
* cookie will be NULL. */ * cookie will be NULL. */
if (!descriptor_cookie) { if (!descriptor_cookie) {
ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential,
NULL, NULL); NULL, NULL);
if (BUG(ret != HS_DESC_DECODE_OK)) { if (BUG(ret != HS_DESC_DECODE_OK)) {
ret = -1; 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 * key, and descriptor cookie, build the auth client so we can then encode the
* descriptor for publication. client_out must be already allocated. */ * descriptor for publication. client_out must be already allocated. */
void 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_public_key_t *client_auth_pk,
const curve25519_secret_key_t * const curve25519_secret_key_t *
auth_ephemeral_sk, 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. */ /* Get the KEYS part so we can derive the CLIENT-ID and COOKIE-KEY. */
keystream_length = keystream_length =
build_descriptor_cookie_keys(subcredential, DIGEST256_LEN, build_descriptor_cookie_keys(subcredential,
auth_ephemeral_sk, client_auth_pk, auth_ephemeral_sk, client_auth_pk,
&keystream); &keystream);
tor_assert(keystream_length > 0); tor_assert(keystream_length > 0);

View File

@ -14,6 +14,7 @@
#include "core/or/or.h" #include "core/or/or.h"
#include "trunnel/ed25519_cert.h" /* needed for trunnel */ #include "trunnel/ed25519_cert.h" /* needed for trunnel */
#include "feature/nodelist/torcert.h" #include "feature/nodelist/torcert.h"
#include "core/crypto/hs_ntor.h" /* for hs_subcredential_t */
/* Trunnel */ /* Trunnel */
struct link_specifier_t; 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 /** Subcredentials of a service, used by the client and service to decrypt
* the encrypted data. */ * the encrypted data. */
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
} hs_descriptor_t; } hs_descriptor_t;
/** Return true iff the given descriptor format version is supported. */ /** Return true iff the given descriptor format version is supported. */
@ -277,7 +278,7 @@ MOCK_DECL(int,
char **encoded_out)); char **encoded_out));
int hs_desc_decode_descriptor(const char *encoded, 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, const curve25519_secret_key_t *client_auth_sk,
hs_descriptor_t **desc_out); hs_descriptor_t **desc_out);
int hs_desc_decode_plaintext(const char *encoded, 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); 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 * const curve25519_public_key_t *
client_auth_pk, client_auth_pk,
const curve25519_secret_key_t * const curve25519_secret_key_t *

408
src/feature/hs/hs_ob.c Normal file
View 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
View 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) */

View File

@ -41,6 +41,7 @@
#include "feature/hs/hs_intropoint.h" #include "feature/hs/hs_intropoint.h"
#include "feature/hs/hs_service.h" #include "feature/hs/hs_service.h"
#include "feature/hs/hs_stats.h" #include "feature/hs/hs_stats.h"
#include "feature/hs/hs_ob.h"
#include "feature/dircommon/dir_connection_st.h" #include "feature/dircommon/dir_connection_st.h"
#include "core/or/edge_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)); service_authorized_client_free(p));
smartlist_free(config->clients); 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)); memset(config, 0, sizeof(*config));
} }
@ -1764,7 +1770,8 @@ build_service_desc_superencrypted(const hs_service_t *service,
sizeof(curve25519_public_key_t)); sizeof(curve25519_public_key_t));
/* Test that subcred is not zero because we might use it below */ /* 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; 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 /* Prepare the client for descriptor and then add to the list in the
* superencrypted part of the descriptor */ * superencrypted part of the descriptor */
hs_desc_build_authorized_client(desc->desc->subcredential, hs_desc_build_authorized_client(&desc->desc->subcredential,
&client->client_pk, &client->client_pk,
&desc->auth_ephemeral_kp.seckey, &desc->auth_ephemeral_kp.seckey,
desc->descriptor_cookie, desc_client); desc->descriptor_cookie, desc_client);
@ -1837,7 +1844,7 @@ build_service_desc_plaintext(const hs_service_t *service,
/* Set the subcredential. */ /* Set the subcredential. */
hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey, hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
desc->desc->subcredential); &desc->desc->subcredential);
plaintext = &desc->desc->plaintext_data; 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. */ /* Assign newly built descriptor to the next slot. */
*desc_out = desc; *desc_out = desc;
/* Fire a CREATED control port event. */ /* Fire a CREATED control port event. */
hs_control_desc_event_created(service->onion_address, hs_control_desc_event_created(service->onion_address,
&desc->blinded_kp.pubkey); &desc->blinded_kp.pubkey);
/* If we are an onionbalance instance, we refresh our keys when we rotate
* descriptors. */
hs_ob_refresh_keys(service);
return; return;
err: 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. /* The following will parse, decode and launch the rendezvous point circuit.
* Both current and legacy cells are handled. */ * 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) { payload, payload_len) < 0) {
goto err; goto err;
} }
@ -4042,6 +4055,11 @@ hs_service_free_(hs_service_t *service)
replaycache_free(service->state.replay_cache_rend_cookie); 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. */ /* Wipe service keys. */
memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk));

View File

@ -248,10 +248,14 @@ typedef struct hs_service_config_t {
/** Does this service export the circuit ID of its clients? */ /** Does this service export the circuit ID of its clients? */
hs_circuit_id_protocol_t circuit_id_protocol; 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; unsigned int has_dos_defense_enabled : 1;
uint32_t intro_dos_rate_per_sec; uint32_t intro_dos_rate_per_sec;
uint32_t intro_dos_burst_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; } hs_service_config_t;
/** Service state. */ /** Service state. */
@ -301,8 +305,13 @@ typedef struct hs_service_t {
/** Next descriptor. */ /** Next descriptor. */
hs_service_descriptor_t *desc_next; 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; } hs_service_t;
/** For the service global hash map, we define a specific type for it which /** For the service global hash map, we define a specific type for it which

View File

@ -13,6 +13,7 @@ LIBTOR_APP_A_SOURCES += \
src/feature/hs/hs_dos.c \ src/feature/hs/hs_dos.c \
src/feature/hs/hs_ident.c \ src/feature/hs/hs_ident.c \
src/feature/hs/hs_intropoint.c \ src/feature/hs/hs_intropoint.c \
src/feature/hs/hs_ob.c \
src/feature/hs/hs_service.c \ src/feature/hs/hs_service.c \
src/feature/hs/hs_stats.c src/feature/hs/hs_stats.c
@ -30,6 +31,7 @@ noinst_HEADERS += \
src/feature/hs/hs_dos.h \ src/feature/hs/hs_dos.h \
src/feature/hs/hs_ident.h \ src/feature/hs/hs_ident.h \
src/feature/hs/hs_intropoint.h \ src/feature/hs/hs_intropoint.h \
src/feature/hs/hs_ob.h \
src/feature/hs/hs_service.h \ src/feature/hs/hs_service.h \
src/feature/hs/hs_stats.h \ src/feature/hs/hs_stats.h \
src/feature/hs/hsdir_index_st.h src/feature/hs/hsdir_index_st.h

View File

@ -1240,8 +1240,8 @@ node_get_rsa_id_digest(const node_t *node)
* If node is NULL, returns an empty smartlist. * If node is NULL, returns an empty smartlist.
* *
* The smartlist must be freed using link_specifier_smartlist_free(). */ * The smartlist must be freed using link_specifier_smartlist_free(). */
smartlist_t * MOCK_IMPL(smartlist_t *,
node_get_link_specifier_smartlist(const node_t *node, bool direct_conn) node_get_link_specifier_smartlist,(const node_t *node, bool direct_conn))
{ {
link_specifier_t *ls; link_specifier_t *ls;
tor_addr_port_t ap; tor_addr_port_t ap;

View File

@ -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_v3_rendezvous_point(const node_t *node);
int node_supports_establish_intro_dos_extension(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); const uint8_t *node_get_rsa_id_digest(const node_t *node);
smartlist_t *node_get_link_specifier_smartlist(const node_t *node, MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node,
bool direct_conn); bool direct_conn));
void link_specifier_smartlist_free_(smartlist_t *ls_list); void link_specifier_smartlist_free_(smartlist_t *ls_list);
#define link_specifier_smartlist_free(ls_list) \ #define link_specifier_smartlist_free(ls_list) \
FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list)) FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list))

View File

@ -279,3 +279,30 @@ select_array_member_cumulative_timei(const uint64_t *entries, int n_entries,
return i_chosen; 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;
}
}

View File

@ -73,4 +73,6 @@ int select_array_member_cumulative_timei(const uint64_t *entries,
int n_entries, int n_entries,
uint64_t total, uint64_t rand_val); 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) */ #endif /* !defined(TOR_DI_OPS_H) */

View File

@ -85,12 +85,12 @@ int
fuzz_main(const uint8_t *data, size_t sz) fuzz_main(const uint8_t *data, size_t sz)
{ {
hs_descriptor_t *desc = NULL; hs_descriptor_t *desc = NULL;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
char *fuzzing_data = tor_memdup_nulterm(data, sz); 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) { if (desc) {
log_debug(LD_GENERAL, "Decoding okay"); log_debug(LD_GENERAL, "Decoding okay");
hs_descriptor_free(desc); hs_descriptor_free(desc);
@ -101,4 +101,3 @@ fuzz_main(const uint8_t *data, size_t sz)
tor_free(fuzzing_data); tor_free(fuzzing_data);
return 0; return 0;
} }

View File

@ -13,9 +13,22 @@
#include "feature/hs/hs_service.h" #include "feature/hs/hs_service.h"
#include "test/hs_test_helpers.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_desc_intro_point_t *
hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, 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; int ret;
ed25519_keypair_t auth_kp; 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); smartlist_add(ip->link_specifiers, ls_ip);
} }
if (intro_auth_kp) {
memcpy(&auth_kp, intro_auth_kp, sizeof(ed25519_keypair_t));
} else {
ret = ed25519_keypair_generate(&auth_kp, 0); ret = ed25519_keypair_generate(&auth_kp, 0);
tt_int_op(ret, OP_EQ, 0); tt_int_op(ret, OP_EQ, 0);
}
ip->auth_key_cert = tor_cert_create(signing_kp, CERT_TYPE_AUTH_HS_IP_KEY, ip->auth_key_cert = tor_cert_create(signing_kp, CERT_TYPE_AUTH_HS_IP_KEY,
&auth_kp.pubkey, now, &auth_kp.pubkey, now,
HS_DESC_CERT_LIFETIME, 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; ed25519_keypair_t ed25519_kp;
tor_cert_t *cross_cert; tor_cert_t *cross_cert;
if (intro_enc_kp) {
memcpy(&curve25519_kp, intro_enc_kp, sizeof(curve25519_keypair_t));
} else {
ret = curve25519_keypair_generate(&curve25519_kp, 0); ret = curve25519_keypair_generate(&curve25519_kp, 0);
tt_int_op(ret, OP_EQ, 0); tt_int_op(ret, OP_EQ, 0);
}
ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit, ed25519_keypair_from_curve25519_keypair(&ed25519_kp, &signbit,
&curve25519_kp); &curve25519_kp);
cross_cert = tor_cert_create(signing_kp, CERT_TYPE_CROSS_HS_IP_KEYS, 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); CERT_FLAG_INCLUDE_SIGNING_KEY);
tt_assert(cross_cert); tt_assert(cross_cert);
ip->enc_key_cert = 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; 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; desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey,
desc->subcredential); &desc->subcredential);
/* Setup superencrypted data section. */ /* Setup superencrypted data section. */
ret = curve25519_keypair_generate(&auth_ephemeral_kp, 0); 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) { if (!no_ip) {
/* Add four intro points. */ /* Add four intro points. */
smartlist_add(desc->encrypted_data.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, 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, 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, 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; descp = desc;
@ -186,7 +213,7 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip,
* an HS. Used to decrypt descriptors in unittests. */ * an HS. Used to decrypt descriptors in unittests. */
void void
hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, 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; ed25519_keypair_t blinded_kp;
uint64_t current_time_period = hs_get_time_period_num(approx_time()); 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, memcpy(&desc->superencrypted_data.auth_ephemeral_pubkey,
&auth_ephemeral_kp.pubkey, sizeof(curve25519_public_key_t)); &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, &auth_ephemeral_kp.seckey,
descriptor_cookie, desc_client); descriptor_cookie, desc_client);
smartlist_add(desc->superencrypted_data.clients, desc_client); smartlist_add(desc->superencrypted_data.clients, desc_client);

View File

@ -8,9 +8,11 @@
#include "feature/hs/hs_descriptor.h" #include "feature/hs/hs_descriptor.h"
/* Set of functions to help build and test descriptors. */ /* Set of functions to help build and test descriptors. */
hs_desc_intro_point_t *hs_helper_build_intro_point( hs_desc_intro_point_t *
const ed25519_keypair_t *signing_kp, time_t now, 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);
hs_descriptor_t *hs_helper_build_hs_desc_no_ip( hs_descriptor_t *hs_helper_build_hs_desc_no_ip(
const ed25519_keypair_t *signing_kp); const ed25519_keypair_t *signing_kp);
hs_descriptor_t *hs_helper_build_hs_desc_with_ip( 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); const ed25519_keypair_t *signing_kp);
void hs_helper_desc_equal(const hs_descriptor_t *desc1, void hs_helper_desc_equal(const hs_descriptor_t *desc1,
const hs_descriptor_t *desc2); const hs_descriptor_t *desc2);
void struct hs_subcredential_t;
hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, void hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp,
uint8_t *subcred_out); struct hs_subcredential_t *subcred_out);
void hs_helper_add_client_auth(const ed25519_public_key_t *service_pk, void hs_helper_add_client_auth(const ed25519_public_key_t *service_pk,
const curve25519_secret_key_t *client_sk); const curve25519_secret_key_t *client_sk);
#endif /* !defined(TOR_HS_TEST_HELPERS_H) */ #endif /* !defined(TOR_HS_TEST_HELPERS_H) */

View File

@ -179,6 +179,7 @@ src_test_test_SOURCES += \
src/test/test_hs_client.c \ src/test/test_hs_client.c \
src/test/test_hs_intropoint.c \ src/test/test_hs_intropoint.c \
src/test/test_hs_control.c \ src/test/test_hs_control.c \
src/test/test_hs_ob.c \
src/test/test_handles.c \ src/test/test_handles.c \
src/test/test_hs_cache.c \ src/test/test_hs_cache.c \
src/test/test_hs_descriptor.c \ src/test/test_hs_descriptor.c \

View File

@ -721,6 +721,7 @@ struct testgroup_t testgroups[] = {
{ "hs_dos/", hs_dos_tests }, { "hs_dos/", hs_dos_tests },
{ "hs_intropoint/", hs_intropoint_tests }, { "hs_intropoint/", hs_intropoint_tests },
{ "hs_ntor/", hs_ntor_tests }, { "hs_ntor/", hs_ntor_tests },
{ "hs_ob/", hs_ob_tests },
{ "hs_service/", hs_service_tests }, { "hs_service/", hs_service_tests },
{ "introduce/", introduce_tests }, { "introduce/", introduce_tests },
{ "keypin/", keypin_tests }, { "keypin/", keypin_tests },

View File

@ -141,6 +141,7 @@ extern struct testcase_t hs_descriptor[];
extern struct testcase_t hs_dos_tests[]; extern struct testcase_t hs_dos_tests[];
extern struct testcase_t hs_intropoint_tests[]; extern struct testcase_t hs_intropoint_tests[];
extern struct testcase_t hs_ntor_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_service_tests[];
extern struct testcase_t hs_tests[]; extern struct testcase_t hs_tests[];
extern struct testcase_t introduce_tests[]; extern struct testcase_t introduce_tests[];

View File

@ -370,7 +370,7 @@ test_hsdir_revision_counter_check(void *arg)
hs_descriptor_t *published_desc = NULL; hs_descriptor_t *published_desc = NULL;
char *published_desc_str = NULL; char *published_desc_str = NULL;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
char *received_desc_str = NULL; char *received_desc_str = NULL;
hs_descriptor_t *received_desc = 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; const ed25519_public_key_t *blinded_key;
blinded_key = &published_desc->plaintext_data.blinded_pubkey; 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); received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
retval = hs_desc_decode_descriptor(received_desc_str, 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_int_op(retval, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(received_desc); 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); received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
retval = hs_desc_decode_descriptor(received_desc_str, 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_int_op(retval, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(received_desc); tt_assert(received_desc);
@ -476,7 +476,7 @@ test_client_cache(void *arg)
ed25519_keypair_t signing_kp; ed25519_keypair_t signing_kp;
hs_descriptor_t *published_desc = NULL; hs_descriptor_t *published_desc = NULL;
char *published_desc_str = NULL; char *published_desc_str = NULL;
uint8_t wanted_subcredential[DIGEST256_LEN]; hs_subcredential_t wanted_subcredential;
response_handler_args_t *args = NULL; response_handler_args_t *args = NULL;
dir_connection_t *conn = NULL; dir_connection_t *conn = NULL;
@ -505,8 +505,10 @@ test_client_cache(void *arg)
retval = hs_desc_encode_descriptor(published_desc, &signing_kp, retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
NULL, &published_desc_str); NULL, &published_desc_str);
tt_int_op(retval, OP_EQ, 0); tt_int_op(retval, OP_EQ, 0);
memcpy(wanted_subcredential, published_desc->subcredential, DIGEST256_LEN); memcpy(&wanted_subcredential, &published_desc->subcredential,
tt_assert(!fast_mem_is_zero((char*)wanted_subcredential, DIGEST256_LEN)); sizeof(hs_subcredential_t));
tt_assert(!fast_mem_is_zero((char*)wanted_subcredential.subcred,
DIGEST256_LEN));
} }
/* Test handle_response_fetch_hsdesc_v3() */ /* Test handle_response_fetch_hsdesc_v3() */
@ -540,8 +542,9 @@ test_client_cache(void *arg)
const hs_descriptor_t *cached_desc = NULL; const hs_descriptor_t *cached_desc = NULL;
cached_desc = hs_cache_lookup_as_client(&signing_kp.pubkey); cached_desc = hs_cache_lookup_as_client(&signing_kp.pubkey);
tt_assert(cached_desc); tt_assert(cached_desc);
tt_mem_op(cached_desc->subcredential, OP_EQ, wanted_subcredential, tt_mem_op(cached_desc->subcredential.subcred,
DIGEST256_LEN); OP_EQ, wanted_subcredential.subcred,
SUBCRED_LEN);
} }
/* Progress time to next TP and check that desc was cleaned */ /* Progress time to next TP and check that desc was cleaned */

View File

@ -433,9 +433,10 @@ test_client_pick_intro(void *arg)
const hs_descriptor_t *fetched_desc = const hs_descriptor_t *fetched_desc =
hs_cache_lookup_as_client(&service_kp.pubkey); hs_cache_lookup_as_client(&service_kp.pubkey);
tt_assert(fetched_desc); tt_assert(fetched_desc);
tt_mem_op(fetched_desc->subcredential, OP_EQ, desc->subcredential, tt_mem_op(fetched_desc->subcredential.subcred,
DIGEST256_LEN); OP_EQ, desc->subcredential.subcred,
tt_assert(!fast_mem_is_zero((char*)fetched_desc->subcredential, SUBCRED_LEN);
tt_assert(!fast_mem_is_zero((char*)fetched_desc->subcredential.subcred,
DIGEST256_LEN)); DIGEST256_LEN));
tor_free(encoded); tor_free(encoded);
} }

View File

@ -53,14 +53,14 @@ test_validate_address(void *arg)
setup_full_capture_of_logs(LOG_WARN); setup_full_capture_of_logs(LOG_WARN);
ret = hs_address_is_valid("blah"); ret = hs_address_is_valid("blah");
tt_int_op(ret, OP_EQ, 0); 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(); teardown_capture_of_logs();
setup_full_capture_of_logs(LOG_WARN); setup_full_capture_of_logs(LOG_WARN);
ret = hs_address_is_valid( ret = hs_address_is_valid(
"p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb"); "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb");
tt_int_op(ret, OP_EQ, 0); 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(); teardown_capture_of_logs();
/* Invalid checksum (taken from prop224) */ /* Invalid checksum (taken from prop224) */
@ -83,7 +83,7 @@ test_validate_address(void *arg)
ret = hs_address_is_valid( ret = hs_address_is_valid(
"????????????????????????????????????????????????????????"); "????????????????????????????????????????????????????????");
tt_int_op(ret, OP_EQ, 0); 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(); teardown_capture_of_logs();
/* Valid address. */ /* Valid address. */

View File

@ -221,7 +221,7 @@ test_decode_descriptor(void *arg)
hs_descriptor_t *desc = NULL; hs_descriptor_t *desc = NULL;
hs_descriptor_t *decoded = NULL; hs_descriptor_t *decoded = NULL;
hs_descriptor_t *desc_no_ip = NULL; hs_descriptor_t *desc_no_ip = NULL;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
(void) arg; (void) arg;
@ -230,10 +230,10 @@ test_decode_descriptor(void *arg)
desc = hs_helper_build_hs_desc_with_ip(&signing_kp); desc = hs_helper_build_hs_desc_with_ip(&signing_kp);
hs_helper_get_subcred_from_identity_keypair(&signing_kp, hs_helper_get_subcred_from_identity_keypair(&signing_kp,
subcredential); &subcredential);
/* Give some bad stuff to the decoding function. */ /* Give some bad stuff to the decoding function. */
ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, ret = hs_desc_decode_descriptor("hladfjlkjadf", &subcredential,
NULL, &decoded); NULL, &decoded);
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_PLAINTEXT_ERROR); 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_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(encoded); 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_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(decoded); tt_assert(decoded);
@ -253,7 +253,7 @@ test_decode_descriptor(void *arg)
ret = ed25519_keypair_generate(&signing_kp_no_ip, 0); ret = ed25519_keypair_generate(&signing_kp_no_ip, 0);
tt_int_op(ret, OP_EQ, 0); tt_int_op(ret, OP_EQ, 0);
hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip, 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); desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip);
tt_assert(desc_no_ip); tt_assert(desc_no_ip);
tor_free(encoded); tor_free(encoded);
@ -262,7 +262,7 @@ test_decode_descriptor(void *arg)
tt_int_op(ret, OP_EQ, 0); tt_int_op(ret, OP_EQ, 0);
tt_assert(encoded); tt_assert(encoded);
hs_descriptor_free(decoded); 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_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(decoded); tt_assert(decoded);
} }
@ -286,14 +286,14 @@ test_decode_descriptor(void *arg)
&auth_ephemeral_kp.pubkey, CURVE25519_PUBKEY_LEN); &auth_ephemeral_kp.pubkey, CURVE25519_PUBKEY_LEN);
hs_helper_get_subcred_from_identity_keypair(&signing_kp, hs_helper_get_subcred_from_identity_keypair(&signing_kp,
subcredential); &subcredential);
/* Build and add the auth client to the descriptor. */ /* Build and add the auth client to the descriptor. */
clients = desc->superencrypted_data.clients; clients = desc->superencrypted_data.clients;
if (!clients) { if (!clients) {
clients = smartlist_new(); clients = smartlist_new();
} }
hs_desc_build_authorized_client(subcredential, hs_desc_build_authorized_client(&subcredential,
&client_kp.pubkey, &client_kp.pubkey,
&auth_ephemeral_kp.seckey, &auth_ephemeral_kp.seckey,
descriptor_cookie, client); 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. */ /* If we do not have the client secret key, the decoding must fail. */
hs_descriptor_free(decoded); hs_descriptor_free(decoded);
ret = hs_desc_decode_descriptor(encoded, subcredential, ret = hs_desc_decode_descriptor(encoded, &subcredential,
NULL, &decoded); NULL, &decoded);
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_NEED_CLIENT_AUTH); tt_int_op(ret, OP_EQ, HS_DESC_DECODE_NEED_CLIENT_AUTH);
tt_assert(!decoded); tt_assert(!decoded);
/* If we have an invalid client secret key, the decoding must fail. */ /* If we have an invalid client secret key, the decoding must fail. */
hs_descriptor_free(decoded); hs_descriptor_free(decoded);
ret = hs_desc_decode_descriptor(encoded, subcredential, ret = hs_desc_decode_descriptor(encoded, &subcredential,
&invalid_client_kp.seckey, &decoded); &invalid_client_kp.seckey, &decoded);
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_BAD_CLIENT_AUTH); tt_int_op(ret, OP_EQ, HS_DESC_DECODE_BAD_CLIENT_AUTH);
tt_assert(!decoded); tt_assert(!decoded);
/* If we have the client secret key, the decoding must succeed and the /* If we have the client secret key, the decoding must succeed and the
* decoded descriptor must be correct. */ * decoded descriptor must be correct. */
ret = hs_desc_decode_descriptor(encoded, subcredential, ret = hs_desc_decode_descriptor(encoded, &subcredential,
&client_kp.seckey, &decoded); &client_kp.seckey, &decoded);
tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK); tt_int_op(ret, OP_EQ, HS_DESC_DECODE_OK);
tt_assert(decoded); tt_assert(decoded);
@ -762,7 +762,7 @@ test_build_authorized_client(void *arg)
"07d087f1d8c68393721f6e70316d3b29"; "07d087f1d8c68393721f6e70316d3b29";
const char client_pubkey_b16[] = const char client_pubkey_b16[] =
"8c1298fa6050e372f8598f6deca32e27b0ad457741422c2629ebb132cf7fae37"; "8c1298fa6050e372f8598f6deca32e27b0ad457741422c2629ebb132cf7fae37";
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
char *mem_op_hex_tmp=NULL; char *mem_op_hex_tmp=NULL;
(void) arg; (void) arg;
@ -774,7 +774,7 @@ test_build_authorized_client(void *arg)
tt_int_op(ret, OP_EQ, 0); tt_int_op(ret, OP_EQ, 0);
curve25519_public_key_generate(&client_auth_pk, &client_auth_sk); 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)); 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); testing_enable_prefilled_rng("\x01", 1);
hs_desc_build_authorized_client(subcredential, hs_desc_build_authorized_client(&subcredential,
&client_auth_pk, &auth_ephemeral_sk, &client_auth_pk, &auth_ephemeral_sk,
descriptor_cookie, desc_client); descriptor_cookie, desc_client);

View File

@ -23,7 +23,7 @@ test_hs_ntor(void *arg)
{ {
int retval; int retval;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
ed25519_keypair_t service_intro_auth_keypair; ed25519_keypair_t service_intro_auth_keypair;
curve25519_keypair_t service_intro_enc_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 data for this unittest */
{ {
/* Generate fake subcredential */ /* Generate fake subcredential */
memset(subcredential, 'Z', DIGEST256_LEN); memset(subcredential.subcred, 'Z', DIGEST256_LEN);
/* service */ /* service */
curve25519_keypair_generate(&service_intro_enc_keypair, 0); 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, hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
&service_intro_enc_keypair.pubkey, &service_intro_enc_keypair.pubkey,
&client_ephemeral_enc_keypair, &client_ephemeral_enc_keypair,
subcredential, &subcredential,
&client_hs_ntor_intro_cell_keys); &client_hs_ntor_intro_cell_keys);
tt_int_op(retval, OP_EQ, 0); 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, hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey,
&service_intro_enc_keypair, &service_intro_enc_keypair,
&client_ephemeral_enc_keypair.pubkey, &client_ephemeral_enc_keypair.pubkey,
subcredential, &subcredential,
&service_hs_ntor_intro_cell_keys); &service_hs_ntor_intro_cell_keys);
tt_int_op(retval, OP_EQ, 0); tt_int_op(retval, OP_EQ, 0);

View File

@ -53,7 +53,7 @@ client1(int argc, char **argv)
curve25519_public_key_t intro_enc_pubkey; curve25519_public_key_t intro_enc_pubkey;
ed25519_public_key_t intro_auth_pubkey; ed25519_public_key_t intro_auth_pubkey;
curve25519_keypair_t client_ephemeral_enc_keypair; curve25519_keypair_t client_ephemeral_enc_keypair;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
/* Output */ /* Output */
hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys; 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(3, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN);
BASE16(4, client_ephemeral_enc_keypair.seckey.secret_key, BASE16(4, client_ephemeral_enc_keypair.seckey.secret_key,
CURVE25519_SECKEY_LEN); CURVE25519_SECKEY_LEN);
BASE16(5, subcredential, DIGEST256_LEN); BASE16(5, subcredential.subcred, DIGEST256_LEN);
/* Generate keypair */ /* Generate keypair */
curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey, 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, retval = hs_ntor_client_get_introduce1_keys(&intro_auth_pubkey,
&intro_enc_pubkey, &intro_enc_pubkey,
&client_ephemeral_enc_keypair, &client_ephemeral_enc_keypair,
subcredential, &subcredential,
&hs_ntor_intro_cell_keys); &hs_ntor_intro_cell_keys);
if (retval < 0) { if (retval < 0) {
goto done; goto done;
@ -106,7 +106,7 @@ server1(int argc, char **argv)
curve25519_keypair_t intro_enc_keypair; curve25519_keypair_t intro_enc_keypair;
ed25519_public_key_t intro_auth_pubkey; ed25519_public_key_t intro_auth_pubkey;
curve25519_public_key_t client_ephemeral_enc_pubkey; curve25519_public_key_t client_ephemeral_enc_pubkey;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
/* Output */ /* Output */
hs_ntor_intro_cell_keys_t hs_ntor_intro_cell_keys; 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(2, intro_auth_pubkey.pubkey, ED25519_PUBKEY_LEN);
BASE16(3, intro_enc_keypair.seckey.secret_key, CURVE25519_SECKEY_LEN); BASE16(3, intro_enc_keypair.seckey.secret_key, CURVE25519_SECKEY_LEN);
BASE16(4, client_ephemeral_enc_pubkey.public_key, CURVE25519_PUBKEY_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 */ /* Generate keypair */
curve25519_public_key_generate(&intro_enc_keypair.pubkey, 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, retval = hs_ntor_service_get_introduce1_keys(&intro_auth_pubkey,
&intro_enc_keypair, &intro_enc_keypair,
&client_ephemeral_enc_pubkey, &client_ephemeral_enc_pubkey,
subcredential, &subcredential,
&hs_ntor_intro_cell_keys); &hs_ntor_intro_cell_keys);
if (retval < 0) { if (retval < 0) {
goto done; goto done;
@ -188,7 +188,7 @@ client2(int argc, char **argv)
ed25519_public_key_t intro_auth_pubkey; ed25519_public_key_t intro_auth_pubkey;
curve25519_keypair_t client_ephemeral_enc_keypair; curve25519_keypair_t client_ephemeral_enc_keypair;
curve25519_public_key_t service_ephemeral_rend_pubkey; curve25519_public_key_t service_ephemeral_rend_pubkey;
uint8_t subcredential[DIGEST256_LEN]; hs_subcredential_t subcredential;
/* Output */ /* Output */
hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys; hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys;
@ -201,7 +201,7 @@ client2(int argc, char **argv)
CURVE25519_SECKEY_LEN); CURVE25519_SECKEY_LEN);
BASE16(4, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN); BASE16(4, intro_enc_pubkey.public_key, CURVE25519_PUBKEY_LEN);
BASE16(5, service_ephemeral_rend_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 */ /* Generate keypair */
curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey, curve25519_public_key_generate(&client_ephemeral_enc_keypair.pubkey,

268
src/test/test_hs_ob.c Normal file
View 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
};

View File

@ -51,6 +51,8 @@
#include "feature/hs/hs_common.h" #include "feature/hs/hs_common.h"
#include "feature/hs/hs_config.h" #include "feature/hs/hs_config.h"
#include "feature/hs/hs_ident.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_intropoint.h"
#include "feature/hs/hs_service.h" #include "feature/hs/hs_service.h"
#include "feature/nodelist/networkstatus.h" #include "feature/nodelist/networkstatus.h"
@ -109,6 +111,9 @@ mock_circuit_mark_for_close(circuit_t *circ, int reason, int line,
return; return;
} }
static size_t relay_payload_len;
static char relay_payload[RELAY_PAYLOAD_SIZE];
static int static int
mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ,
uint8_t relay_command, const char *payload, 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) cpath_layer;
(void) filename; (void) filename;
(void) lineno; (void) lineno;
memcpy(relay_payload, payload, payload_len);
relay_payload_len = payload_len;
return 0; return 0;
} }
@ -1160,7 +1169,7 @@ test_closing_intro_circs(void *arg)
/** Test sending and receiving introduce2 cells */ /** Test sending and receiving introduce2 cells */
static void static void
test_introduce2(void *arg) test_bad_introduce2(void *arg)
{ {
int ret; int ret;
int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
@ -2169,6 +2178,348 @@ test_export_client_circuit_id(void *arg)
tor_free(cp2); 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[] = { struct testcase_t hs_service_tests[] = {
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
NULL, NULL }, NULL, NULL },
@ -2194,7 +2545,7 @@ struct testcase_t hs_service_tests[] = {
NULL, NULL }, NULL, NULL },
{ "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK, { "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK,
NULL, NULL }, NULL, NULL },
{ "introduce2", test_introduce2, TT_FORK, { "bad_introduce2", test_bad_introduce2, TT_FORK,
NULL, NULL }, NULL, NULL },
{ "service_event", test_service_event, TT_FORK, { "service_event", test_service_event, TT_FORK,
NULL, NULL }, NULL, NULL },
@ -2212,6 +2563,7 @@ struct testcase_t hs_service_tests[] = {
TT_FORK, NULL, NULL }, TT_FORK, NULL, NULL },
{ "export_client_circuit_id", test_export_client_circuit_id, TT_FORK, { "export_client_circuit_id", test_export_client_circuit_id, TT_FORK,
NULL, NULL }, NULL, NULL },
{ "intro2_handling", test_intro2_handling, TT_FORK, NULL, NULL },
END_OF_TESTCASES END_OF_TESTCASES
}; };

View File

@ -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 static void
test_util_di_map(void *arg) test_util_di_map(void *arg)
{ {
@ -6386,6 +6415,7 @@ struct testcase_t util_tests[] = {
UTIL_LEGACY(path_is_relative), UTIL_LEGACY(path_is_relative),
UTIL_LEGACY(strtok), UTIL_LEGACY(strtok),
UTIL_LEGACY(di_ops), UTIL_LEGACY(di_ops),
UTIL_TEST(memcpy_iftrue_timei, 0),
UTIL_TEST(di_map, 0), UTIL_TEST(di_map, 0),
UTIL_TEST(round_to_next_multiple_of, 0), UTIL_TEST(round_to_next_multiple_of, 0),
UTIL_TEST(laplace, 0), UTIL_TEST(laplace, 0),