/* 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 "feature/hs/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_notice(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->state.ob_subcreds) { tor_free(service->state.ob_subcreds); } service->state.ob_subcreds = ob_subcreds; service->state.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); }