Skip to content

Commit

Permalink
Merge remote-tracking branch 'tor-github/pr/1685/head'
Browse files Browse the repository at this point in the history
  • Loading branch information
nmathewson committed Feb 24, 2020
2 parents 7ba7f9c + 93cb807 commit caa392a
Show file tree
Hide file tree
Showing 42 changed files with 1,598 additions and 186 deletions.
4 changes: 4 additions & 0 deletions changes/bug32709
Original file line number Diff line number Diff line change
@@ -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.

13 changes: 13 additions & 0 deletions doc/tor.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3193,6 +3193,19 @@ The next section describes the per service options that can only be set
The HAProxy version 1 protocol is described in detail at
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

[[HiddenServiceOnionBalanceInstance]] **HiddenServiceOnionBalanceInstance** **0**|**1**::

If set to 1, this onion service becomes an OnionBalance instance and will
accept client connections destined to an OnionBalance frontend. In this
case, Tor expects to find a file named "ob_config" inside the
**HiddenServiceDir** directory with content:
+
MasterOnionAddress <frontend_onion_address>
+
where <frontend_onion_address> is the onion address of the OnionBalance
frontend (e.g. wrxdvcaqpuzakbfww5sxs6r2uybczwijzfn2ezy2osaj7iox7kl7nhad.onion).


[[HiddenServiceMaxStreams]] **HiddenServiceMaxStreams** __N__::
The maximum number of simultaneous streams (connections) per rendezvous
circuit. The maximum value allowed is 65535. (Setting this to 0 will allow
Expand Down
2 changes: 1 addition & 1 deletion scripts/maint/practracker/exceptions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ problem function-size /src/feature/hs/hs_cell.c:hs_cell_build_establish_intro()
problem function-size /src/feature/hs/hs_cell.c:hs_cell_parse_introduce2() 152
problem function-size /src/feature/hs/hs_client.c:send_introduce1() 103
problem function-size /src/feature/hs/hs_common.c:hs_get_responsible_hsdirs() 102
problem function-size /src/feature/hs/hs_config.c:config_service_v3() 107
problem function-size /src/feature/hs/hs_config.c:config_service_v3() 128
problem function-size /src/feature/hs/hs_config.c:config_generic_service() 138
problem function-size /src/feature/hs/hs_descriptor.c:desc_encode_v3() 101
problem function-size /src/feature/hs/hs_descriptor.c:decrypt_desc_layer() 111
Expand Down
2 changes: 2 additions & 0 deletions src/app/config/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ static const config_var_t option_vars_[] = {
LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceEnableIntroDoSBurstPerSec",
LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceOnionBalanceInstance",
LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
V(HidServAuth, LINELIST, NULL),
V(ClientOnionAuthDir, FILENAME, NULL),
Expand Down
44 changes: 35 additions & 9 deletions src/core/crypto/hs_ntor.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input,
* necessary key material, and return 0. */
static void
get_introduce1_key_material(const uint8_t *secret_input,
const uint8_t *subcredential,
const hs_subcredential_t *subcredential,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{
uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN];
Expand All @@ -181,7 +181,7 @@ get_introduce1_key_material(const uint8_t *secret_input,
/* Let's build info */
ptr = info_blob;
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
APPEND(ptr, subcredential, DIGEST256_LEN);
APPEND(ptr, subcredential->subcred, SUBCRED_LEN);
tor_assert(ptr == info_blob + sizeof(info_blob));

/* Let's build the input to the KDF */
Expand Down Expand Up @@ -317,7 +317,7 @@ hs_ntor_client_get_introduce1_keys(
const ed25519_public_key_t *intro_auth_pubkey,
const curve25519_public_key_t *intro_enc_pubkey,
const curve25519_keypair_t *client_ephemeral_enc_keypair,
const uint8_t *subcredential,
const hs_subcredential_t *subcredential,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{
int bad = 0;
Expand Down Expand Up @@ -450,8 +450,30 @@ hs_ntor_service_get_introduce1_keys(
const ed25519_public_key_t *intro_auth_pubkey,
const curve25519_keypair_t *intro_enc_keypair,
const curve25519_public_key_t *client_ephemeral_enc_pubkey,
const uint8_t *subcredential,
const hs_subcredential_t *subcredential,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{
return hs_ntor_service_get_introduce1_keys_multi(
intro_auth_pubkey,
intro_enc_keypair,
client_ephemeral_enc_pubkey,
1,
subcredential,
hs_ntor_intro_cell_keys_out);
}

/**
* As hs_ntor_service_get_introduce1_keys(), but take multiple subcredentials
* as input, and yield multiple sets of keys as output.
**/
int
hs_ntor_service_get_introduce1_keys_multi(
const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_keypair_t *intro_enc_keypair,
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
size_t n_subcredentials,
const hs_subcredential_t *subcredentials,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out)
{
int bad = 0;
uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN];
Expand All @@ -460,7 +482,8 @@ hs_ntor_service_get_introduce1_keys(
tor_assert(intro_auth_pubkey);
tor_assert(intro_enc_keypair);
tor_assert(client_ephemeral_enc_pubkey);
tor_assert(subcredential);
tor_assert(n_subcredentials >= 1);
tor_assert(subcredentials);
tor_assert(hs_ntor_intro_cell_keys_out);

/* Compute EXP(X, b) */
Expand All @@ -476,13 +499,16 @@ hs_ntor_service_get_introduce1_keys(
secret_input);
bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN);

/* Get ENC_KEY and MAC_KEY! */
get_introduce1_key_material(secret_input, subcredential,
hs_ntor_intro_cell_keys_out);
for (unsigned i = 0; i < n_subcredentials; ++i) {
/* Get ENC_KEY and MAC_KEY! */
get_introduce1_key_material(secret_input, &subcredentials[i],
&hs_ntor_intro_cell_keys_out[i]);
}

memwipe(secret_input, 0, sizeof(secret_input));
if (bad) {
memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t));
memwipe(hs_ntor_intro_cell_keys_out, 0,
sizeof(hs_ntor_intro_cell_keys_t) * n_subcredentials);
}

return bad ? -1 : 0;
Expand Down
21 changes: 19 additions & 2 deletions src/core/crypto/hs_ntor.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,20 @@ typedef struct hs_ntor_rend_cell_keys_t {
uint8_t ntor_key_seed[DIGEST256_LEN];
} hs_ntor_rend_cell_keys_t;

#define SUBCRED_LEN DIGEST256_LEN

/**
* A 'subcredential' used to prove knowledge of a hidden service.
**/
typedef struct hs_subcredential_t {
uint8_t subcred[SUBCRED_LEN];
} hs_subcredential_t;

int hs_ntor_client_get_introduce1_keys(
const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_public_key_t *intro_enc_pubkey,
const struct curve25519_keypair_t *client_ephemeral_enc_keypair,
const uint8_t *subcredential,
const hs_subcredential_t *subcredential,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);

int hs_ntor_client_get_rendezvous1_keys(
Expand All @@ -49,11 +58,19 @@ int hs_ntor_client_get_rendezvous1_keys(
const struct curve25519_public_key_t *service_ephemeral_rend_pubkey,
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);

int hs_ntor_service_get_introduce1_keys_multi(
const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_keypair_t *intro_enc_keypair,
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
size_t n_subcredentials,
const hs_subcredential_t *subcredentials,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);

int hs_ntor_service_get_introduce1_keys(
const struct ed25519_public_key_t *intro_auth_pubkey,
const struct curve25519_keypair_t *intro_enc_keypair,
const struct curve25519_public_key_t *client_ephemeral_enc_pubkey,
const uint8_t *subcredential,
const hs_subcredential_t *subcredential,
hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out);

int hs_ntor_service_get_rendezvous1_keys(
Expand Down
4 changes: 2 additions & 2 deletions src/core/or/circuitbuild.c
Original file line number Diff line number Diff line change
Expand Up @@ -2819,8 +2819,8 @@ extend_info_dup(extend_info_t *info)
* If there is no chosen exit, or if we don't know the node_t for
* the chosen exit, return NULL.
*/
const node_t *
build_state_get_exit_node(cpath_build_state_t *state)
MOCK_IMPL(const node_t *,
build_state_get_exit_node,(cpath_build_state_t *state))
{
if (!state || !state->chosen_exit)
return NULL;
Expand Down
3 changes: 2 additions & 1 deletion src/core/or/circuitbuild.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ int circuit_can_use_tap(const origin_circuit_t *circ);
int circuit_has_usable_onion_key(const origin_circuit_t *circ);
int extend_info_has_preferred_onion_key(const extend_info_t* ei);
const uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state);
const node_t *build_state_get_exit_node(cpath_build_state_t *state);
MOCK_DECL(const node_t *,
build_state_get_exit_node,(cpath_build_state_t *state));
const char *build_state_get_exit_nickname(cpath_build_state_t *state);

struct circuit_guard_state_t;
Expand Down
138 changes: 97 additions & 41 deletions src/feature/hs/hs_cell.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "feature/hs_common/replaycache.h"

#include "feature/hs/hs_cell.h"
#include "feature/hs/hs_ob.h"
#include "core/crypto/hs_ntor.h"

#include "core/or/origin_circuit_st.h"
Expand Down Expand Up @@ -67,32 +68,37 @@ compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len,
memwipe(mac_msg, 0, sizeof(mac_msg));
}

/** From a set of keys, subcredential and the ENCRYPTED section of an
* INTRODUCE2 cell, return a newly allocated intro cell keys structure.
* Finally, the client public key is copied in client_pk. On error, return
* NULL. */
/**
* From a set of keys, a list of subcredentials, and the ENCRYPTED section of
* an INTRODUCE2 cell, return an array of newly allocated intro cell keys
* structures. Finally, the client public key is copied in client_pk. On
* error, return NULL.
**/
static hs_ntor_intro_cell_keys_t *
get_introduce2_key_material(const ed25519_public_key_t *auth_key,
const curve25519_keypair_t *enc_key,
const uint8_t *subcredential,
size_t n_subcredentials,
const hs_subcredential_t *subcredentials,
const uint8_t *encrypted_section,
curve25519_public_key_t *client_pk)
{
hs_ntor_intro_cell_keys_t *keys;

tor_assert(auth_key);
tor_assert(enc_key);
tor_assert(subcredential);
tor_assert(n_subcredentials > 0);
tor_assert(subcredentials);
tor_assert(encrypted_section);
tor_assert(client_pk);

keys = tor_malloc_zero(sizeof(*keys));
keys = tor_calloc(n_subcredentials, sizeof(hs_ntor_intro_cell_keys_t));

/* First bytes of the ENCRYPTED section are the client public key. */
memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN);

if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk,
subcredential, keys) < 0) {
if (hs_ntor_service_get_introduce1_keys_multi(auth_key, enc_key, client_pk,
n_subcredentials,
subcredentials, keys) < 0) {
/* Don't rely on the caller to wipe this on error. */
memwipe(client_pk, 0, sizeof(curve25519_public_key_t));
tor_free(keys);
Expand Down Expand Up @@ -747,6 +753,74 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len)
return ret;
}

/** For the encrypted INTRO2 cell in <b>encrypted_section</b>, use the crypto
* material in <b>data</b> to compute the right ntor keys. Also validate the
* INTRO2 MAC to ensure that the keys are the right ones.
*
* Return NULL on failure to either produce the key material or on MAC
* valication. Else a newly allocated intro keys object. */
static hs_ntor_intro_cell_keys_t *
get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
const uint8_t *encrypted_section,
size_t encrypted_section_len)
{
hs_ntor_intro_cell_keys_t *intro_keys = NULL;
hs_ntor_intro_cell_keys_t *intro_keys_result = NULL;

/* Build the key material out of the key material found in the cell. */
intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
data->n_subcredentials,
data->subcredentials,
encrypted_section,
&data->client_pk);
if (intro_keys == NULL) {
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
"compute key material");
return NULL;
}

/* Make sure we are not about to underflow. */
if (BUG(encrypted_section_len < DIGEST256_LEN)) {
return NULL;
}

/* Validate MAC from the cell and our computed key material. The MAC field
* in the cell is at the end of the encrypted section. */
intro_keys_result = tor_malloc_zero(sizeof(*intro_keys_result));
for (unsigned i = 0; i < data->n_subcredentials; ++i) {
uint8_t mac[DIGEST256_LEN];

/* The MAC field is at the very end of the ENCRYPTED section. */
size_t mac_offset = encrypted_section_len - sizeof(mac);
/* Compute the MAC. Use the entire encoded payload with a length up to the
* ENCRYPTED section. */
compute_introduce_mac(data->payload,
data->payload_len - encrypted_section_len,
encrypted_section, encrypted_section_len,
intro_keys[i].mac_key,
sizeof(intro_keys[i].mac_key),
mac, sizeof(mac));
/* Time-invariant conditional copy: if the MAC is what we expected, then
* set intro_keys_result to intro_keys[i]. Otherwise, don't: but don't
* leak which one it was! */
bool equal = tor_memeq(mac, encrypted_section + mac_offset, sizeof(mac));
memcpy_if_true_timei(equal, intro_keys_result, &intro_keys[i],
sizeof(*intro_keys_result));
}

/* We no longer need intro_keys. */
memwipe(intro_keys, 0,
sizeof(hs_ntor_intro_cell_keys_t) * data->n_subcredentials);
tor_free(intro_keys);

if (safe_mem_is_zero(intro_keys_result, sizeof(*intro_keys_result))) {
log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell");
tor_free(intro_keys_result); /* sets intro_keys_result to NULL */
}

return intro_keys_result;
}

/** Parse the INTRODUCE2 cell using data which contains everything we need to
* do so and contains the destination buffers of information we extract and
* compute from the cell. Return 0 on success else a negative value. The
Expand Down Expand Up @@ -795,47 +869,29 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
/* Check our replay cache for this introduction point. */
if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section,
encrypted_section_len, &elapsed)) {
log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the"
log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the "
"same ENCRYPTED section was seen %ld seconds ago. "
"Dropping cell.", (long int) elapsed);
goto done;
}

/* Build the key material out of the key material found in the cell. */
intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp,
data->subcredential,
encrypted_section,
&data->client_pk);
if (intro_keys == NULL) {
log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to "
"compute key material on circuit %u for service %s",
TO_CIRCUIT(circ)->n_circ_id,
/* First bytes of the ENCRYPTED section are the client public key (they are
* guaranteed to exist because of the length check above). We are gonna use
* the client public key to compute the ntor keys and decrypt the payload:
*/
memcpy(&data->client_pk.public_key, encrypted_section,
CURVE25519_PUBKEY_LEN);

/* Get the right INTRODUCE2 ntor keys and verify the cell MAC */
intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section,
encrypted_section_len);
if (!intro_keys) {
log_warn(LD_REND, "Could not get valid INTRO2 keys on circuit %u "
"for service %s", TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
goto done;
}

/* Validate MAC from the cell and our computed key material. The MAC field
* in the cell is at the end of the encrypted section. */
{
uint8_t mac[DIGEST256_LEN];
/* The MAC field is at the very end of the ENCRYPTED section. */
size_t mac_offset = encrypted_section_len - sizeof(mac);
/* Compute the MAC. Use the entire encoded payload with a length up to the
* ENCRYPTED section. */
compute_introduce_mac(data->payload,
data->payload_len - encrypted_section_len,
encrypted_section, encrypted_section_len,
intro_keys->mac_key, sizeof(intro_keys->mac_key),
mac, sizeof(mac));
if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) {
log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on "
"circuit %u for service %s",
TO_CIRCUIT(circ)->n_circ_id,
safe_str_client(service->onion_address));
goto done;
}
}

{
/* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */
const uint8_t *encrypted_data =
Expand Down
Loading

0 comments on commit caa392a

Please sign in to comment.