2020-01-09 14:51:56 -05:00
|
|
|
/* Copyright (c) 2017-2020, The Tor Project, Inc. */
|
|
|
|
/* See LICENSE for licensing information */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \file hs_ob.c
|
|
|
|
* \brief Implement Onion Balance specific code.
|
|
|
|
*
|
|
|
|
* \details
|
|
|
|
*
|
|
|
|
* XXX:
|
|
|
|
**/
|
|
|
|
|
|
|
|
#define HS_OB_PRIVATE
|
|
|
|
|
|
|
|
#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);
|
2020-01-09 15:18:32 -05:00
|
|
|
log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered",
|
|
|
|
line->value);
|
2020-01-09 14:51:56 -05:00
|
|
|
}
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2020-01-09 15:18:32 -05:00
|
|
|
/** 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.
|
|
|
|
*/
|
|
|
|
|
2020-01-14 12:54:56 -05:00
|
|
|
/** 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;
|
|
|
|
}
|
|
|
|
|
2020-01-09 14:51:56 -05:00
|
|
|
/** 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;
|
|
|
|
}
|
2020-01-09 15:18:32 -05:00
|
|
|
|
|
|
|
/** 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;
|
|
|
|
}
|