summaryrefslogtreecommitdiff
path: root/src/or/hs_service.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/or/hs_service.c')
-rw-r--r--src/or/hs_service.c960
1 files changed, 911 insertions, 49 deletions
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 9114655edf..3cde2fe1b1 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -10,6 +10,7 @@
#include "or.h"
#include "circpathbias.h"
+#include "circuitbuild.h"
#include "circuitlist.h"
#include "config.h"
#include "networkstatus.h"
@@ -18,12 +19,18 @@
#include "rendservice.h"
#include "router.h"
#include "routerkeys.h"
+#include "routerlist.h"
#include "hs_common.h"
#include "hs_config.h"
+#include "hs_circuit.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
#include "hs_intropoint.h"
#include "hs_service.h"
+/* Trunnel */
+#include "ed25519_cert.h"
#include "hs/cell_establish_intro.h"
#include "hs/cell_common.h"
@@ -36,6 +43,19 @@
var = *var##_iter;
#define FOR_EACH_SERVICE_END } STMT_END ;
+/* Helper macro. Iterate over both current and previous descriptor of a
+ * service. The var is the name of the descriptor pointer. This macro skips
+ * any descriptor object of the service that is NULL. */
+#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \
+ STMT_BEGIN \
+ hs_service_descriptor_t *var; \
+ for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \
+ ++var ## _loop_idx) { \
+ (var ## _loop_idx == 0) ? (var = service->desc_current) : \
+ (var = service->desc_next); \
+ if (var == NULL) continue;
+#define FOR_EACH_DESCRIPTOR_END } STMT_END ;
+
/* Onion service directory file names. */
static const char *fname_keyfile_prefix = "hs_ed25519";
static const char *fname_hostname = "hostname";
@@ -213,13 +233,209 @@ service_free_all(void)
}
}
+/* Free a given service intro point object. */
+static void
+service_intro_point_free(hs_service_intro_point_t *ip)
+{
+ if (!ip) {
+ return;
+ }
+ memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp));
+ memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp));
+ crypto_pk_free(ip->legacy_key);
+ replaycache_free(ip->replay_cache);
+ hs_intro_free_content(&ip->base);
+ tor_free(ip);
+}
+
+/* Helper: free an hs_service_intro_point_t object. This function is used by
+ * digest256map_free() which requires a void * pointer. */
+static void
+service_intro_point_free_(void *obj)
+{
+ service_intro_point_free(obj);
+}
+
+/* Return a newly allocated service intro point and fully initialized from the
+ * given extend_info_t ei if non NULL. If is_legacy is true, we also generate
+ * the legacy key. On error, NULL is returned. */
+static hs_service_intro_point_t *
+service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
+{
+ hs_desc_link_specifier_t *ls;
+ hs_service_intro_point_t *ip;
+
+ ip = tor_malloc_zero(sizeof(*ip));
+ /* We'll create the key material. No need for extra strong, those are short
+ * term keys. */
+ ed25519_keypair_generate(&ip->auth_key_kp, 0);
+
+ /* XXX: These will be controlled by consensus params. (#20961) */
+ ip->introduce2_max =
+ crypto_rand_int_range(INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS,
+ INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS);
+ ip->time_to_expire = time(NULL) +
+ crypto_rand_int_range(INTRO_POINT_LIFETIME_MIN_SECONDS,
+ INTRO_POINT_LIFETIME_MAX_SECONDS);
+ ip->replay_cache = replaycache_new(0, 0);
+
+ /* Initialize the base object. We don't need the certificate object. */
+ ip->base.link_specifiers = smartlist_new();
+
+ /* Generate the encryption key for this intro point. */
+ curve25519_keypair_generate(&ip->enc_key_kp, 0);
+ /* Figure out if this chosen node supports v3 or is legacy only. */
+ if (is_legacy) {
+ ip->base.is_only_legacy = 1;
+ /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */
+ ip->legacy_key = crypto_pk_new();
+ if (crypto_pk_generate_key(ip->legacy_key) < 0) {
+ goto err;
+ }
+ }
+
+ if (ei == NULL) {
+ goto done;
+ }
+
+ /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are
+ * mandatory. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV4);
+ /* It is impossible to have an extend info object without a v4. */
+ tor_assert(ls);
+ smartlist_add(ip->base.link_specifiers, ls);
+ ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID);
+ /* It is impossible to have an extend info object without an identity
+ * digest. */
+ tor_assert(ls);
+ smartlist_add(ip->base.link_specifiers, ls);
+ ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID);
+ /* It is impossible to have an extend info object without an ed25519
+ * identity key. */
+ tor_assert(ls);
+ smartlist_add(ip->base.link_specifiers, ls);
+ /* IPv6 is optional. */
+ ls = hs_desc_link_specifier_new(ei, LS_IPV6);
+ if (ls) {
+ smartlist_add(ip->base.link_specifiers, ls);
+ }
+
+ /* Finally, copy onion key from the extend_info_t object. */
+ memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key));
+
+ done:
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ return NULL;
+}
+
+/* Add the given intro point object to the given intro point map. The intro
+ * point MUST have its RSA encryption key set if this is a legacy type or the
+ * authentication key set otherwise. */
+static void
+service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip)
+{
+ uint8_t key[DIGEST256_LEN] = {0};
+
+ tor_assert(map);
+ tor_assert(ip);
+
+ memcpy(key, ip->auth_key_kp.pubkey.pubkey, sizeof(key));
+ digest256map_set(map, key, ip);
+}
+
+/* From a given intro point, return the first link specifier of type
+ * encountered in the link specifier list. Return NULL if it can't be found.
+ *
+ * The caller does NOT have ownership of the object, the intro point does. */
+static hs_desc_link_specifier_t *
+get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type)
+{
+ hs_desc_link_specifier_t *lnk_spec = NULL;
+
+ tor_assert(ip);
+
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ hs_desc_link_specifier_t *, ls) {
+ if (ls->type == type) {
+ lnk_spec = ls;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(ls);
+
+ end:
+ return lnk_spec;
+}
+
+/* Given a service intro point, return the node_t associated to it. This can
+ * return NULL if the given intro point has no legacy ID or if the node can't
+ * be found in the consensus. */
+static const node_t *
+get_node_from_intro_point(const hs_service_intro_point_t *ip)
+{
+ const hs_desc_link_specifier_t *ls;
+
+ tor_assert(ip);
+
+ ls = get_link_spec_by_type(ip, LS_LEGACY_ID);
+ /* Legacy ID is mandatory for an intro point object to have. */
+ tor_assert(ls);
+ /* XXX In the future, we want to only use the ed25519 ID (#22173). */
+ return node_get_by_id((const char *) ls->u.legacy_id);
+}
+
+/* Return an introduction point circuit matching the given intro point object.
+ * NULL is returned is no such circuit can be found. */
+static origin_circuit_t *
+get_intro_circuit(const hs_service_intro_point_t *ip)
+{
+ origin_circuit_t *circ = NULL;
+
+ tor_assert(ip);
+
+ if (ip->base.is_only_legacy) {
+ uint8_t digest[DIGEST_LEN];
+ if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) {
+ goto end;
+ }
+ circ = hs_circuitmap_get_intro_circ_v2_service_side(digest);
+ } else {
+ circ = hs_circuitmap_get_intro_circ_v3_service_side(
+ &ip->auth_key_kp.pubkey);
+ }
+ end:
+ return circ;
+}
+
/* Close all rendezvous circuits for the given service. */
static void
close_service_rp_circuits(hs_service_t *service)
{
+ origin_circuit_t *ocirc = NULL;
+
tor_assert(service);
- /* XXX: To implement. */
- return;
+
+ /* The reason we go over all circuit instead of using the circuitmap API is
+ * because most hidden service circuits are rendezvous circuits so there is
+ * no real improvement at getting all rendezvous circuits from the
+ * circuitmap and then going over them all to find the right ones.
+ * Furthermore, another option would have been to keep a list of RP cookies
+ * for a service but it creates an engineering complexity since we don't
+ * have a "RP circuit closed" event to clean it up properly so we avoid a
+ * memory DoS possibility. */
+
+ while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) {
+ /* Only close circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, it is removed from the
+ * hs circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ }
}
/* Close the circuit(s) for the given map of introduction points. */
@@ -230,13 +446,11 @@ close_intro_circuits(hs_service_intropoints_t *intro_points)
DIGEST256MAP_FOREACH(intro_points->map, key,
const hs_service_intro_point_t *, ip) {
- origin_circuit_t *ocirc =
- hs_circuitmap_get_intro_circ_v3_service_side(
- &ip->auth_key_kp.pubkey);
+ origin_circuit_t *ocirc = get_intro_circuit(ip);
if (ocirc) {
- hs_circuitmap_remove_circuit(TO_CIRCUIT(ocirc));
/* Reason is FINISHED because service has been removed and thus the
- * circuit is considered old/uneeded. */
+ * circuit is considered old/uneeded. When freed, the circuit is removed
+ * from the HS circuitmap. */
circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
}
} DIGEST256MAP_FOREACH_END;
@@ -248,12 +462,9 @@ close_service_intro_circuits(hs_service_t *service)
{
tor_assert(service);
- if (service->desc_current) {
- close_intro_circuits(&service->desc_current->intro_points);
- }
- if (service->desc_next) {
- close_intro_circuits(&service->desc_next->intro_points);
- }
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ close_intro_circuits(&desc->intro_points);
+ } FOR_EACH_DESCRIPTOR_END;
}
/* Close any circuits related to the given service. */
@@ -281,7 +492,7 @@ move_descriptor_intro_points(hs_service_descriptor_t *src,
tor_assert(src);
tor_assert(dst);
- /* XXX: Free dst introduction points. */
+ digest256map_free(dst->intro_points.map, service_intro_point_free_);
dst->intro_points.map = src->intro_points.map;
/* Nullify the source. */
src->intro_points.map = NULL;
@@ -295,7 +506,6 @@ move_intro_points(hs_service_t *src, hs_service_t *dst)
tor_assert(src);
tor_assert(dst);
- /* Cleanup destination. */
if (src->desc_current && dst->desc_current) {
move_descriptor_intro_points(src->desc_current, dst->desc_current);
}
@@ -351,7 +561,6 @@ static void
register_all_services(void)
{
struct hs_service_ht *new_service_map;
- hs_service_t *s, **iter;
tor_assert(hs_service_staging_list);
@@ -371,6 +580,8 @@ register_all_services(void)
move_ephemeral_services(hs_service_map, new_service_map);
SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) {
+ hs_service_t *s;
+
/* Check if that service is already in our global map and if so, we'll
* transfer the intro points to it. */
s = find_service(hs_service_map, &snew->keys.identity_pk);
@@ -398,9 +609,9 @@ register_all_services(void)
/* Close any circuits associated with the non surviving services. Every
* service in the current global map are roaming. */
- HT_FOREACH(iter, hs_service_ht, hs_service_map) {
- close_service_circuits(*iter);
- }
+ FOR_EACH_SERVICE_BEGIN(service) {
+ close_service_circuits(service);
+ } FOR_EACH_SERVICE_END;
/* Time to make the switch. We'll clear the staging list because its content
* has now changed ownership to the map. */
@@ -522,13 +733,673 @@ load_service_keys(hs_service_t *service)
return ret;
}
+/* Free a given service descriptor object and all key material is wiped. */
+static void
+service_descriptor_free(hs_service_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+ hs_descriptor_free(desc->desc);
+ memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp));
+ memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp));
+ /* Cleanup all intro points. */
+ digest256map_free(desc->intro_points.map, service_intro_point_free_);
+ tor_free(desc);
+}
+
+/* Return a newly allocated service descriptor object. */
+static hs_service_descriptor_t *
+service_descriptor_new(void)
+{
+ hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc));
+ sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+ /* Initialize the intro points map. */
+ sdesc->intro_points.map = digest256map_new();
+ return sdesc;
+}
+
+#if 0
+/* Copy the descriptor link specifier object from src to dst. */
+static void
+link_specifier_copy(hs_desc_link_specifier_t *dst,
+ const hs_desc_link_specifier_t *src)
+{
+ tor_assert(dst);
+ tor_assert(src);
+ memcpy(dst, src, sizeof(hs_desc_link_specifier_t));
+}
+
+/* Using a given descriptor signing keypair signing_kp, a service intro point
+ * object ip and the time now, setup the content of an already allocated
+ * descriptor intro desc_ip.
+ *
+ * Return 0 on success else a negative value. */
+static int
+setup_desc_intro_point(const ed25519_keypair_t *signing_kp,
+ const hs_service_intro_point_t *ip,
+ time_t now, hs_desc_intro_point_t *desc_ip)
+{
+ int ret = -1;
+ time_t nearest_hour = now - (now % 3600);
+
+ tor_assert(signing_kp);
+ tor_assert(ip);
+ tor_assert(desc_ip);
+
+ /* Copy the onion key. */
+ memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key));
+
+ /* Key and certificate material. */
+ desc_ip->auth_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_AUTH_HS_IP_KEY,
+ &ip->auth_key_kp.pubkey,
+ nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->auth_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create intro point auth-key certificate");
+ goto done;
+ }
+
+ /* Copy link specifier(s). */
+ SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers,
+ const hs_desc_link_specifier_t *, ls) {
+ hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup));
+ link_specifier_copy(dup, ls);
+ smartlist_add(desc_ip->link_specifiers, dup);
+ } SMARTLIST_FOREACH_END(ls);
+
+ /* For a legacy intro point, we'll use an RSA/ed cross certificate. */
+ if (ip->base.is_only_legacy) {
+ desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key);
+ /* Create cross certification cert. */
+ ssize_t cert_len = tor_make_rsa_ed25519_crosscert(
+ &signing_kp->pubkey,
+ desc_ip->legacy.key,
+ nearest_hour + HS_DESC_CERT_LIFETIME,
+ &desc_ip->legacy.cert.encoded);
+ if (cert_len < 0) {
+ log_warn(LD_REND, "Unable to create enc key legacy cross cert.");
+ goto done;
+ }
+ desc_ip->legacy.cert.len = cert_len;
+ }
+
+ /* Encryption key and its cross certificate. */
+ {
+ ed25519_public_key_t ed25519_pubkey;
+
+ /* Use the public curve25519 key. */
+ memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey,
+ sizeof(desc_ip->enc_key));
+ /* The following can't fail. */
+ ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey,
+ &ip->enc_key_kp.pubkey,
+ 0);
+ desc_ip->enc_key_cert = tor_cert_create(signing_kp,
+ CERT_TYPE_CROSS_HS_IP_KEYS,
+ &ed25519_pubkey, nearest_hour,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (desc_ip->enc_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create enc key curve25519 cross cert.");
+ goto done;
+ }
+ }
+ /* Success. */
+ ret = 0;
+
+ done:
+ return ret;
+}
+
+/* Using the given descriptor from the given service, build the descriptor
+ * intro point list so we can then encode the descriptor for publication. This
+ * function does not pick intro points, they have to be in the descriptor
+ * current map. Cryptographic material (keys) must be initialized in the
+ * descriptor for this function to make sense. */
+static void
+build_desc_intro_points(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Ease our life. */
+ encrypted = &desc->desc->encrypted_data;
+ /* Cleanup intro points, we are about to set them from scratch. */
+ hs_descriptor_free_intro_points(desc->desc);
+
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ const hs_service_intro_point_t *, ip) {
+ hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new();
+ if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) {
+ hs_desc_intro_point_free(desc_ip);
+ continue;
+ }
+ /* We have a valid descriptor intro point. Add it to the list. */
+ smartlist_add(encrypted->intro_points, desc_ip);
+ } DIGEST256MAP_FOREACH_END;
+}
+
+#endif /* build_desc_intro_points is disabled because not used */
+
+/* Populate the descriptor encrypted section fomr the given service object.
+ * This will generate a valid list of introduction points that can be used
+ * after for circuit creation. Return 0 on success else -1 on error. */
+static int
+build_service_desc_encrypted(const hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ hs_desc_encrypted_data_t *encrypted;
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ encrypted = &desc->desc->encrypted_data;
+
+ encrypted->create2_ntor = 1;
+ encrypted->single_onion_service = service->config.is_single_onion;
+
+ /* Setup introduction points from what we have in the service. */
+ if (encrypted->intro_points == NULL) {
+ encrypted->intro_points = smartlist_new();
+ }
+ /* We do NOT build introduction point yet, we only do that once the circuit
+ * have been opened. Until we have the right number of introduction points,
+ * we do not encode anything in the descriptor. */
+
+ /* XXX: Support client authorization (#20700). */
+ encrypted->intro_auth_types = NULL;
+ return 0;
+}
+
+/* Populare the descriptor plaintext section from the given service object.
+ * The caller must make sure that the keys in the descriptors are valid that
+ * is are non-zero. Return 0 on success else -1 on error. */
+static int
+build_service_desc_plaintext(const hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ int ret = -1;
+ hs_desc_plaintext_data_t *plaintext;
+
+ tor_assert(service);
+ tor_assert(desc);
+ /* XXX: Use a "assert_desc_ok()" ? */
+ tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp,
+ sizeof(desc->blinded_kp)));
+ tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp,
+ sizeof(desc->signing_kp)));
+
+ /* Set the subcredential. */
+ hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey,
+ desc->desc->subcredential);
+
+ plaintext = &desc->desc->plaintext_data;
+
+ plaintext->version = service->config.version;
+ plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME;
+ plaintext->signing_key_cert =
+ tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC,
+ &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ if (plaintext->signing_key_cert == NULL) {
+ log_warn(LD_REND, "Unable to create descriptor signing certificate for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ goto end;
+ }
+ /* Copy public key material to go in the descriptor. */
+ ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey);
+ ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey);
+ /* Success. */
+ ret = 0;
+
+ end:
+ return ret;
+}
+
+/* For the given service and descriptor object, create the key material which
+ * is the blinded keypair and the descriptor signing keypair. Return 0 on
+ * success else -1 on error where the generated keys MUST be ignored. */
+static int
+build_service_desc_keys(const hs_service_t *service,
+ hs_service_descriptor_t *desc,
+ uint64_t time_period_num)
+{
+ int ret = 0;
+ ed25519_keypair_t kp;
+
+ tor_assert(desc);
+ tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk,
+ ED25519_PUBKEY_LEN));
+
+ /* XXX: Support offline key feature (#18098). */
+
+ /* Copy the identity keys to the keypair so we can use it to create the
+ * blinded key. */
+ memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey));
+ memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey));
+ /* Build blinded keypair for this time period. */
+ hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp);
+ /* Let's not keep too much traces of our keys in memory. */
+ memwipe(&kp, 0, sizeof(kp));
+
+ /* No need for extra strong, this is a temporary key only for this
+ * descriptor. Nothing long term. */
+ if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) {
+ log_warn(LD_REND, "Can't generate descriptor signing keypair for "
+ "service %s",
+ safe_str_client(service->onion_address));
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* Given a service and the current time, build a descriptor for the service.
+ * This function does not pick introduction point, this needs to be done by
+ * the update function. On success, desc_out will point to the newly allocated
+ * descriptor object.
+ *
+ * This can error if we are unable to create keys or certificate. */
+static void
+build_service_descriptor(hs_service_t *service, time_t now,
+ uint64_t time_period_num,
+ hs_service_descriptor_t **desc_out)
+{
+ char *encoded_desc;
+ hs_service_descriptor_t *desc;
+
+ tor_assert(service);
+ tor_assert(desc_out);
+
+ desc = service_descriptor_new();
+
+ /* Create the needed keys so we can setup the descriptor content. */
+ if (build_service_desc_keys(service, desc, time_period_num) < 0) {
+ goto err;
+ }
+ /* Setup plaintext descriptor content. */
+ if (build_service_desc_plaintext(service, desc, now) < 0) {
+ goto err;
+ }
+ /* Setup encrypted descriptor content. */
+ if (build_service_desc_encrypted(service, desc) < 0) {
+ goto err;
+ }
+
+ /* Let's make sure that we've created a descriptor that can actually be
+ * encoded properly. This function also checks if the encoded output is
+ * decodable after. */
+ if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp,
+ &encoded_desc) < 0)) {
+ goto err;
+ }
+ tor_free(encoded_desc);
+
+ /* Assign newly built descriptor to the next slot. */
+ *desc_out = desc;
+ return;
+
+ err:
+ service_descriptor_free(desc);
+}
+
+/* Build descriptors for each service if needed. There are conditions to build
+ * a descriptor which are details in the function. */
+static void
+build_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ if (service->desc_current == NULL) {
+ /* This means we just booted up because else this descriptor will never
+ * be NULL as it should always point to the descriptor that was in
+ * desc_next after rotation. */
+ build_service_descriptor(service, now, hs_get_time_period_num(now),
+ &service->desc_current);
+
+ log_info(LD_REND, "Hidden service %s current descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ /* A next descriptor to NULL indicate that we need to build a fresh one if
+ * we are in the overlap period for the _next_ time period since it means
+ * we either just booted or we just rotated our descriptors. */
+ if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) {
+ build_service_descriptor(service, now, hs_get_next_time_period_num(now),
+ &service->desc_next);
+ log_info(LD_REND, "Hidden service %s next descriptor successfully "
+ "built. Now scheduled for upload.",
+ safe_str_client(service->onion_address));
+ }
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Randomly pick a node to become an introduction point but not present in the
+ * given exclude_nodes list. The chosen node is put in the exclude list
+ * regardless of success or not because in case of failure, the node is simply
+ * unsusable from that point on. If direct_conn is set, try to pick a node
+ * that our local firewall/policy allows to directly connect to and if not,
+ * fallback to a normal 3-hop node. Return a newly allocated service intro
+ * point ready to be used for encoding. NULL on error. */
+static hs_service_intro_point_t *
+pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
+{
+ const node_t *node;
+ extend_info_t *info = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ /* Normal 3-hop introduction point flags. */
+ router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC;
+ /* Single onion flags. */
+ router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN;
+
+ node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes,
+ direct_conn ? direct_flags : flags);
+ if (node == NULL && direct_conn) {
+ /* Unable to find a node for direct connection, let's fall back to a
+ * normal 3-hop node. */
+ node = router_choose_random_node(exclude_nodes,
+ get_options()->ExcludeNodes, flags);
+ }
+ if (!node) {
+ goto err;
+ }
+
+ /* We have a suitable node, add it to the exclude list. We do this *before*
+ * we can validate the extend information because even in case of failure,
+ * we don't want to use that node anymore. */
+ smartlist_add(exclude_nodes, (void *) node);
+
+ /* We do this to ease our life but also this call makes appropriate checks
+ * of the node object such as validating ntor support for instance. */
+ info = extend_info_from_node(node, direct_conn);
+ if (BUG(info == NULL)) {
+ goto err;
+ }
+ /* Create our objects and populate them with the node information. */
+ ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node));
+ if (ip == NULL) {
+ goto err;
+ }
+ extend_info_free(info);
+ return ip;
+ err:
+ service_intro_point_free(ip);
+ extend_info_free(info);
+ return NULL;
+}
+
+/* For a given descriptor from the given service, pick any needed intro points
+ * and update the current map with those newly picked intro points. Return the
+ * number node that might have been added to the descriptor current map. */
+static unsigned int
+pick_needed_intro_points(hs_service_t *service,
+ hs_service_descriptor_t *desc)
+{
+ int i = 0, num_needed_ip;
+ smartlist_t *exclude_nodes = smartlist_new();
+
+ tor_assert(service);
+ tor_assert(desc);
+
+ /* Compute how many intro points we actually need to open. */
+ num_needed_ip = service->config.num_intro_points -
+ digest256map_size(desc->intro_points.map);
+ if (BUG(num_needed_ip < 0)) {
+ /* Let's not make tor freak out here and just skip this. */
+ goto done;
+ }
+ /* We want to end up with config.num_intro_points intro points, but if we
+ * have no intro points at all (chances are they all cycled or we are
+ * starting up), we launch NUM_INTRO_POINTS_EXTRA extra circuits and use the
+ * first config.num_intro_points that complete. See proposal #155, section 4
+ * for the rationale of this which is purely for performance.
+ *
+ * The ones after the first config.num_intro_points will be converted to
+ * 'General' internal circuits and then we'll drop them from the list of
+ * intro points. */
+ if (digest256map_size(desc->intro_points.map) == 0) {
+ /* XXX: Should a consensus param control that value? */
+ num_needed_ip += NUM_INTRO_POINTS_EXTRA;
+ }
+
+ /* Build an exclude list of nodes of our intro point(s). The expiring intro
+ * points are OK to pick again because this is afterall a concept of round
+ * robin so they are considered valid nodes to pick again. */
+ DIGEST256MAP_FOREACH(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ smartlist_add(exclude_nodes, (void *) get_node_from_intro_point(ip));
+ } DIGEST256MAP_FOREACH_END;
+
+ for (i = 0; i < num_needed_ip; i++) {
+ hs_service_intro_point_t *ip;
+
+ /* This function will add the picked intro point node to the exclude nodes
+ * list so we don't pick the same one at the next iteration. */
+ ip = pick_intro_point(service->config.is_single_onion, exclude_nodes);
+ if (ip == NULL) {
+ /* If we end up unable to pick an introduction point it is because we
+ * can't find suitable node and calling this again is highly unlikely to
+ * give us a valid node all of the sudden. */
+ log_info(LD_REND, "Unable to find a suitable node to be an "
+ "introduction point for service %s.",
+ safe_str_client(service->onion_address));
+ goto done;
+ }
+ /* Valid intro point object, add it to the descriptor current map. */
+ service_intro_point_add(desc->intro_points.map, ip);
+ }
+
+ /* Success. */
+ done:
+ /* We don't have ownership of the node_t object in this list. */
+ smartlist_free(exclude_nodes);
+ return i;
+}
+
+/* Update the given descriptor from the given service. The possible update
+ * actions includes:
+ * - Picking missing intro points if needed.
+ * - Incrementing the revision counter if needed.
+ */
+static void
+update_service_descriptor(hs_service_t *service,
+ hs_service_descriptor_t *desc, time_t now)
+{
+ unsigned int num_intro_points;
+
+ tor_assert(service);
+ tor_assert(desc);
+ tor_assert(desc->desc);
+
+ num_intro_points = digest256map_size(desc->intro_points.map);
+
+ /* Pick any missing introduction point(s). */
+ if (num_intro_points < service->config.num_intro_points) {
+ unsigned int num_new_intro_points = pick_needed_intro_points(service,
+ desc);
+ if (num_new_intro_points != 0) {
+ log_info(LD_REND, "Service %s just picked %u intro points and wanted "
+ "%u. It currently has %d intro points. "
+ "Launching ESTABLISH_INTRO circuit shortly.",
+ safe_str_client(service->onion_address),
+ num_new_intro_points,
+ service->config.num_intro_points - num_intro_points,
+ num_intro_points);
+ /* We'll build those introduction point into the descriptor once we have
+ * confirmation that the circuits are opened and ready. However,
+ * indicate that this descriptor should be uploaded from now on. */
+ desc->next_upload_time = now;
+ }
+ }
+}
+
+/* Update descriptors for each service if needed. */
+static void
+update_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We'll try to update each descriptor that is if certain conditions apply
+ * in order for the descriptor to be updated. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ update_service_descriptor(service, desc, now);
+ } FOR_EACH_DESCRIPTOR_END;
+ } FOR_EACH_SERVICE_END;
+}
+
+/* Return true iff the given intro point has expired that is it has been used
+ * for too long or we've reached our max seen INTRODUCE2 cell. */
+static int
+intro_point_should_expire(const hs_service_intro_point_t *ip,
+ time_t now)
+{
+ tor_assert(ip);
+
+ if (ip->introduce2_count >= ip->introduce2_max) {
+ goto expired;
+ }
+
+ if (ip->time_to_expire <= now) {
+ goto expired;
+ }
+
+ /* Not expiring. */
+ return 0;
+ expired:
+ return 1;
+}
+
+/* Go over the given set of intro points for each service and remove any
+ * invalid ones. The conditions for removal are:
+ *
+ * - The node doesn't exists anymore (not in consensus)
+ * OR
+ * - The intro point maximum circuit retry count has been reached and no
+ * circuit can be found associated with it.
+ * OR
+ * - The intro point has expired and we should pick a new one.
+ *
+ * If an intro point is removed, the circuit (if any) is immediately close.
+ * If a circuit can't be found, the intro point is kept if it hasn't reached
+ * its maximum circuit retry value and thus should be retried. */
+static void
+cleanup_intro_points(hs_service_t *service, time_t now)
+{
+ tor_assert(service);
+
+ /* For both descriptors, cleanup the intro points. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ /* Go over the current intro points we have, make sure they are still
+ * valid and remove any of them that aren't. */
+ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key,
+ hs_service_intro_point_t *, ip) {
+ const node_t *node = get_node_from_intro_point(ip);
+ origin_circuit_t *ocirc = get_intro_circuit(ip);
+ int has_expired = intro_point_should_expire(ip, now);
+
+ /* We cleanup an intro point if it has expired or if we do not know the
+ * node_t anymore (removed from our latest consensus) or if we've
+ * reached the maximum number of retry with a non existing circuit. */
+ if (has_expired || node == NULL ||
+ (ocirc == NULL &&
+ ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES)) {
+ MAP_DEL_CURRENT(key);
+ service_intro_point_free(ip);
+ /* XXX: Legacy code does NOT do that, it keeps the circuit open until
+ * a new descriptor is uploaded and then closed all expiring intro
+ * point circuit. Here, we close immediately and because we just
+ * discarded the intro point, a new one will be selected, a new
+ * descriptor created and uploaded. There is no difference to an
+ * attacker between the timing of a new consensus and intro point
+ * rotation (possibly?). */
+ if (ocirc) {
+ /* After this, no new cells will be handled on the circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ continue;
+ }
+ if (ocirc == NULL) {
+ /* Circuit disappeared so make sure the intro point is updated. By
+ * keeping the object in the descriptor, we'll be able to retry. */
+ ip->circuit_established = 0;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/** We just entered overlap period and we need to rotate our <b>service</b>
+ * descriptors */
+static void
+rotate_service_descriptors(hs_service_t *service)
+{
+ if (service->desc_current) {
+ /* Close all IP circuits for the descriptor. */
+ close_intro_circuits(&service->desc_current->intro_points);
+ /* We don't need this one anymore, we won't serve any clients coming with
+ * this service descriptor. */
+ service_descriptor_free(service->desc_current);
+ }
+ /* The next one become the current one and emptying the next will trigger
+ * a descriptor creation for it. */
+ service->desc_current = service->desc_next;
+ service->desc_next = NULL;
+}
+
+/* Rotate descriptors for each service if needed. If we are just entering
+ * the overlap period, rotate them that is point the previous descriptor to
+ * the current and cleanup the previous one. A non existing current
+ * descriptor will trigger a descriptor build for the next time period. */
+static void
+rotate_all_descriptors(time_t now)
+{
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* We are _not_ in the overlap period so skip rotation. */
+ if (!hs_overlap_mode_is_active(NULL, now)) {
+ service->state.in_overlap_period = 0;
+ continue;
+ }
+ /* We've entered the overlap period already so skip rotation. */
+ if (service->state.in_overlap_period) {
+ continue;
+ }
+ /* It's the first time the service encounters the overlap period so flag
+ * it in order to make sure we don't rotate at next check. */
+ service->state.in_overlap_period = 1;
+
+ /* If we have a next descriptor lined up, rotate the descriptors so that it
+ * becomes current. */
+ if (service->desc_next) {
+ rotate_service_descriptors(service);
+ }
+ log_info(LD_REND, "We've just entered the overlap period. Service %s "
+ "descriptors have been rotated!",
+ safe_str_client(service->onion_address));
+ } FOR_EACH_SERVICE_END;
+}
+
/* Scheduled event run from the main loop. Make sure all our services are up
* to date and ready for the other scheduled events. This includes looking at
* the introduction points status and descriptor rotation time. */
static void
run_housekeeping_event(time_t now)
{
- (void) now;
+ /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are
+ * simply moving things around or removing uneeded elements. */
+
+ FOR_EACH_SERVICE_BEGIN(service) {
+ /* Cleanup invalid intro points from the service descriptor. */
+ cleanup_intro_points(service, now);
+
+ /* At this point, the service is now ready to go through the scheduled
+ * events guaranteeing a valid state. Intro points might be missing from
+ * the descriptors after the cleanup but the update/build process will
+ * make sure we pick those missing ones. */
+ } FOR_EACH_SERVICE_END;
}
/* Scheduled event run from the main loop. Make sure all descriptors are up to
@@ -537,14 +1408,21 @@ run_housekeeping_event(time_t now)
static void
run_build_descriptor_event(time_t now)
{
- (void) now;
/* For v2 services, this step happens in the upload event. */
/* Run v3+ events. */
- FOR_EACH_SERVICE_BEGIN(service) {
- /* XXX: Actually build descriptors. */
- (void) service;
- } FOR_EACH_SERVICE_END;
+ /* We start by rotating the descriptors only if needed. */
+ rotate_all_descriptors(now);
+
+ /* Then, we'll try to build new descriptors that we might need. The
+ * condition is that the next descriptor is non existing because it has
+ * been rotated or we just started up. */
+ build_all_descriptors(now);
+
+ /* Finally, we'll check if we should update the descriptors. Missing
+ * introduction points will be picked in this function which is useful for
+ * newly built descriptors. */
+ update_all_descriptors(now);
}
/* Scheduled event run from the main loop. Make sure we have all the circuits
@@ -552,8 +1430,6 @@ run_build_descriptor_event(time_t now)
static void
run_build_circuit_event(time_t now)
{
- (void) now;
-
/* Make sure we can actually have enough information to build internal
* circuits as required by services. */
if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
@@ -562,11 +1438,14 @@ run_build_circuit_event(time_t now)
/* Run v2 check. */
if (num_rend_services() > 0) {
- rend_consider_services_intro_points();
+ rend_consider_services_intro_points(now);
}
+
/* Run v3+ check. */
FOR_EACH_SERVICE_BEGIN(service) {
/* XXX: Check every service for validity of circuits. */
+ /* XXX: Make sure we have a retry period so we don't stress circuit
+ * creation. */
(void) service;
} FOR_EACH_SERVICE_END;
}
@@ -672,27 +1551,10 @@ hs_service_free(hs_service_t *service)
return;
}
- /* Free descriptors. */
- if (service->desc_current) {
- hs_descriptor_free(service->desc_current->desc);
- /* Wipe keys. */
- memwipe(&service->desc_current->signing_kp, 0,
- sizeof(service->desc_current->signing_kp));
- memwipe(&service->desc_current->blinded_kp, 0,
- sizeof(service->desc_current->blinded_kp));
- /* XXX: Free intro points. */
- tor_free(service->desc_current);
- }
- if (service->desc_next) {
- hs_descriptor_free(service->desc_next->desc);
- /* Wipe keys. */
- memwipe(&service->desc_next->signing_kp, 0,
- sizeof(service->desc_next->signing_kp));
- memwipe(&service->desc_next->blinded_kp, 0,
- sizeof(service->desc_next->blinded_kp));
- /* XXX: Free intro points. */
- tor_free(service->desc_next);
- }
+ /* Free descriptors. Go over both descriptor with this loop. */
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ service_descriptor_free(desc);
+ } FOR_EACH_DESCRIPTOR_END;
/* Free service configuration. */
service_clear_config(&service->config);