mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-12-06 19:41:15 +01:00
hs-v3: Validate INTRO2 cells for onion balance
Closes #32709 Signed-off-by: David Goulet <dgoulet@torproject.org>
This commit is contained in:
committed by
Nick Mathewson
parent
ef28afa255
commit
02f1caa583
@@ -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"
|
||||
@@ -802,6 +803,47 @@ get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data,
|
||||
return intro_keys;
|
||||
}
|
||||
|
||||
/** Return the newly allocated intro keys using the given service
|
||||
* configuration and INTRODUCE2 data for the cell encrypted section.
|
||||
*
|
||||
* Every onion balance configured master key will be tried. If NULL is
|
||||
* returned, no hit was found for the onion balance keys. */
|
||||
static hs_ntor_intro_cell_keys_t *
|
||||
get_intro2_keys_as_ob(const hs_service_config_t *config,
|
||||
const hs_cell_introduce2_data_t *data,
|
||||
const uint8_t *encrypted_section,
|
||||
size_t encrypted_section_len)
|
||||
{
|
||||
uint8_t *ob_subcreds = NULL;
|
||||
size_t ob_num_subcreds;
|
||||
hs_ntor_intro_cell_keys_t *intro_keys = NULL;
|
||||
|
||||
ob_num_subcreds = hs_ob_get_subcredentials(config, &ob_subcreds);
|
||||
if (!ob_num_subcreds) {
|
||||
/* We are _not_ an OB instance since no configured master onion key(s)
|
||||
* were found and thus no subcredentials were built. */
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < ob_num_subcreds; idx++) {
|
||||
/* Copy current data into a new INTRO2 cell data. We will then change the
|
||||
* subcredential in order to validate. */
|
||||
hs_cell_introduce2_data_t new_data = *data;
|
||||
new_data.subcredential = &(ob_subcreds[idx * DIGEST256_LEN]);
|
||||
intro_keys = get_introduce2_keys_and_verify_mac(&new_data,
|
||||
encrypted_section,
|
||||
encrypted_section_len);
|
||||
if (intro_keys) {
|
||||
/* It validates. We have a hit as an onion balance instance. */
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
tor_free(ob_subcreds);
|
||||
return intro_keys;
|
||||
}
|
||||
|
||||
/** 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
|
||||
@@ -856,14 +898,36 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* 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);
|
||||
|
||||
/* If we are configured as an Onion Balance instance, we need to try
|
||||
* validation with the configured master public keys given in the config
|
||||
* file. This is because the master identity key and the blinded key is put
|
||||
* in the INTRODUCE2 cell by the client thus it will never validate with
|
||||
* this instance default public key. */
|
||||
if (service->config.ob_master_pubkeys) {
|
||||
intro_keys = get_intro2_keys_as_ob(&service->config, data,
|
||||
encrypted_section,
|
||||
encrypted_section_len);
|
||||
}
|
||||
if (!intro_keys) {
|
||||
log_info(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;
|
||||
/* We are not an onion balance instance or no keys matched, fallback to
|
||||
* our default values. */
|
||||
|
||||
/* 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_info(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;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -158,6 +158,8 @@ ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
|
||||
goto end;
|
||||
}
|
||||
smartlist_add(config->ob_master_pubkeys, pubkey);
|
||||
log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
|
||||
line->value);
|
||||
}
|
||||
/* Success. */
|
||||
ret = 1;
|
||||
@@ -171,6 +173,26 @@ ob_option_parse(hs_service_config_t *config, const ob_options_t *opts)
|
||||
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,
|
||||
uint8_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.
|
||||
*/
|
||||
|
||||
/** Read and parse the config file at fname on disk. The service config object
|
||||
* is populated with the options if any.
|
||||
*
|
||||
@@ -220,3 +242,78 @@ hs_ob_parse_config_file(hs_service_config_t *config)
|
||||
tor_free(config_file_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Compute all possible subcredentials for every onion master key in the
|
||||
* given service config object. The subcredentials 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 is set to NULL.
|
||||
*
|
||||
* Otherwise, this can't fail. */
|
||||
size_t
|
||||
hs_ob_get_subcredentials(const hs_service_config_t *config,
|
||||
uint8_t **subcredentials)
|
||||
{
|
||||
unsigned int num_pkeys, idx = 0;
|
||||
uint8_t *subcreds = NULL;
|
||||
const int steps[3] = {0, -1, 1};
|
||||
const unsigned int num_steps = ARRAY_LENGTH(steps);
|
||||
const size_t subcred_len = DIGEST256_LEN;
|
||||
const uint64_t tp = hs_get_time_period_num(0);
|
||||
|
||||
tor_assert(config);
|
||||
tor_assert(subcredentials);
|
||||
|
||||
num_pkeys = smartlist_len(config->ob_master_pubkeys);
|
||||
if (!num_pkeys) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Time to build all the subcredentials for each time period: 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. This function is called for _each_ INTRODUCE2 cell arriving
|
||||
* on this instance and thus the less we allocate small chunks often,
|
||||
* usually the healthier our overall memory will be.
|
||||
*
|
||||
* 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. */
|
||||
subcreds = tor_malloc_zero(subcred_len * num_steps * num_pkeys);
|
||||
|
||||
/* For each time period step. */
|
||||
for (unsigned int i = 0; i < num_steps; i++) {
|
||||
SMARTLIST_FOREACH_BEGIN(config->ob_master_pubkeys,
|
||||
const ed25519_public_key_t *, pkey) {
|
||||
build_subcredential(pkey, tp + steps[i],
|
||||
&(subcreds[idx * subcred_len]));
|
||||
idx++;
|
||||
} SMARTLIST_FOREACH_END(pkey);
|
||||
}
|
||||
|
||||
end:
|
||||
*subcredentials = subcreds;
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
|
||||
int hs_ob_parse_config_file(hs_service_config_t *config);
|
||||
|
||||
size_t hs_ob_get_subcredentials(const hs_service_config_t *config,
|
||||
uint8_t **subcredentials);
|
||||
|
||||
#ifdef HS_OB_PRIVATE
|
||||
|
||||
typedef struct ob_options_t {
|
||||
|
||||
Reference in New Issue
Block a user