From 3e537c6fe4b10b72079524829e13de65f3400c2b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Sun, 23 Jul 2017 12:32:16 -0400 Subject: [PATCH 01/91] trunnel: Add prop224 RENDEZVOUS1 cell definition Signed-off-by: David Goulet --- src/trunnel/hs/cell_rendezvous.c | 292 +++++++++++++++++++++++++ src/trunnel/hs/cell_rendezvous.h | 118 ++++++++++ src/trunnel/hs/cell_rendezvous.trunnel | 18 ++ src/trunnel/include.am | 2 + 4 files changed, 430 insertions(+) create mode 100644 src/trunnel/hs/cell_rendezvous.c create mode 100644 src/trunnel/hs/cell_rendezvous.h create mode 100644 src/trunnel/hs/cell_rendezvous.trunnel diff --git a/src/trunnel/hs/cell_rendezvous.c b/src/trunnel/hs/cell_rendezvous.c new file mode 100644 index 0000000000..e961cd09d4 --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.c @@ -0,0 +1,292 @@ +/* cell_rendezvous.c -- generated by Trunnel v1.5.1. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#include +#include "trunnel-impl.h" + +#include "cell_rendezvous.h" + +#define TRUNNEL_SET_ERROR_CODE(obj) \ + do { \ + (obj)->trunnel_error_code_ = 1; \ + } while (0) + +#if defined(__COVERITY__) || defined(__clang_analyzer__) +/* If we're runnning a static analysis tool, we don't want it to complain + * that some of our remaining-bytes checks are dead-code. */ +int cellrendezvous_deadcode_dummy__ = 0; +#define OR_DEADCODE_DUMMY || cellrendezvous_deadcode_dummy__ +#else +#define OR_DEADCODE_DUMMY +#endif + +#define CHECK_REMAINING(nbytes, label) \ + do { \ + if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ + goto label; \ + } \ + } while (0) + +trn_cell_rendezvous1_t * +trn_cell_rendezvous1_new(void) +{ + trn_cell_rendezvous1_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous1_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_rendezvous1_clear(trn_cell_rendezvous1_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->handshake_info); + TRUNNEL_DYNARRAY_CLEAR(&obj->handshake_info); +} + +void +trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *obj) +{ + if (obj == NULL) + return; + trn_cell_rendezvous1_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_rendezvous1_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp) +{ + (void)inp; return TRUNNEL_REND_COOKIE_LEN; +} + +uint8_t +trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx) +{ + trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); + return inp->rendezvous_cookie[idx]; +} + +uint8_t +trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx) +{ + return trn_cell_rendezvous1_get_rendezvous_cookie((trn_cell_rendezvous1_t*)inp, idx); +} +int +trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); + inp->rendezvous_cookie[idx] = elt; + return 0; +} + +uint8_t * +trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp) +{ + return inp->rendezvous_cookie; +} +const uint8_t * +trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous1_getarray_rendezvous_cookie((trn_cell_rendezvous1_t*)inp); +} +size_t +trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->handshake_info); +} + +uint8_t +trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->handshake_info, idx); +} + +uint8_t +trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx) +{ + return trn_cell_rendezvous1_get_handshake_info((trn_cell_rendezvous1_t*)inp, idx); +} +int +trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->handshake_info, idx, elt); + return 0; +} +int +trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt) +{ + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->handshake_info, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp) +{ + return inp->handshake_info.elts_; +} +const uint8_t * +trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous1_getarray_handshake_info((trn_cell_rendezvous1_t*)inp); +} +int +trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen) +{ + uint8_t *newptr; + newptr = trunnel_dynarray_setlen(&inp->handshake_info.allocated_, + &inp->handshake_info.n_, inp->handshake_info.elts_, newlen, + sizeof(inp->handshake_info.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->handshake_info.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_rendezvous1_check(obj)) + return -1; + + + /* Length of u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + result += TRUNNEL_REND_COOKIE_LEN; + + /* Length of u8 handshake_info[] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->handshake_info); + return result; +} +int +trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_rendezvous1_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_rendezvous1_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_rendezvous1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + trunnel_assert(written <= avail); + if (avail - written < TRUNNEL_REND_COOKIE_LEN) + goto truncated; + memcpy(ptr, obj->rendezvous_cookie, TRUNNEL_REND_COOKIE_LEN); + written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; + + /* Encode u8 handshake_info[] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->handshake_info); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->handshake_info.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_rendezvous1_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_rendezvous1_parse_into(trn_cell_rendezvous1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + CHECK_REMAINING(TRUNNEL_REND_COOKIE_LEN, truncated); + memcpy(obj->rendezvous_cookie, ptr, TRUNNEL_REND_COOKIE_LEN); + remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; + + /* Parse u8 handshake_info[] */ + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->handshake_info, remaining, {}); + obj->handshake_info.n_ = remaining; + if (remaining) + memcpy(obj->handshake_info.elts_, ptr, remaining); + ptr += remaining; remaining -= remaining; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; +} + +ssize_t +trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_rendezvous1_new(); + if (NULL == *output) + return -1; + result = trn_cell_rendezvous1_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_rendezvous1_free(*output); + *output = NULL; + } + return result; +} diff --git a/src/trunnel/hs/cell_rendezvous.h b/src/trunnel/hs/cell_rendezvous.h new file mode 100644 index 0000000000..2387d77f4f --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.h @@ -0,0 +1,118 @@ +/* cell_rendezvous.h -- generated by by Trunnel v1.5.1. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#ifndef TRUNNEL_CELL_RENDEZVOUS_H +#define TRUNNEL_CELL_RENDEZVOUS_H + +#include +#include "trunnel.h" + +#define TRUNNEL_REND_COOKIE_LEN 20 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1) +struct trn_cell_rendezvous1_st { + uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN]; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) handshake_info; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t; +/** Return a newly allocated trn_cell_rendezvous1 with all elements + * set to zero. + */ +trn_cell_rendezvous1_t *trn_cell_rendezvous1_new(void); +/** Release all storage held by the trn_cell_rendezvous1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *victim); +/** Try to parse a trn_cell_rendezvous1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_rendezvous1_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_rendezvous1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj); +/** Try to encode the trn_cell_rendezvous1 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t trn_cell_rendezvous1_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous1_t *input); +/** Check whether the internal state of the trn_cell_rendezvous1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj); +/** Return the (constant) length of the array holding the + * rendezvous_cookie field of the trn_cell_rendezvous1_t in 'inp'. + */ +size_t trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp'. + */ +uint8_t trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx); +/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return + * a const pointer + */ +uint8_t trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp', so that + * it will hold the value 'elt'. + */ +int trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array + * field rendezvous_cookie of 'inp'. + */ +uint8_t * trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp); +/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return + * a const pointer + */ +const uint8_t * trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp); +/** Return the length of the dynamic array holding the handshake_info + * field of the trn_cell_rendezvous1_t in 'inp'. + */ +size_t trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp'. + */ +uint8_t trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx); +/** As trn_cell_rendezvous1_get_handshake_info, but take and return a + * const pointer + */ +uint8_t trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp', so that it + * will hold the value 'elt'. + */ +int trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp'. + */ +int trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field handshake_info + * of 'inp'. + */ +uint8_t * trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp); +/** As trn_cell_rendezvous1_get_handshake_info, but take and return a + * const pointer + */ +const uint8_t * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp); +/** Change the length of the variable-length array field + * handshake_info of 'inp' to 'newlen'.Fill extra elements with 0. + * Return 0 on success; return -1 and set the error code on 'inp' on + * failure. + */ +int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen); + + +#endif diff --git a/src/trunnel/hs/cell_rendezvous.trunnel b/src/trunnel/hs/cell_rendezvous.trunnel new file mode 100644 index 0000000000..27f1728b4a --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.trunnel @@ -0,0 +1,18 @@ +/* + * This contains the definition of the RENDEZVOUS1 cell for onion service + * version 3 and onward. The following format is specified in proposal 224 + * section 4.2. + */ + +/* Rendezvous cookie length. */ +const TRUNNEL_REND_COOKIE_LEN = 20; + +/* RENDEZVOUS1 payload. See details in section 4.2. */ +struct trn_cell_rendezvous1 { + /* The RENDEZVOUS_COOKIE field. */ + u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN]; + + /* The HANDSHAKE_INFO field which has a variable length depending on the + * handshake type used. */ + u8 handshake_info[]; +}; diff --git a/src/trunnel/include.am b/src/trunnel/include.am index de6cf4781f..ca79ff3a39 100644 --- a/src/trunnel/include.am +++ b/src/trunnel/include.am @@ -22,6 +22,7 @@ TRUNNELSOURCES = \ src/trunnel/hs/cell_common.c \ src/trunnel/hs/cell_establish_intro.c \ src/trunnel/hs/cell_introduce1.c \ + src/trunnel/hs/cell_rendezvous.c \ src/trunnel/channelpadding_negotiation.c TRUNNELHEADERS = \ @@ -34,6 +35,7 @@ TRUNNELHEADERS = \ src/trunnel/hs/cell_common.h \ src/trunnel/hs/cell_establish_intro.h \ src/trunnel/hs/cell_introduce1.h \ + src/trunnel/hs/cell_rendezvous.h \ src/trunnel/channelpadding_negotiation.h src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES) From 2b9575a9c0ecf6e3880654be16d103326788cecf Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 24 Jul 2017 13:45:01 -0400 Subject: [PATCH 02/91] prop224: Update hs identifier circuit Remove the legacy intro point key because both service and client only uses the ed25519 key even though the intro point chosen is a legacy one. This also adds the CLIENT_PK key that is needed for the ntor handshake. Signed-off-by: David Goulet --- src/or/hs_ident.c | 3 --- src/or/hs_ident.h | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c index 5b5dc9aaff..c3f789db56 100644 --- a/src/or/hs_ident.c +++ b/src/or/hs_ident.c @@ -30,9 +30,6 @@ hs_ident_circuit_free(hs_ident_circuit_t *ident) if (ident == NULL) { return; } - if (ident->auth_key_type == HS_AUTH_KEY_TYPE_LEGACY) { - crypto_pk_free(ident->auth_rsa_pk); - } memwipe(ident, 0, sizeof(hs_ident_circuit_t)); tor_free(ident); } diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h index 8a7c3598cf..ca1fa3d707 100644 --- a/src/or/hs_ident.h +++ b/src/or/hs_ident.h @@ -52,27 +52,30 @@ typedef struct hs_ident_circuit_t { * set when an object is initialized in its constructor. */ hs_ident_circuit_type_t circuit_type; - /* (Only intro point circuit) Which type of authentication key this - * circuit identifier is using. */ - hs_auth_key_type_t auth_key_type; + /* (All circuit) Introduction point authentication key. It's also needed on + * the rendezvous circuit for the ntor handshake. */ + ed25519_public_key_t intro_auth_pk; - /* (Only intro point circuit) Introduction point authentication key. In - * legacy mode, we use an RSA key else an ed25519 public key. */ - crypto_pk_t *auth_rsa_pk; - ed25519_public_key_t auth_ed25519_pk; + /* (Only client rendezvous circuit) Introduction point encryption public + * key. We keep it in the rendezvous identifier for the ntor handshake. */ + curve25519_public_key_t intro_enc_pk; /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the * service with an INTRODUCE1 cell and used by the service in an * RENDEZVOUS1 cell. */ uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; - /* (Only rendezvous circuit) The HANDSHAKE_INFO needed in the RENDEZVOUS1 - * cell of the service. The construction is as follows: + /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the + * RENDEZVOUS1 cell of the service. The construction is as follows: * SERVER_PK [32 bytes] * AUTH_MAC [32 bytes] */ uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + /* (Only client rendezvous circuit) Client ephemeral keypair needed for the + * e2e encryption with the service. */ + curve25519_keypair_t rendezvous_client_kp; + /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for * the e2e encryption with the client on the circuit. */ uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; From b8ceab9bb33a8096f14a671399ce34db439f63e2 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Sun, 23 Jul 2017 10:43:16 -0400 Subject: [PATCH 03/91] prop224: Helper to dup a link_specifier_t object Signed-off-by: David Goulet --- src/or/hs_common.c | 18 ++++++++++++++++++ src/or/hs_common.h | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 27330bfcdb..f6adad30cc 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -541,6 +541,24 @@ hs_build_address(const ed25519_public_key_t *key, uint8_t version, tor_assert(hs_address_is_valid(addr_out)); } +/* Return a newly allocated copy of lspec. */ +link_specifier_t * +hs_link_specifier_dup(const link_specifier_t *lspec) +{ + link_specifier_t *dup = link_specifier_new(); + memcpy(dup, lspec, sizeof(*dup)); + /* The unrecognized field is a dynamic array so make sure to copy its + * content and not the pointer. */ + link_specifier_setlen_un_unrecognized( + dup, link_specifier_getlen_un_unrecognized(lspec)); + if (link_specifier_getlen_un_unrecognized(dup)) { + memcpy(link_specifier_getarray_un_unrecognized(dup), + link_specifier_getconstarray_un_unrecognized(lspec), + link_specifier_getlen_un_unrecognized(dup)); + } + return dup; +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 203a5d0818..a6c9994ef9 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -11,6 +11,9 @@ #include "or.h" +/* Trunnel */ +#include "ed25519_cert.h" + /* Protocol version 2. Use this instead of hardcoding "2" in the code base, * this adds a clearer semantic to the value when used. */ #define HS_VERSION_TWO 2 @@ -113,6 +116,8 @@ const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, uint64_t hs_get_next_time_period_num(time_t now); +link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS From 78e2bc4000a9014c1762f828e15e762c359aa20e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Jul 2017 13:42:35 -0400 Subject: [PATCH 04/91] prop224: Add the introduction point onion key to descriptor A prop224 descriptor was missing the onion key for an introduction point which is needed to extend to it by the client. Closes #22979 Signed-off-by: David Goulet --- src/or/hs_descriptor.c | 49 ++++++++++++++++++++++++++++++++++++++++++ src/or/hs_descriptor.h | 4 ++++ src/or/parsecommon.h | 1 + 3 files changed, 54 insertions(+) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 2393eac252..91d0ef544f 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -78,6 +78,7 @@ #define str_intro_auth_required "intro-auth-required" #define str_single_onion "single-onion-service" #define str_intro_point "introduction-point" +#define str_ip_onion_key "onion-key" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" #define str_ip_enc_key_cert "enc-key-cert" @@ -136,6 +137,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { /* Descriptor ruleset for the introduction points section. */ static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), + T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), @@ -479,6 +481,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip) return encoded; } +/* Encode an introduction point onion key. Return a newly allocated string + * with it. On failure, return NULL. */ +static char * +encode_onion_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "onion-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { + goto done; + } + tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); + + done: + return encoded; +} + /* Encode an introduction point object and return a newly allocated string * with it. On failure, return NULL. */ static char * @@ -498,6 +520,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(ls_str); } + /* Onion key encoding. */ + { + char *encoded_onion_key = encode_onion_key(ip); + if (encoded_onion_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_onion_key); + tor_free(encoded_onion_key); + } + /* Authentication key encoding. */ { char *encoded_cert; @@ -1662,6 +1694,23 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) goto err; } + /* "onion-key" SP ntor SP key NL */ + tok = find_by_keyword(tokens, R3_INTRO_ONION_KEY); + if (!strcmp(tok->args[0], "ntor")) { + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); + + if (curve25519_public_from_base64(&ip->onion_key, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); + goto err; + } + } else { + /* Unknown key type so we can't use that introduction point. */ + log_warn(LD_REND, "Introduction point onion key is unrecognized."); + goto err; + } + /* "auth-key" NL certificate NL */ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY); tor_assert(tok->object_body); diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 58c4089795..fdf0b90b5b 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -80,6 +80,10 @@ typedef struct hs_desc_intro_point_t { * contains hs_desc_link_specifier_t object. It MUST have at least one. */ smartlist_t *link_specifiers; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* Authentication key used to establish the introduction point circuit and * cross-certifies the blinded public key for the replica thus signed by * the blinded key and in turn signs it. */ diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index b9f1613457..a76b104fde 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -160,6 +160,7 @@ typedef enum { R3_INTRO_AUTH_REQUIRED, R3_SINGLE_ONION_SERVICE, R3_INTRODUCTION_POINT, + R3_INTRO_ONION_KEY, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, R3_INTRO_ENC_KEY_CERT, From c9927ce4d5cf46cd12d09e73e80f435b4852f26e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 20 Jul 2017 12:16:39 -0400 Subject: [PATCH 05/91] prop224: Add onion key to service descriptor intro point Signed-off-by: David Goulet --- src/or/hs_service.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 54b9e69724..ad95a20ed8 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -30,6 +30,10 @@ typedef struct hs_service_intro_point_t { /* Top level intropoint "shared" data between client/service. */ hs_intropoint_t base; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* Authentication keypair used to create the authentication certificate * which is published in the descriptor. */ ed25519_keypair_t auth_key_kp; From 44e3255c4df78828110cec360031a616c6d8d0fa Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 20 Jul 2017 11:34:32 -0400 Subject: [PATCH 06/91] hs: Implement constructor for hs_desc_intro_point_t Add a new and free function for hs_desc_intro_point_t so the service can use them to setup those objects properly. Signed-off-by: David Goulet --- src/or/hs_descriptor.c | 59 ++++++++++++++++++++--------------- src/or/hs_descriptor.h | 4 ++- src/test/hs_test_helpers.c | 3 +- src/test/test_hs_descriptor.c | 6 ++-- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 91d0ef544f..7908cd961d 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -146,29 +146,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Free a descriptor intro point object. */ -STATIC void -desc_intro_point_free(hs_desc_intro_point_t *ip) -{ - if (!ip) { - return; - } - if (ip->link_specifiers) { - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, tor_free(ls)); - smartlist_free(ip->link_specifiers); - } - tor_cert_free(ip->auth_key_cert); - tor_cert_free(ip->enc_key_cert); - if (ip->legacy.key) { - crypto_pk_free(ip->legacy.key); - } - if (ip->legacy.cert.encoded) { - tor_free(ip->legacy.cert.encoded); - } - tor_free(ip); -} - /* Free the content of the plaintext section of a descriptor. */ static void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) @@ -199,7 +176,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) } if (desc->intro_points) { SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, - desc_intro_point_free(ip)); + hs_desc_intro_point_free(ip)); smartlist_free(desc->intro_points); } memwipe(desc, 0, sizeof(*desc)); @@ -1683,11 +1660,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Ok we seem to have a well formed section containing enough tokens to * parse. Allocate our IP object and try to populate it. */ - ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t)); + ip = hs_desc_intro_point_new(); /* "introduction-point" SP link-specifiers NL */ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT); tor_assert(tok->n_args == 1); + /* Our constructor creates this list by default so free it. */ + smartlist_free(ip->link_specifiers); ip->link_specifiers = decode_link_specifiers(tok->args[0]); if (!ip->link_specifiers) { log_warn(LD_REND, "Introduction point has invalid link specifiers"); @@ -1782,7 +1761,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) goto done; err: - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; done: @@ -2401,3 +2380,31 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } +/* Return a newly allocated descriptor intro point. */ +hs_desc_intro_point_t * +hs_desc_intro_point_new(void) +{ + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + return ip; +} + +/* Free a descriptor intro point object. */ +void +hs_desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (ip == NULL) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, tor_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + tor_cert_free(ip->enc_key_cert); + crypto_pk_free(ip->legacy.key); + tor_free(ip->legacy.cert.encoded); + tor_free(ip); +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index fdf0b90b5b..80ddf8978a 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -215,6 +215,9 @@ int hs_desc_decode_encrypted(const hs_descriptor_t *desc, size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); +hs_desc_intro_point_t *hs_desc_intro_point_new(void); +void hs_desc_intro_point_free(hs_desc_intro_point_t *ip); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ @@ -233,7 +236,6 @@ STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); -STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); STATIC size_t decode_superencrypted(const char *message, size_t message_len, uint8_t **encrypted_out); #endif /* HS_DESCRIPTOR_PRIVATE */ diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index 3f0d6a9413..24d4a7e91a 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -15,8 +15,7 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, int ret; ed25519_keypair_t auth_kp; hs_desc_intro_point_t *intro_point = NULL; - hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); - ip->link_specifiers = smartlist_new(); + hs_desc_intro_point_t *ip = hs_desc_intro_point_new(); { hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls)); diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index b1abe381d4..d83f5e4c61 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -427,7 +427,7 @@ test_decode_invalid_intro_point(void *arg) const char *junk = "this is not a descriptor"; ip = decode_introduction_point(desc, junk); tt_assert(!ip); - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; } @@ -445,7 +445,7 @@ test_decode_invalid_intro_point(void *arg) tt_assert(!ip); tor_free(encoded_ip); smartlist_free(lines); - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; } @@ -545,7 +545,7 @@ test_decode_invalid_intro_point(void *arg) done: hs_descriptor_free(desc); - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); } static void From 9052530bdde9f03da883dfb70fe261ea7d0e1b4d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 6 Feb 2017 12:26:36 -0500 Subject: [PATCH 07/91] prop224: API for the creation of blinded keys Add a function for both the client and service side that is building a blinded key from a keypair (service) and from a public key (client). Those two functions uses the current time period information to build the key. Signed-off-by: David Goulet --- src/or/hs_common.c | 110 ++++++++++++++++++++++++++++++++++++- src/or/hs_common.h | 27 ++++++++- src/test/test_hs_service.c | 6 +- 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index f6adad30cc..9d42c8f105 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -20,6 +20,14 @@ #include "hs_service.h" #include "rendcommon.h" +/* Ed25519 Basepoint value. Taken from section 5 of + * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ +static const char *str_ed25519_basepoint = + "(15112221349535400772501151409588531511" + "454012693041857206046113283949847762202, " + "463168356949264781694283940034751631413" + "07993866256225615783033603165251855960)"; + /* Allocate and return a string containing the path to filename in directory. * This function will never return NULL. The caller must free this path. */ char * @@ -83,8 +91,8 @@ get_time_period_length(void) } /** Get the HS time period number at time now */ -STATIC uint64_t -get_time_period_num(time_t now) +uint64_t +hs_get_time_period_num(time_t now) { uint64_t time_period_num; uint64_t time_period_length = get_time_period_length(); @@ -105,7 +113,7 @@ get_time_period_num(time_t now) uint64_t hs_get_next_time_period_num(time_t now) { - return get_time_period_num(now) + 1; + return hs_get_time_period_num(now) + 1; } /* Create a new rend_data_t for a specific given version. @@ -360,6 +368,56 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* When creating a blinded key, we need a parameter which construction is as + * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). + * + * The nonce has a pre-defined format which uses the time period number + * period_num and the start of the period in second start_time_period. + * + * The secret of size secret_len is optional meaning that it can be NULL and + * thus will be ignored for the param construction. + * + * The result is put in param_out. */ +static void +build_blinded_key_param(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t period_num, uint64_t start_time_period, + uint8_t *param_out) +{ + size_t offset = 0; + uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; + crypto_digest_t *digest; + + tor_assert(pubkey); + tor_assert(param_out); + + /* Create the nonce N. The construction is as follow: + * N = "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ + memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); + offset += HS_KEYBLIND_NONCE_PREFIX_LEN; + set_uint64(nonce + offset, period_num); + offset += sizeof(uint64_t); + set_uint64(nonce + offset, start_time_period); + offset += sizeof(uint64_t); + tor_assert(offset == HS_KEYBLIND_NONCE_LEN); + + /* Generate the parameter h and the construction is as follow: + * h = H(pubkey | [secret] | ed25519-basepoint | nonce) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); + /* Optional secret. */ + if (secret) { + crypto_digest_add_bytes(digest, (char *) secret, secret_len); + } + crypto_digest_add_bytes(digest, str_ed25519_basepoint, + strlen(str_ed25519_basepoint)); + crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce)); + + /* Extract digest and put it in the param. */ + crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + /* Using an ed25519 public key and version to build the checksum of an * address. Put in checksum_out. Format is: * SHA3-256(".onion checksum" || PUBKEY || VERSION) @@ -559,6 +617,52 @@ hs_link_specifier_dup(const link_specifier_t *lspec) return dup; } +/* From a given ed25519 public key pk and an optional secret, compute a + * blinded public key and put it in blinded_pk_out. This is only useful to + * the client side because the client only has access to the identity public + * key of the service. */ +void +hs_build_blinded_pubkey(const ed25519_public_key_t *pk, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *blinded_pk_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(pk); + tor_assert(blinded_pk_out); + tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + + build_blinded_key_param(pk, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_public_blind(blinded_pk_out, pk, param); +} + +/* From a given ed25519 keypair kp and an optional secret, compute a blinded + * keypair for the current time period and put it in blinded_kp_out. This is + * only useful by the service side because the client doesn't have access to + * the identity secret key. */ +void +hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *blinded_kp_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(kp); + tor_assert(blinded_kp_out); + /* Extra safety. A zeroed key is bad. */ + tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + + build_blinded_key_param(&kp->pubkey, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_keypair_blind(blinded_kp_out, kp, param); +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a6c9994ef9..ae9c4e36a5 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -79,6 +79,22 @@ #define HS_SERVICE_ADDR_LEN_BASE32 \ (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) +/* The default HS time period length */ +#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ +/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ + +/* Keyblinding parameter construction is as follow: + * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ +#define HS_KEYBLIND_NONCE_PREFIX "key-blind" +#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1) +#define HS_KEYBLIND_NONCE_LEN \ + (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) + /* Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, @@ -98,6 +114,15 @@ int hs_address_is_valid(const char *address); int hs_parse_address(const char *address, ed25519_public_key_t *key_out, uint8_t *checksum_out, uint8_t *version_out); +void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *pubkey_out); +void hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *kp_out); + void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); rend_data_t *rend_data_client_create(const char *onion_address, @@ -114,6 +139,7 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); @@ -123,7 +149,6 @@ link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); #ifdef TOR_UNIT_TESTS STATIC uint64_t get_time_period_length(void); -STATIC uint64_t get_time_period_num(time_t now); #endif /* TOR_UNIT_TESTS */ diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 4a7cb81140..174d07f488 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -330,18 +330,18 @@ test_time_period(void *arg) tt_int_op(retval, ==, 0); /* Check that the time period number is right */ - tn = get_time_period_num(fake_time); + tn = hs_get_time_period_num(fake_time); tt_u64_op(tn, ==, 16903); /* Increase current time to 11:59:59 UTC and check that the time period number is still the same */ fake_time += 3599; - tn = get_time_period_num(fake_time); + tn = hs_get_time_period_num(fake_time); tt_u64_op(tn, ==, 16903); /* Now take time to 12:00:00 UTC and check that the time period rotated */ fake_time += 1; - tn = get_time_period_num(fake_time); + tn = hs_get_time_period_num(fake_time); tt_u64_op(tn, ==, 16904); /* Now also check our hs_get_next_time_period_num() function */ From 0f104ddce578c52d604931c595b28aa61a184b40 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 3 Feb 2017 15:29:31 -0500 Subject: [PATCH 08/91] prop224: Scheduled events for service Add the main loop entry point to the HS service subsystem. It is run every second and make sure that all services are in their quiescent state after that which means valid descriptors, all needed circuits opened and latest descriptors have been uploaded. For now, only v2 is supported and placeholders for v3 actions for that main loop callback. Signed-off-by: David Goulet --- src/or/hs_service.c | 105 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_service.h | 2 + src/or/main.c | 30 ++++++++++--- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 5fde42ddbb..a890389cad 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -11,6 +11,7 @@ #include "or.h" #include "circuitlist.h" #include "config.h" +#include "nodelist.h" #include "relay.h" #include "rendservice.h" #include "router.h" @@ -24,6 +25,15 @@ #include "hs/cell_establish_intro.h" #include "hs/cell_common.h" +/* Helper macro. Iterate over every service in the global map. The var is the + * name of the service pointer. */ +#define FOR_EACH_SERVICE_BEGIN(var) \ + STMT_BEGIN \ + hs_service_t **var##_iter, *var; \ + HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \ + var = *var##_iter; +#define FOR_EACH_SERVICE_END } STMT_END ; + /* Onion service directory file names. */ static const char *fname_keyfile_prefix = "hs_ed25519"; static const char *fname_hostname = "hostname"; @@ -510,6 +520,78 @@ load_service_keys(hs_service_t *service) return ret; } +/* 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; +} + +/* Scheduled event run from the main loop. Make sure all descriptors are up to + * date. Once this returns, each service descriptor needs to be considered for + * new introduction circuits and then for upload. */ +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; +} + +/* Scheduled event run from the main loop. Make sure we have all the circuits + * we need for each service. */ +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) { + return; + } + + /* Run v2 check. */ + if (num_rend_services() > 0) { + rend_consider_services_intro_points(); + } + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* XXX: Check every service for validity of circuits. */ + (void) service; + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Try to upload the descriptor for + * each service. */ +static void +run_upload_descriptor_event(time_t now) +{ + /* v2 services use the same function for descriptor creation and upload so + * we do everything here because the intro circuits were checked before. */ + if (num_rend_services() > 0) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* XXX: Upload if needed the descriptor(s). Update next upload time. */ + (void) service; + } FOR_EACH_SERVICE_END; +} + +/* ========== */ +/* Public API */ +/* ========== */ + /* Load and/or generate keys for all onion services including the client * authorization if any. Return 0 on success, -1 on failure. */ int @@ -619,6 +701,29 @@ hs_service_free(hs_service_t *service) tor_free(service); } +/* Periodic callback. Entry point from the main loop to the HS service + * subsystem. This is call every second. This is skipped if tor can't build a + * circuit or the network is disabled. */ +void +hs_service_run_scheduled_events(time_t now) +{ + /* First thing we'll do here is to make sure our services are in a + * quiescent state for the scheduled events. */ + run_housekeeping_event(now); + + /* Order matters here. We first make sure the descriptor object for each + * service contains the latest data. Once done, we check if we need to open + * new introduction circuit. Finally, we try to upload the descriptor for + * each service. */ + + /* Make sure descriptors are up to date. */ + run_build_descriptor_event(now); + /* Make sure services have enough circuits. */ + run_build_circuit_event(now); + /* Upload the descriptors if needed/possible. */ + run_upload_descriptor_event(now); +} + /* Initialize the service HS subsystem. */ void hs_service_init(void) diff --git a/src/or/hs_service.h b/src/or/hs_service.h index ad95a20ed8..493329e22f 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -223,6 +223,8 @@ void hs_service_free(hs_service_t *service); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); +void hs_service_run_scheduled_events(time_t now); + /* These functions are only used by unit tests and we need to expose them else * hs_service.o ends up with no symbols in libor.a which makes clang throw a * warning at compile time. See #21825. */ diff --git a/src/or/main.c b/src/or/main.c index dc23184961..a45e64929f 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1194,6 +1194,7 @@ CALLBACK(heartbeat); CALLBACK(clean_consdiffmgr); CALLBACK(reset_padding_counts); CALLBACK(check_canonical_channels); +CALLBACK(hs_service); #undef CALLBACK @@ -1229,6 +1230,7 @@ static periodic_event_item_t periodic_events[] = { CALLBACK(clean_consdiffmgr), CALLBACK(reset_padding_counts), CALLBACK(check_canonical_channels), + CALLBACK(hs_service), END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1461,12 +1463,6 @@ run_scheduled_events(time_t now) /* 6. And remove any marked circuits... */ circuit_close_all_marked(); - /* 7. And upload service descriptors if necessary. */ - if (have_completed_a_circuit() && !net_is_disabled()) { - rend_consider_services_upload(now); - rend_consider_descriptor_republication(); - } - /* 8. and blow away any connections that need to die. have to do this now, * because if we marked a conn for close and left its socket -1, then * we'll pass it to poll/select and bad things will happen. @@ -2101,6 +2097,28 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options) return CDM_CLEAN_CALLBACK_INTERVAL; } +/* + * Periodic callback: Run scheduled events for HS service. This is called + * every second. + */ +static int +hs_service_callback(time_t now, const or_options_t *options) +{ + (void) options; + + /* We need to at least be able to build circuits and that we actually have + * a working network. */ + if (!have_completed_a_circuit() || net_is_disabled()) { + goto end; + } + + hs_service_run_scheduled_events(now); + + end: + /* Every 1 second. */ + return 1; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; /** Number of libevent errors in the last second: we die if we get too many. */ From f53b72baf7472423f662b49bebde1a88727901fb Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 13 Feb 2017 15:32:13 +0200 Subject: [PATCH 09/91] prop224: Add descriptor overlap mode function The function has been added but not used except for the unit tests. Signed-off-by: David Goulet --- src/or/hs_common.c | 28 ++++++++++++++++++++++ src/or/hs_common.h | 2 ++ src/or/hs_service.c | 2 ++ src/test/test_hs_service.c | 49 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 9d42c8f105..631e4b7115 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -663,6 +663,34 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, ed25519_keypair_blind(blinded_kp_out, kp, param); } +/* Return true if overlap mode is active given the date in consensus. If + * consensus is NULL, then we use the latest live consensus we can find. */ +int +hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now) +{ + struct tm valid_after_tm; + + if (!consensus) { + consensus = networkstatus_get_live_consensus(now); + if (!consensus) { + return 0; + } + } + + /* XXX: Futur commits will change this to a slot system so it can be + * fine tuned better for testing networks in terms of timings. */ + + /* From the spec: "Specifically, when a hidden service fetches a consensus + * with "valid-after" between 00:00UTC and 12:00UTC, it goes into + * "descriptor overlap" mode." */ + tor_gmtime_r(&consensus->valid_after, &valid_after_tm); + if (valid_after_tm.tm_hour > 0 && valid_after_tm.tm_hour < 12) { + return 1; + } + + return 0; +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index ae9c4e36a5..bda91515b1 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -144,6 +144,8 @@ uint64_t hs_get_next_time_period_num(time_t now); link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); +int hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/or/hs_service.c b/src/or/hs_service.c index a890389cad..9114655edf 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -9,8 +9,10 @@ #define HS_SERVICE_PRIVATE #include "or.h" +#include "circpathbias.h" #include "circuitlist.h" #include "config.h" +#include "networkstatus.h" #include "nodelist.h" #include "relay.h" #include "rendservice.h" diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 174d07f488..fe4ce42336 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -553,6 +553,53 @@ test_access_service(void *arg) hs_free_all(); } +/** Test that our HS overlap period functions work properly. */ +static void +test_desc_overlap_period(void *arg) +{ + (void) arg; + int retval; + time_t now = time(NULL); + networkstatus_t *dummy_consensus = NULL; + + /* First try with a consensus inside the overlap period */ + dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + retval = parse_rfc1123_time("Wed, 13 Apr 2016 10:00:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap + period is still active. */ + dummy_consensus->valid_after += 3600; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it goes to 11:59:59 UTC. Overlap + period is still active. */ + dummy_consensus->valid_after += 3599; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it drifts to noon, and check that + overlap mode is not active anymore. */ + dummy_consensus->valid_after += 1; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + /* Check that overlap mode is also inactive at 23:59:59 UTC */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 23:59:59 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + done: + tor_free(dummy_consensus); +} + struct testcase_t hs_service_tests[] = { { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, NULL, NULL }, @@ -572,6 +619,8 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "access_service", test_access_service, TT_FORK, NULL, NULL }, + { "desc_overlap_period", test_desc_overlap_period, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From c4ba4d4cc8a1d31f78d1dc54c7851bed32a25e5c Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 11 May 2017 10:16:28 -0400 Subject: [PATCH 10/91] prop224: Implement subcredential creation Signed-off-by: David Goulet --- src/or/hs_common.c | 37 +++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 10 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 631e4b7115..102e4689fa 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -500,6 +500,43 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, tor_assert(offset == HS_SERVICE_ADDR_LEN); } +/* Using the given identity public key and a blinded public key, compute the + * subcredential and put it in subcred_out. This can't fail. */ +void +hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out) +{ + uint8_t credential[DIGEST256_LEN]; + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(subcred_out); + + /* First, build the credential. Construction is as follow: + * credential = H("credential" | public-identity-key) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX, + HS_CREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN); + crypto_digest_free(digest); + + /* Now, compute the subcredential. Construction is as follow: + * subcredential = H("subcredential" | credential | blinded-public-key). */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX, + HS_SUBCREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) credential, + sizeof(credential)); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + /* Using a base32 representation of a service address, parse its content into * the key_out, checksum_out and version_out. Any out variable can be NULL in * case the caller would want only one field. checksum_out MUST at least be 2 diff --git a/src/or/hs_common.h b/src/or/hs_common.h index bda91515b1..6abcd98319 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -95,6 +95,12 @@ #define HS_KEYBLIND_NONCE_LEN \ (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) +/* Credential and subcredential prefix value. */ +#define HS_CREDENTIAL_PREFIX "credential" +#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1) +#define HS_SUBCREDENTIAL_PREFIX "subcredential" +#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) + /* Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, @@ -139,6 +145,10 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +void hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out); + uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); From 00a02a3a59f6e44619e6caed150b001acae37231 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 3 Feb 2017 15:30:46 -0500 Subject: [PATCH 11/91] prop224: Service v3 descriptor creation and logic This commit adds the functionality for a service to build its descriptor. Also, a global call to build all descriptors for all services is added to the service scheduled events. Signed-off-by: David Goulet --- src/or/circuitlist.c | 35 ++ src/or/circuitlist.h | 1 + src/or/circuituse.c | 5 - src/or/hs_descriptor.c | 90 +++- src/or/hs_descriptor.h | 12 +- src/or/hs_intropoint.c | 14 + src/or/hs_intropoint.h | 10 +- src/or/hs_service.c | 960 ++++++++++++++++++++++++++++++++++++++--- src/or/hs_service.h | 8 +- src/or/rendservice.c | 4 +- src/or/rendservice.h | 2 +- 11 files changed, 1077 insertions(+), 64 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index d11e128787..f44343e11f 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1532,6 +1532,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start) return NULL; } +/** Return the first service rendezvous circuit originating from the global + * circuit list after start or at the start of the list if start + * is NULL. Return NULL if no circuit is found. + * + * A service rendezvous point circuit has a purpose of either + * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does + * not return a circuit marked for close and its state must be open. */ +origin_circuit_t * +circuit_get_next_service_rp_circ(origin_circuit_t *start) +{ + int idx = 0; + smartlist_t *lst = circuit_get_global_list(); + + if (start) { + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; + } + + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); + + /* Ignore a marked for close circuit or purpose not matching a service + * intro point or if the state is not open. */ + if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN || + (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND && + circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) { + continue; + } + /* The purposes we are looking for are only for origin circuits so the + * following is valid. */ + return TO_ORIGIN_CIRCUIT(circ); + } + /* Not found. */ + return NULL; +} + /** Return the first circuit originating here in global_circuitlist after * start whose purpose is purpose, and where digest (if * set) matches the private key digest of the rend data associated with the diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 2f76252563..048cd5f763 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -48,6 +48,7 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const uint8_t *digest, uint8_t purpose); origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start); origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start); origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); diff --git a/src/or/circuituse.c b/src/or/circuituse.c index a3b7066b18..af061527d6 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1280,11 +1280,6 @@ circuit_build_needed_circs(time_t now) if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) connection_ap_rescan_and_attach_pending(); - /* make sure any hidden services have enough intro points - * HS intro point streams only require an internal circuit */ - if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) - rend_consider_services_intro_points(); - circuit_expire_old_circs_as_needed(now); if (!options->DisablePredictedCircuits) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 7908cd961d..5a230759a4 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -58,6 +58,7 @@ #include "hs_descriptor.h" #include "or.h" +#include "circuitbuild.h" #include "ed25519_cert.h" /* Trunnel interface. */ #include "parsecommon.h" #include "rendcache.h" @@ -362,6 +363,14 @@ encode_link_specifiers(const smartlist_t *specs) link_specifier_set_ls_len(ls, legacy_id_len); break; } + case LS_ED25519_ID: + { + size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); + uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); + memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); + link_specifier_set_ls_len(ls, ed25519_id_len); + break; + } default: tor_assert(0); } @@ -1143,6 +1152,15 @@ decode_link_specifiers(const char *encoded) memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), sizeof(hs_spec->u.legacy_id)); break; + case LS_ED25519_ID: + /* Both are known at compile time so let's make sure they are the same + * else we can copy memory out of bound. */ + tor_assert(link_specifier_getlen_un_ed25519_id(ls) == + sizeof(hs_spec->u.ed25519_id)); + memcpy(hs_spec->u.ed25519_id, + link_specifier_getconstarray_un_ed25519_id(ls), + sizeof(hs_spec->u.ed25519_id)); + break; default: goto err; } @@ -2398,7 +2416,7 @@ hs_desc_intro_point_free(hs_desc_intro_point_t *ip) } if (ip->link_specifiers) { SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, tor_free(ls)); + ls, hs_desc_link_specifier_free(ls)); smartlist_free(ip->link_specifiers); } tor_cert_free(ip->auth_key_cert); @@ -2408,3 +2426,73 @@ hs_desc_intro_point_free(hs_desc_intro_point_t *ip) tor_free(ip); } +/* Free the given descriptor link specifier. */ +void +hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls) +{ + if (ls == NULL) { + return; + } + tor_free(ls); +} + +/* Return a newly allocated descriptor link specifier using the given extend + * info and requested type. Return NULL on error. */ +hs_desc_link_specifier_t * +hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) +{ + hs_desc_link_specifier_t *ls = NULL; + + tor_assert(info); + + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = type; + switch (ls->type) { + case LS_IPV4: + if (info->addr.family != AF_INET) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_IPV6: + if (info->addr.family != AF_INET6) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_LEGACY_ID: + memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); + break; + case LS_ED25519_ID: + memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, + sizeof(ls->u.ed25519_id)); + break; + default: + /* Unknown type is code flow error. */ + tor_assert(0); + } + + return ls; + err: + tor_free(ls); + return NULL; +} + +/* From the given descriptor, remove and free every introduction point. */ +void +hs_descriptor_free_intro_points(hs_descriptor_t *desc) +{ + smartlist_t *ips; + + tor_assert(desc); + + ips = desc->encrypted_data.intro_points; + if (ips) { + SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *, + ip, hs_desc_intro_point_free(ip)); + smartlist_clear(ips); + } +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 80ddf8978a..2f1b0160ef 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -23,6 +23,9 @@ /* The latest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 +/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours + * which is 180 minutes or 10800 seconds. */ +#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) /* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) @@ -65,12 +68,14 @@ typedef struct hs_desc_link_specifier_t { * specification. */ uint8_t type; - /* It's either an address/port or a legacy identity fingerprint. */ + /* It must be one of these types, can't be more than one. */ union { /* IP address and port of the relay use to extend. */ tor_addr_port_t ap; /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ uint8_t legacy_id[DIGEST_LEN]; + /* ed25519 identity. A 32-byte key. */ + uint8_t ed25519_id[ED25519_PUBKEY_LEN]; } u; } hs_desc_link_specifier_t; @@ -201,6 +206,11 @@ void hs_descriptor_free(hs_descriptor_t *desc); void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc); void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); +void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls); +hs_desc_link_specifier_t *hs_desc_link_specifier_new( + const extend_info_t *info, uint8_t type); +void hs_descriptor_free_intro_points(hs_descriptor_t *desc); + int hs_desc_encode_descriptor(const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, char **encoded_out); diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index 2abbfcd6c3..25c43b3ad3 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -24,6 +24,7 @@ #include "hs/cell_introduce1.h" #include "hs_circuitmap.h" +#include "hs_descriptor.h" #include "hs_intropoint.h" #include "hs_common.h" @@ -594,3 +595,16 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } +/* Free the given intropoint object ip. */ +void +hs_intro_free_content(hs_intropoint_t *ip) +{ + if (ip == NULL) { + return; + } + tor_cert_free(ip->auth_key_cert); + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, + hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); +} + diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index bfb1331ba0..2e11de1b54 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -13,11 +13,11 @@ #include "torcert.h" /* Authentication key type in an ESTABLISH_INTRO cell. */ -enum hs_intro_auth_key_type { +typedef enum { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01, HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02, -}; +} hs_intro_auth_key_type_t; /* INTRODUCE_ACK status code. */ typedef enum { @@ -30,6 +30,9 @@ typedef enum { /* Object containing introduction point common data between the service and * the client side. */ typedef struct hs_intropoint_t { + /* Does this intro point only supports legacy ID ?. */ + unsigned int is_only_legacy : 1; + /* Authentication key certificate from the descriptor. */ tor_cert_t *auth_key_cert; /* A list of link specifier. */ @@ -47,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)); /* also used by rendservice.c */ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); +hs_intropoint_t *hs_intro_new(void); +void hs_intro_free_content(hs_intropoint_t *ip); + #ifdef HS_INTROPOINT_PRIVATE #include "hs/cell_establish_intro.h" 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(ô->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 service + * 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); diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 493329e22f..476fee72ff 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -38,8 +38,12 @@ typedef struct hs_service_intro_point_t { * which is published in the descriptor. */ ed25519_keypair_t auth_key_kp; - /* Encryption private key. */ - curve25519_secret_key_t enc_key_sk; + /* Encryption keypair for the "ntor" type. */ + curve25519_keypair_t enc_key_kp; + + /* Legacy key if that intro point doesn't support v3. This should be used if + * the base object legacy flag is set. */ + crypto_pk_t *legacy_key; /* Amount of INTRODUCE2 cell accepted from this intro point. */ uint64_t introduce2_count; diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 98ed1100ec..4641e110d8 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -3983,10 +3983,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) * This is called once a second by the main loop. */ void -rend_consider_services_intro_points(void) +rend_consider_services_intro_points(time_t now) { int i; - time_t now; const or_options_t *options = get_options(); /* Are we in single onion mode? */ const int allow_direct = rend_service_allow_non_anonymous_connection( @@ -4003,7 +4002,6 @@ rend_consider_services_intro_points(void) exclude_nodes = smartlist_new(); retry_nodes = smartlist_new(); - now = time(NULL); SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) { int r; diff --git a/src/or/rendservice.h b/src/or/rendservice.h index ffed21d14e..4a06657eab 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -149,7 +149,7 @@ void rend_service_free_staging_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); -void rend_consider_services_intro_points(void); +void rend_consider_services_intro_points(time_t now); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); void rend_consider_descriptor_republication(void); From 6a21ac7f9809963287dd678c9f2c494b3f9ebba3 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 16 Feb 2017 15:55:12 -0500 Subject: [PATCH 12/91] prop224: Introduction circuit creation Signed-off-by: David Goulet --- src/or/circuitlist.c | 1 + src/or/hs_circuit.c | 99 +++++++++++++++++++++++ src/or/hs_circuit.h | 9 +++ src/or/hs_service.c | 184 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 286 insertions(+), 7 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index f44343e11f..39ebeb5f3d 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -65,6 +65,7 @@ #include "control.h" #include "entrynodes.h" #include "main.h" +#include "hs_circuit.h" #include "hs_circuitmap.h" #include "hs_common.h" #include "hs_ident.h" diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 2f595d72e5..482ba30f35 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -10,10 +10,17 @@ #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "rephist.h" +#include "router.h" #include "hs_circuit.h" #include "hs_ident.h" #include "hs_ntor.h" +#include "hs_service.h" + +/* Trunnel. */ +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" /* A circuit is about to become an e2e rendezvous circuit. Check * circ_purpose and ensure that it's properly set. Return true iff @@ -167,6 +174,98 @@ finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, } } +/* For a given circuit and a service introduction point object, register the + * intro circuit to the circuitmap. This supports legacy intro point. */ +static void +register_intro_circ(const hs_service_intro_point_t *ip, + origin_circuit_t *circ) +{ + tor_assert(ip); + tor_assert(circ); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + return; + } + hs_circuitmap_register_intro_circ_v2_service_side(circ, digest); + } else { + hs_circuitmap_register_intro_circ_v3_service_side(circ, + &ip->auth_key_kp.pubkey); + } +} + +/* From a given service and service intro point, create an introduction point + * circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_intro_circuit_identifier(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_ident_circuit_t *ident; + + tor_assert(service); + tor_assert(ip); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); + + return ident; +} + +/* For a given service and a service intro point, launch a circuit to the + * extend info ei. If the service is a single onion, a one-hop circuit will be + * requested. Return 0 if the circuit was successfully launched and tagged + * with the correct identifier. On error, a negative value is returned. */ +int +hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei, time_t now) +{ + /* Standard flags for introduction circuit. */ + int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(ei); + + /* Update circuit flags in case of a single onion service that requires a + * direct connection. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + log_info(LD_REND, "Launching a circuit to intro point %s for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + + /* Note down that we are about to use an internal circuit. */ + rep_hist_note_used_internal(now, circ_flags & CIRCLAUNCH_NEED_UPTIME, + circ_flags & CIRCLAUNCH_NEED_CAPACITY); + + /* Note down the launch for the retry period. Even if the circuit fails to + * be launched, we still want to respect the retry period to avoid stress on + * the circuit subsystem. */ + service->state.num_intro_circ_launched++; + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + ei, circ_flags); + if (circ == NULL) { + goto end; + } + + /* Setup the circuit identifier and attach it to it. */ + circ->hs_ident = create_intro_circuit_identifier(service, ip); + tor_assert(circ->hs_ident); + /* Register circuit in the global circuitmap. */ + register_intro_circ(ip, circ); + + /* Success. */ + ret = 0; + end: + return ret; +} + /* Circuit circ just finished the rend ntor key exchange. Use the key * exchange output material at ntor_key_seed and setup circ to * serve as a rendezvous end-to-end circuit between the client and the diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 71ce5c3331..8738438eb2 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -10,6 +10,15 @@ #define TOR_HS_CIRCUIT_H #include "or.h" +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_service.h" + +/* Circuit API. */ +int hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei, time_t now); /* e2e circuit API. */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 3cde2fe1b1..06ab7b9836 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -13,6 +13,7 @@ #include "circuitbuild.h" #include "circuitlist.h" #include "config.h" +#include "main.h" #include "networkstatus.h" #include "nodelist.h" #include "relay.h" @@ -21,6 +22,7 @@ #include "routerkeys.h" #include "routerlist.h" +#include "hs_circuit.h" #include "hs_common.h" #include "hs_config.h" #include "hs_circuit.h" @@ -385,6 +387,35 @@ get_node_from_intro_point(const hs_service_intro_point_t *ip) return node_get_by_id((const char *) ls->u.legacy_id); } +/* Given a service intro point, return the extend_info_t for it. This can + * return NULL if the node can't be found for the intro point or the extend + * info can't be created for the found node. If direct_conn is set, the extend + * info is validated on if we can connect directly. */ +static extend_info_t * +get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, + unsigned int direct_conn) +{ + extend_info_t *info = NULL; + const node_t *node; + + tor_assert(ip); + + node = get_node_from_intro_point(ip); + if (node == NULL) { + /* This can happen if the relay serving as intro point has been removed + * from the consensus. In that case, the intro point will be removed from + * the descriptor during the scheduled events. */ + goto end; + } + + /* In the case of a direct connection (single onion service), it is possible + * our firewall policy won't allow it so this can return a NULL value. */ + info = extend_info_from_node(node, direct_conn); + + end: + return info; +} + /* 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 * @@ -1425,14 +1456,149 @@ run_build_descriptor_event(time_t now) update_all_descriptors(now); } +/* For the given service, launch any intro point circuits that could be + * needed. This considers every descriptor of the service. */ +static void +launch_intro_point_circuits(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, try to launch any missing introduction point + * circuits using the current map. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Keep a ref on if we need a direct connection. We use this often. */ + unsigned int direct_conn = service->config.is_single_onion; + + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + extend_info_t *ei; + + /* Skip the intro point that already has an existing circuit + * (established or not). */ + if (get_intro_circuit(ip)) { + continue; + } + + ei = get_extend_info_from_intro_point(ip, direct_conn); + if (ei == NULL) { + if (!direct_conn) { + /* In case of a multi-hop connection, it should never happen that we + * can't get the extend info from the node. Avoid connection and + * remove intro point from descriptor in order to recover from this + * potential bug. */ + tor_assert_nonfatal(ei); + } + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + continue; + } + + /* Launch a circuit to the intro point. */ + ip->circuit_retries++; + if (hs_circ_launch_intro_point(service, ip, ei, now) < 0) { + log_warn(LD_REND, "Unable to launch intro circuit to node %s " + "for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + /* Intro point will be retried if possible after this. */ + } + extend_info_free(ei); + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* Don't try to build more than this many circuits before giving up for a + * while. Dynamically calculated based on the configured number of intro + * points for the given service and how many descriptor exists. The default + * use case of 3 introduction points and two descriptors will allow 28 + * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */ +static unsigned int +get_max_intro_circ_per_period(const hs_service_t *service) +{ + unsigned int count = 0; + unsigned int multiplier = 0; + unsigned int num_wanted_ip; + + tor_assert(service); + tor_assert(service->config.num_intro_points <= + HS_CONFIG_V3_MAX_INTRO_POINTS); + + num_wanted_ip = service->config.num_intro_points; + + /* The calculation is as follow. We have a number of intro points that we + * want configured as a torrc option (num_intro_points). We then add an + * extra value so we can launch multiple circuits at once and pick the + * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll + * pick 5 intros and launch 5 circuits. */ + count += (num_wanted_ip + NUM_INTRO_POINTS_EXTRA); + + /* Then we add the number of retries that is possible to do for each intro + * point. If we want 3 intros, we'll allow 3 times the number of possible + * retry. */ + count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES); + + /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we + * have none. */ + multiplier += (service->desc_current) ? 1 : 0; + multiplier += (service->desc_next) ? 1 : 0; + + return (count * multiplier); +} + +/* For the given service, return 1 if the service is allowed to launch more + * introduction circuits else 0 if the maximum has been reached for the retry + * period of INTRO_CIRC_RETRY_PERIOD. */ +static int +can_service_launch_intro_circuit(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* Consider the intro circuit retry period of the service. */ + if (now > (service->state.intro_circ_retry_started_time + + INTRO_CIRC_RETRY_PERIOD)) { + service->state.intro_circ_retry_started_time = now; + service->state.num_intro_circ_launched = 0; + goto allow; + } + /* Check if we can still launch more circuits in this period. */ + if (service->state.num_intro_circ_launched <= + get_max_intro_circ_per_period(service)) { + goto allow; + } + + /* Rate limit log that we've reached our circuit creation limit. */ + { + char *msg; + time_t elapsed_time = now - service->state.intro_circ_retry_started_time; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, now))) { + log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit " + "of %u per %d seconds. It launched %u circuits in " + "the last %ld seconds. Will retry in %ld seconds.", + safe_str_client(service->onion_address), + get_max_intro_circ_per_period(service), + INTRO_CIRC_RETRY_PERIOD, + service->state.num_intro_circ_launched, elapsed_time, + INTRO_CIRC_RETRY_PERIOD - elapsed_time); + tor_free(msg); + } + } + + /* Not allow. */ + return 0; + allow: + return 1; +} + /* Scheduled event run from the main loop. Make sure we have all the circuits * we need for each service. */ static void run_build_circuit_event(time_t 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) { + /* Make sure we can actually have enough information or able to build + * internal circuits as required by services. */ + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN || + !have_completed_a_circuit()) { return; } @@ -1443,10 +1609,14 @@ run_build_circuit_event(time_t 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 introduction circuit, we need to make sure we don't stress too much + * circuit creation so make sure this service is respecting that limit. */ + if (can_service_launch_intro_circuit(service, now)) { + /* Launch intro point circuits if needed. */ + launch_intro_point_circuits(service, now); + /* Once the circuits have opened, we'll make sure to update the + * descriptor intro point list and cleanup any extraneous. */ + } } FOR_EACH_SERVICE_END; } From d765cf30b51dfcd58756b6b3d24a14ac2c47f3e8 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 21 Feb 2017 14:20:39 -0500 Subject: [PATCH 13/91] prop224: Circuit has opened and ESTABLISH_INTRO cell Add the entry point from the circuit subsystem of "circuit has opened" which is for all type of hidden service circuits. For the introduction point, this commit actually adds the support for handling those circuits when opened and sending ESTABLISH_INTRO on a circuit. Rendevzou point circuit aren't supported yet at this commit. Signed-off-by: David Goulet --- src/or/circuituse.c | 4 +- src/or/hs_cell.c | 163 +++++++++++++++++++++++++++++++++ src/or/hs_cell.h | 19 ++++ src/or/hs_circuit.c | 164 +++++++++++++++++++++++++++++++++- src/or/hs_circuit.h | 9 ++ src/or/hs_service.c | 164 ++++++++++++++++++++++++++++++++-- src/or/hs_service.h | 4 + src/or/include.am | 4 +- src/or/rendservice.c | 11 +-- src/or/rendservice.h | 8 +- src/test/test_hs_intropoint.c | 8 +- 11 files changed, 533 insertions(+), 25 deletions(-) create mode 100644 src/or/hs_cell.c create mode 100644 src/or/hs_cell.h diff --git a/src/or/circuituse.c b/src/or/circuituse.c index af061527d6..4d450f1147 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1636,11 +1636,11 @@ circuit_has_opened(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at the service, waiting for introductions */ - rend_service_intro_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_S_CONNECT_REND: /* at the service, connecting to rend point */ - rend_service_rendezvous_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_TESTING: circuit_testing_opened(circ); diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c new file mode 100644 index 0000000000..e15f4e3e55 --- /dev/null +++ b/src/or/hs_cell.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.c + * \brief Hidden service API for cell creation and handling. + **/ + +#include "or.h" +#include "rendservice.h" + +#include "hs_cell.h" + +/* Trunnel. */ +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA + * encryption key. The encoded cell is put in cell_out that MUST at least be + * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on + * success else a negative value and cell_out is untouched. */ +static ssize_t +build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, + uint8_t *cell_out) +{ + ssize_t cell_len; + char buf[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(circ_nonce); + tor_assert(enc_key); + tor_assert(cell_out); + + cell_len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), + enc_key, circ_nonce); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + if (cell_len >= 0) { + memcpy(cell_out, buf, cell_len); + } + return cell_len; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point + * object. The encoded cell is put in cell_out that MUST at least be of the + * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else + * a negative value and cell_out is untouched. This function also supports + * legacy cell creation. */ +ssize_t +hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out) +{ + ssize_t cell_len = -1; + uint16_t sig_len = ED25519_SIG_LEN; + trn_cell_extension_t *ext; + trn_cell_establish_intro_t *cell = NULL; + + tor_assert(circ_nonce); + tor_assert(ip); + + /* Quickly handle the legacy IP. */ + if (ip->base.is_only_legacy) { + tor_assert(ip->legacy_key); + cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key, + cell_out); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + /* Success or not we are done here. */ + goto done; + } + + /* Set extension data. None used here. */ + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + cell = trn_cell_establish_intro_new(); + trn_cell_establish_intro_set_extensions(cell, ext); + /* Set signature size. Array is then allocated in the cell. We need to do + * this early so we can use trunnel API to get the signature length. */ + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); + + /* Set AUTH_KEY_TYPE: 2 means ed25519 */ + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); + + /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of + * AUTH_KEY to match */ + { + uint16_t auth_key_len = ED25519_PUBKEY_LEN; + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + /* We do this call _after_ setting the length because it's reallocated at + * that point only. */ + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); + memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len); + } + + /* Calculate HANDSHAKE_AUTH field (MAC). */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_mac_offset = + sig_len + sizeof(cell->sig_len) + + trn_cell_establish_intro_getlen_handshake_mac(cell); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr; + + /* We first encode the current fields we have in the cell so we can + * compute the MAC using the raw bytes. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + /* Sanity check. */ + tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset); + + /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */ + crypto_mac_sha3_256(mac, sizeof(mac), + (uint8_t *) circ_nonce, DIGEST_LEN, + tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); + handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); + memcpy(handshake_ptr, mac, sizeof(mac)); + } + + /* Calculate the cell signature SIG. */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len)); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr; + ed25519_signature_t sig; + + /* We first encode the current fields we have in the cell so we can + * compute the signature from the raw bytes of the cell. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + + if (ed25519_sign_prefixed(&sig, tmp_cell_enc, + tmp_cell_enc_len - tmp_cell_sig_offset, + ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) { + log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell."); + goto done; + } + /* Copy the signature into the cell. */ + sig_ptr = trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, sig_len); + } + + /* Encode the cell. Can't be bigger than a standard cell. */ + cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + done: + trn_cell_establish_intro_free(cell); + return cell_len; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h new file mode 100644 index 0000000000..9cc6109ebf --- /dev/null +++ b/src/or/hs_cell.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.h + * \brief Header file containing cell data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CELL_H +#define TOR_HS_CELL_H + +#include "hs_service.h" + +ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out); + +#endif /* TOR_HS_CELL_H */ + diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 482ba30f35..01fd864839 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -6,13 +6,16 @@ **/ #include "or.h" +#include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "relay.h" #include "rephist.h" #include "router.h" +#include "hs_cell.h" #include "hs_circuit.h" #include "hs_ident.h" #include "hs_ntor.h" @@ -195,6 +198,48 @@ register_intro_circ(const hs_service_intro_point_t *ip, } } +/* Return the number of opened introduction circuit for the given circuit that + * is matching its identity key. */ +static unsigned int +count_opened_desc_intro_point_circuits(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(service); + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + circuit_t *circ; + origin_circuit_t *ocirc; + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + continue; + } + ocirc = hs_circuitmap_get_intro_circ_v2_service_side(digest); + } else { + ocirc = + hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); + } + if (ocirc == NULL) { + continue; + } + circ = TO_CIRCUIT(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO); + /* Having a circuit not for the requested service is really bad. */ + tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk, + ô->hs_ident->identity_pk)); + /* Only count opened circuit and skip circuit that will be closed. */ + if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) { + count++; + } + } DIGEST256MAP_FOREACH_END; + return count; +} + /* From a given service and service intro point, create an introduction point * circuit identifier. This can't fail. */ static hs_ident_circuit_t * @@ -213,6 +258,60 @@ create_intro_circuit_identifier(const hs_service_t *service, return ident; } +/* For a given introduction point and an introduction circuit, send the + * ESTABLISH_INTRO cell. The service object is used for logging. This can fail + * and if so, the circuit is closed and the intro point object is flagged + * that the circuit is not established anymore which is important for the + * retry mechanism. */ +static void +send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, origin_circuit_t *circ) +{ + ssize_t cell_len; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + + /* Encode establish intro cell. */ + cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, + ip, payload); + if (cell_len < 0) { + log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " + "on circuit %u. Closing circuit.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* Send the cell on the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_INTRO, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " + "on circuit %u.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + /* On error, the circuit has been closed. */ + goto done; + } + + /* Record the attempt to use this circuit. */ + pathbias_count_use_attempt(circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* ========== */ +/* Public API */ +/* ========== */ + /* For a given service and a service intro point, launch a circuit to the * extend info ei. If the service is a single onion, a one-hop circuit will be * requested. Return 0 if the circuit was successfully launched and tagged @@ -266,6 +365,69 @@ hs_circ_launch_intro_point(hs_service_t *service, return ret; } +/* Called when a service introduction point circuit is done building. Given + * the service and intro point object, this function will send the + * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the + * circuit has been repurposed to General because we already have too many + * opened. */ +int +hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ) +{ + int ret = 0; + unsigned int num_intro_circ, num_needed_circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(desc); + tor_assert(circ); + + num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); + num_needed_circ = service->config.num_intro_points; + if (num_intro_circ > num_needed_circ) { + /* There are too many opened valid intro circuit for what the service + * needs so repurpose this one. */ + + /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just + * closes the circuit. I have NO idea why it does that so it hasn't been + * added here. I can only assume in case our ExcludeNodes list changes but + * in that case, all circuit are flagged unusable (config.c). --dgoulet */ + + log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " + "have enough for service %s. Repurposing " + "it to general and leaving internal.", + safe_str_client(service->onion_address)); + tor_assert(circ->build_state->is_internal); + /* Remove it from the circuitmap. */ + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + /* Cleaning up the hidden service identifier and repurpose. */ + hs_ident_circuit_free(circ->hs_ident); + circ->hs_ident = NULL; + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); + /* Inform that this circuit just opened for this new purpose. */ + circuit_has_opened(circ); + /* This return value indicate to the caller that the IP object should be + * removed from the service because it's corresponding circuit has just + * been repurposed. */ + ret = 1; + goto done; + } + + log_info(LD_REND, "Introduction circuit %u established for service %s.", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call + * makes sure the circuit gets closed. */ + send_establish_intro(service, ip, circ); + + done: + return ret; +} + /* Circuit circ just finished the rend ntor key exchange. Use the key * exchange output material at ntor_key_seed and setup circ to * serve as a rendezvous end-to-end circuit between the client and the @@ -273,7 +435,7 @@ hs_circ_launch_intro_point(hs_service_t *service, * and the other side is the client. * * Return 0 if the operation went well; in case of error return -1. */ -int + int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, const uint8_t *ntor_key_seed, size_t seed_len, int is_service_side) diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 8738438eb2..35eab75695 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -16,10 +16,19 @@ #include "hs_service.h" /* Circuit API. */ +int hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ); int hs_circ_launch_intro_point(hs_service_t *service, const hs_service_intro_point_t *ip, extend_info_t *ei, time_t now); +/* Cell API. */ +void hs_circ_send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, + origin_circuit_t *circ); + /* e2e circuit API. */ int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 06ab7b9836..4cd808133c 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -12,6 +12,7 @@ #include "circpathbias.h" #include "circuitbuild.h" #include "circuitlist.h" +#include "circuituse.h" #include "config.h" #include "main.h" #include "networkstatus.h" @@ -338,13 +339,72 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) 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); + digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); +} + +/* For a given service, remove the intro point from that service which will + * look in both descriptors. */ +static void +service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + tor_assert(service); + tor_assert(ip); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We'll try to remove the descriptor on both descriptors which is not + * very expensive to do instead of doing loopup + remove. */ + digest256map_remove(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey); + } FOR_EACH_DESCRIPTOR_END; +} + +/* For a given service and authentication key, return the intro point or NULL + * if not found. This will check both descriptors in the service. */ +static hs_service_intro_point_t * +service_intro_point_find(const hs_service_t *service, + const ed25519_public_key_t *auth_key) +{ + hs_service_intro_point_t *ip = NULL; + + tor_assert(service); + tor_assert(auth_key); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if ((ip = digest256map_get(desc->intro_points.map, + auth_key->pubkey)) != NULL) { + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return ip; +} + +/* For a given service and intro point, return the descriptor for which the + * intro point is assigned to. NULL is returned if not found. */ +static hs_service_descriptor_t * +service_desc_find_by_intro(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_service_descriptor_t *descp = NULL; + + tor_assert(service); + tor_assert(ip); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (digest256map_get(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey)) { + descp = desc; + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return descp; } /* From a given intro point, return the first link specifier of type @@ -790,7 +850,6 @@ service_descriptor_new(void) 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, @@ -916,8 +975,6 @@ build_desc_intro_points(const hs_service_t *service, } 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. */ @@ -1635,14 +1692,105 @@ run_upload_descriptor_event(time_t now) /* Run v3+ check. */ FOR_EACH_SERVICE_BEGIN(service) { /* XXX: Upload if needed the descriptor(s). Update next upload time. */ - (void) service; + /* XXX: Build the descriptor intro points list with + * build_desc_intro_points() once we have enough circuit opened. */ + build_desc_intro_points(service, NULL, now); } FOR_EACH_SERVICE_END; } +/* Called when the introduction point circuit is done building and ready to be + * used. */ +static void +service_intro_circ_has_opened(origin_circuit_t *circ) +{ + int close_reason; + hs_service_t *service; + hs_service_intro_point_t *ip; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(circ->cpath); + /* Getting here means this is a v3 intro circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* Get service object from the circuit identifier. */ + service = find_service(hs_service_map, &circ->hs_ident->identity_pk); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + close_reason = END_CIRC_REASON_NOSUCHSERVICE; + goto err; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + ip = service_intro_point_find(service, &circ->hs_ident->intro_auth_pk); + if (ip == NULL) { + log_warn(LD_REND, "Unknown authentication key on the introduction " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + /* Closing this circuit because we don't recognize the key. */ + close_reason = END_CIRC_REASON_NOSUCHSERVICE; + goto err; + } + /* We can't have an IP object without a descriptor. */ + desc = service_desc_find_by_intro(service, ip); + tor_assert(desc); + + if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { + /* Getting here means that the circuit has been re-purposed because we + * have enough intro circuit opened. Remove the IP from the service. */ + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + goto done; + + err: + /* Close circuit, we can't use it. */ + circuit_mark_for_close(TO_CIRCUIT(circ), close_reason); + done: + return; +} + +static void +service_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + /* XXX: Implement rendezvous support. */ +} + /* ========== */ /* Public API */ /* ========== */ +/* Called when any kind of hidden service circuit is done building thus + * opened. This is the entry point from the circuit subsystem. */ +void +hs_service_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + (circ->hs_ident) ? service_intro_circ_has_opened(circ) : + rend_service_intro_has_opened(circ); + break; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + (circ->hs_ident) ? service_rendezvous_circ_has_opened(circ) : + rend_service_rendezvous_has_opened(circ); + break; + default: + tor_assert(0); + } +} + /* Load and/or generate keys for all onion services including the client * authorization if any. Return 0 on success, -1 on failure. */ int diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 476fee72ff..96df09493e 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -103,6 +103,9 @@ typedef struct hs_service_descriptor_t { * hs_service_intropoints_t object indexed by authentication key (the RSA * key if the node is legacy). */ hs_service_intropoints_t intro_points; + + /* The time period number this descriptor has been created for. */ + uint64_t time_period_num; } hs_service_descriptor_t; /* Service key material. */ @@ -228,6 +231,7 @@ void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); void hs_service_run_scheduled_events(time_t now); +void hs_service_circuit_has_opened(origin_circuit_t *circ); /* These functions are only used by unit tests and we need to expose them else * hs_service.o ends up with no symbols in libor.a which makes clang throw a diff --git a/src/or/include.am b/src/or/include.am index 15b86ef50b..8db5be095a 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -54,6 +54,7 @@ LIBTOR_A_SOURCES = \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/hs_cache.c \ + src/or/hs_cell.c \ src/or/hs_circuit.c \ src/or/hs_circuitmap.c \ src/or/hs_client.c \ @@ -184,11 +185,12 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_cell.h \ + src/or/hs_config.h \ src/or/hs_circuit.h \ src/or/hs_circuitmap.h \ src/or/hs_client.h \ src/or/hs_common.h \ - src/or/hs_config.h \ src/or/hs_descriptor.h \ src/or/hs_ident.h \ src/or/hs_intropoint.h \ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 4641e110d8..8239803fb2 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -3116,10 +3116,11 @@ count_intro_point_circuits(const rend_service_t *service) crypto material. On success, fill cell_body_out and return the number of bytes written. On fail, return -1. */ -STATIC ssize_t -encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, char *rend_circ_nonce) +ssize_t +rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce) { int retval = -1; int r; @@ -3256,7 +3257,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /* Send the ESTABLISH_INTRO cell */ { ssize_t len; - len = encode_establish_intro_cell_legacy(buf, sizeof(buf), + len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), circuit->intro_key, circuit->cpath->prev->rend_circ_nonce); if (len < 0) { diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 4a06657eab..78f4b92c2e 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -126,10 +126,6 @@ STATIC int rend_service_verify_single_onion_poison( STATIC int rend_service_poison_new_single_onion_dir( const rend_service_t *s, const or_options_t* options); -STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, - char *rend_circ_nonce); #ifdef TOR_UNIT_TESTS STATIC void set_rend_service_list(smartlist_t *new_list); @@ -172,6 +168,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request, char **err_msg_out); int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro, char **err_msg_out); +ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce); int rend_service_validate_intro_late(const rend_intro_cell_t *intro, char **err_msg_out); void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc); diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index c6197875b5..076d125ffc 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -488,10 +488,10 @@ helper_establish_intro_v2(or_circuit_t *intro_circ) key1 = pk_generate(0); /* Use old circuit_key_material why not */ - cell_len = encode_establish_intro_cell_legacy((char*)cell_body, - sizeof(cell_body), - key1, - (char *) circuit_key_material); + cell_len = rend_service_encode_establish_intro_cell( + (char*)cell_body, + sizeof(cell_body), key1, + (char *) circuit_key_material); tt_int_op(cell_len, >, 0); /* Receive legacy establish_intro */ From 79e8d113d5ebfbc5ccf76f5db7bc0259a29520fc Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 7 Mar 2017 14:33:03 -0500 Subject: [PATCH 14/91] prop224: Handle service INTRO_ESTABLISHED cell Signed-off-by: David Goulet --- src/or/hs_cell.c | 22 ++++++++++ src/or/hs_cell.h | 3 ++ src/or/hs_circuit.c | 42 ++++++++++++++++++- src/or/hs_circuit.h | 5 +++ src/or/hs_service.c | 99 ++++++++++++++++++++++++++++++++++++++++++--- src/or/hs_service.h | 3 ++ src/or/rendcommon.c | 2 +- 7 files changed, 168 insertions(+), 8 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index e15f4e3e55..0d34ef5965 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -161,3 +161,25 @@ hs_cell_build_establish_intro(const char *circ_nonce, return cell_len; } +/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we + * are successful at parsing it, return the length of the parsed cell else a + * negative value on error. */ +ssize_t +hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) +{ + ssize_t ret; + trn_cell_intro_established_t *cell = NULL; + + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. */ + ret = trn_cell_intro_established_parse(&cell, payload, payload_len); + if (ret >= 0) { + /* On success, we do not keep the cell, we just notify the caller that it + * was successfully parsed. */ + trn_cell_intro_established_free(cell); + } + return ret; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 9cc6109ebf..8e34028896 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -15,5 +15,8 @@ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, const hs_service_intro_point_t *ip, uint8_t *cell_out); +ssize_t hs_cell_parse_intro_established(const uint8_t *payload, + size_t payload_len); + #endif /* TOR_HS_CELL_H */ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 01fd864839..51c07c0ba7 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -428,6 +428,46 @@ hs_circ_service_intro_has_opened(hs_service_t *service, return ret; } +/* Handle an INTRO_ESTABLISHED cell payload of length payload_len arriving on + * the given introduction circuit circ. The service is only used for logging + * purposes. Return 0 on success else a negative value. */ +int +hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. For a legacy node, it's an empty payload so as long as we + * have the cell, we are good. */ + if (!ip->base.is_only_legacy && + hs_cell_parse_intro_established(payload, payload_len) < 0) { + log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Switch the purpose to a fully working intro point. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the + * circuit so update our pathbias subsystem. */ + pathbias_mark_use_success(circ); + /* Success. */ + ret = 0; + + done: + return ret; +} + /* Circuit circ just finished the rend ntor key exchange. Use the key * exchange output material at ntor_key_seed and setup circ to * serve as a rendezvous end-to-end circuit between the client and the @@ -435,7 +475,7 @@ hs_circ_service_intro_has_opened(hs_service_t *service, * and the other side is the client. * * Return 0 if the operation went well; in case of error return -1. */ - int +int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, const uint8_t *ntor_key_seed, size_t seed_len, int is_service_side) diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 35eab75695..bc29781d8f 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -28,6 +28,11 @@ int hs_circ_launch_intro_point(hs_service_t *service, void hs_circ_send_establish_intro(const hs_service_t *service, hs_service_intro_point_t *ip, origin_circuit_t *circ); +int hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); /* e2e circuit API. */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 4cd808133c..5edfdd5b31 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1725,14 +1725,8 @@ service_intro_circ_has_opened(origin_circuit_t *circ) goto err; } - /* From the service object, get the intro point object of that circuit. The - * following will query both descriptors intro points list. */ ip = service_intro_point_find(service, &circ->hs_ident->intro_auth_pk); if (ip == NULL) { - log_warn(LD_REND, "Unknown authentication key on the introduction " - "circuit %u for service %s", - TO_CIRCUIT(circ)->n_circ_id, - safe_str_client(service->onion_address)); /* Closing this circuit because we don't recognize the key. */ close_reason = END_CIRC_REASON_NOSUCHSERVICE; goto err; @@ -1764,10 +1758,103 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ) /* XXX: Implement rendezvous support. */ } +/* Handle an INTRO_ESTABLISHED cell arriving on the given introduction + * circuit. Return 0 on success else a negative value. */ +static int +service_handle_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* Get service object from the circuit identifier. */ + service = find_service(hs_service_map, &circ->hs_ident->identity_pk); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + ip = service_intro_point_find(service, &circ->hs_ident->intro_auth_pk); + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_REND, "Introduction circuit established without an intro " + "point object on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. On success, the ip object is updated. */ + if (hs_circ_handle_intro_established(service, ip, circ, payload, + payload_len) < 0) { + goto err; + } + + /* Flag that we have an established circuit for this intro point. This value + * is what indicates the upload scheduled event if we are ready to build the + * intro point into the descriptor and upload. */ + ip->circuit_established = 1; + + log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " + "on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + return 0; + + err: + return -1; +} + /* ========== */ /* Public API */ /* ========== */ +/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an + * established introduction point. Return 0 on success else a negative value + * and the circuit is closed. */ +int +hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto err; + } + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + ret = (circ->hs_ident) ? service_handle_intro_established(circ, payload, + payload_len) : + rend_service_intro_established(circ, payload, + payload_len); + if (ret < 0) { + goto err; + } + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + /* Called when any kind of hidden service circuit is done building thus * opened. This is the entry point from the circuit subsystem. */ void diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 96df09493e..3de96d64e0 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -232,6 +232,9 @@ int hs_service_load_all_keys(void); void hs_service_run_scheduled_events(time_t now); void hs_service_circuit_has_opened(origin_circuit_t *circ); +int hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); /* These functions are only used by unit tests and we need to expose them else * hs_service.o ends up with no symbols in libor.a which makes clang throw a diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 986bfde75f..2cd66cb9ce 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -793,7 +793,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = rend_service_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) From faadbafba37932455ee60e02053e2e1300b63f33 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 8 Mar 2017 12:08:03 -0500 Subject: [PATCH 15/91] prop224: Add helper function to lookup HS objects Add this helper function that can lookup and return all the needed object from a circuit identifier. It is a pattern we do often so make it nicer and avoid duplicating it everywhere. Signed-off-by: David Goulet --- src/or/hs_service.c | 65 ++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 5edfdd5b31..0b60a33ed1 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -407,6 +407,39 @@ service_desc_find_by_intro(const hs_service_t *service, return descp; } +/* From a circuit identifier, get all the possible objects associated with the + * ident. If not NULL, service, ip or desc are set if the object can be found. + * They are untouched if they can't be found. + * + * This is an helper function because we do those lookups often so it's more + * convenient to simply call this functions to get all the things at once. */ +static void +get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc) +{ + hs_service_t *s; + + tor_assert(ident); + + /* Get service object from the circuit identifier. */ + s = find_service(hs_service_map, &ident->identity_pk); + if (s && service) { + *service = s; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + if (s && ip) { + *ip = service_intro_point_find(s, &ident->intro_auth_pk); + } + + /* Get the descriptor for this introduction point and service. */ + if (s && ip && *ip && desc) { + *desc = service_desc_find_by_intro(s, *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. * @@ -1703,9 +1736,8 @@ run_upload_descriptor_event(time_t now) static void service_intro_circ_has_opened(origin_circuit_t *circ) { - int close_reason; - hs_service_t *service; - hs_service_intro_point_t *ip; + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; hs_service_descriptor_t *desc = NULL; tor_assert(circ); @@ -1714,25 +1746,24 @@ service_intro_circ_has_opened(origin_circuit_t *circ) tor_assert(circ->hs_ident); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); - /* Get service object from the circuit identifier. */ - service = find_service(hs_service_map, &circ->hs_ident->identity_pk); + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { log_warn(LD_REND, "Unknown service identity key %s on the introduction " "circuit %u. Can't find onion service.", safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), TO_CIRCUIT(circ)->n_circ_id); - close_reason = END_CIRC_REASON_NOSUCHSERVICE; goto err; } - - ip = service_intro_point_find(service, &circ->hs_ident->intro_auth_pk); if (ip == NULL) { - /* Closing this circuit because we don't recognize the key. */ - close_reason = END_CIRC_REASON_NOSUCHSERVICE; + log_warn(LD_REND, "Unknown introduction point auth key on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); goto err; } /* We can't have an IP object without a descriptor. */ - desc = service_desc_find_by_intro(service, ip); tor_assert(desc); if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { @@ -1746,7 +1777,7 @@ service_intro_circ_has_opened(origin_circuit_t *circ) err: /* Close circuit, we can't use it. */ - circuit_mark_for_close(TO_CIRCUIT(circ), close_reason); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); done: return; } @@ -1772,19 +1803,17 @@ service_handle_intro_established(origin_circuit_t *circ, tor_assert(payload); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + /* We need the service and intro point for this cell. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, NULL); + /* Get service object from the circuit identifier. */ - service = find_service(hs_service_map, &circ->hs_ident->identity_pk); if (service == NULL) { log_warn(LD_REND, "Unknown service identity key %s on the introduction " - "circuit %u. Can't find onion service.", + "circuit %u. Can't find onion service.", safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), TO_CIRCUIT(circ)->n_circ_id); goto err; } - - /* From the service object, get the intro point object of that circuit. The - * following will query both descriptors intro points list. */ - ip = service_intro_point_find(service, &circ->hs_ident->intro_auth_pk); if (ip == NULL) { /* We don't recognize the key. */ log_warn(LD_REND, "Introduction circuit established without an intro " From 5e710368b3e9a19862422d4bd43f2c1d8d0ceba8 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 7 Mar 2017 14:57:14 -0500 Subject: [PATCH 16/91] prop224: Handle service INTRODUCE2 cell At this commit, launching rendezvous circuit is not implemented, only a placeholder. Signed-off-by: David Goulet --- src/or/hs_cell.c | 318 ++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_cell.h | 39 ++++++ src/or/hs_circuit.c | 66 +++++++++ src/or/hs_circuit.h | 8 ++ src/or/hs_service.c | 77 ++++++++++- src/or/hs_service.h | 3 + src/or/rendcommon.c | 2 +- 7 files changed, 511 insertions(+), 2 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 0d34ef5965..aff6ee04e9 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -7,13 +7,180 @@ **/ #include "or.h" +#include "config.h" #include "rendservice.h" #include "hs_cell.h" +#include "hs_ntor.h" /* Trunnel. */ +#include "ed25519_cert.h" #include "hs/cell_common.h" #include "hs/cell_establish_intro.h" +#include "hs/cell_introduce1.h" + +/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is + * the cell content up to the ENCRYPTED section of length encoded_cell_len. + * The encrypted param is the start of the ENCRYPTED section of length + * encrypted_len. The mac_key is the key needed for the computation of the MAC + * derived from the ntor handshake of length mac_key_len. + * + * The length mac_out_len must be at least DIGEST256_LEN. */ +static void +compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, + const uint8_t *encrypted, size_t encrypted_len, + const uint8_t *mac_key, size_t mac_key_len, + uint8_t *mac_out, size_t mac_out_len) +{ + size_t offset = 0; + size_t mac_msg_len; + uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(encoded_cell); + tor_assert(encrypted); + tor_assert(mac_key); + tor_assert(mac_out); + tor_assert(mac_out_len >= DIGEST256_LEN); + + /* Compute the size of the message which is basically the entire cell until + * the MAC field of course. */ + mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN); + tor_assert(mac_msg_len <= sizeof(mac_msg)); + + /* First, put the encoded cell in the msg. */ + memcpy(mac_msg, encoded_cell, encoded_cell_len); + offset += encoded_cell_len; + /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which + * is junk at this point). */ + memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN)); + offset += (encrypted_len - DIGEST256_LEN); + tor_assert(offset == mac_msg_len); + + crypto_mac_sha3_256(mac_out, mac_out_len, + mac_key, mac_key_len, + mac_msg, mac_msg_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. */ +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, + 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(encrypted_section); + tor_assert(client_pk); + + keys = tor_malloc_zero(sizeof(*keys)); + + /* 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) { + /* Don't rely on the caller to wipe this on error. */ + memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); + tor_free(keys); + keys = NULL; + } + return keys; +} + +/* Using the given encryption key, decrypt the encrypted_section of length + * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated + * buffer containing the decrypted data. On decryption failure, NULL is + * returned. */ +static uint8_t * +decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + uint8_t *decrypted = NULL; + crypto_cipher_t *cipher = NULL; + + tor_assert(enc_key); + tor_assert(encrypted_section); + + /* Decrypt ENCRYPTED section. */ + cipher = crypto_cipher_new_with_bits((char *) enc_key, + CURVE25519_PUBKEY_LEN * 8); + tor_assert(cipher); + + /* This is symmetric encryption so can't be bigger than the encrypted + * section length. */ + decrypted = tor_malloc_zero(encrypted_section_len); + if (crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted_section, + encrypted_section_len) < 0) { + tor_free(decrypted); + decrypted = NULL; + goto done; + } + + done: + crypto_cipher_free(cipher); + return decrypted; +} + +/* Given a pointer to the decrypted data of the ENCRYPTED section of an + * INTRODUCE2 cell of length decrypted_len, parse and validate the cell + * content. Return a newly allocated cell structure or NULL on error. The + * circuit and service object are only used for logging purposes. */ +static trn_cell_introduce_encrypted_t * +parse_introduce2_encrypted(const uint8_t *decrypted_data, + size_t decrypted_len, const origin_circuit_t *circ, + const hs_service_t *service) +{ + trn_cell_introduce_encrypted_t *enc_cell = NULL; + + tor_assert(decrypted_data); + tor_assert(circ); + tor_assert(service); + + if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data, + decrypted_len) < 0) { + log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of " + "the INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) != + HS_CELL_ONION_KEY_TYPE_NTOR) { + log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but " + "expected %u on circuit %u for service %s", + trn_cell_introduce_encrypted_get_onion_key_type(enc_cell), + HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != + CURVE25519_PUBKEY_LEN) { + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %ld but " + "expected %d on circuit %u for service %s", + trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* XXX: Validate NSPEC field as well. */ + + return enc_cell; + err: + trn_cell_introduce_encrypted_free(enc_cell); + return NULL; +} /* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA * encryption key. The encoded cell is put in cell_out that MUST at least be @@ -183,3 +350,154 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) return ret; } +/* Parsse 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 + * service and circ are only used for logging purposes. */ +ssize_t +hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service) +{ + int ret = -1; + uint8_t *decrypted = NULL; + size_t encrypted_section_len; + const uint8_t *encrypted_section; + curve25519_public_key_t client_pk; + trn_cell_introduce1_t *cell = NULL; + trn_cell_introduce_encrypted_t *enc_cell = NULL; + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + + tor_assert(data); + tor_assert(circ); + tor_assert(service); + + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, data->payload, + data->payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* XXX: Add/Test replaycache. */ + + log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " + "for service %s. Decoding encrypted section...", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + + /* Encrypted section must at least contain the CLIENT_PK and MAC which is + * defined in section 3.3.2 of the specification. */ + if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length " + "for service %s. Dropping cell.", + safe_str_client(service->onion_address)); + 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, &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, + 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 = + encrypted_section + sizeof(data->client_pk); + /* It's symmetric encryption so it's correct to use the ENCRYPTED length + * for decryption. Computes the length of ENCRYPTED_DATA meaning removing + * the CLIENT_PK and MAC length. */ + size_t encrypted_data_len = + encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN); + + /* This decrypts the ENCRYPTED_DATA section of the cell. */ + decrypted = decrypt_introduce2(intro_keys->enc_key, + encrypted_data, encrypted_data_len); + if (decrypted == NULL) { + log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an " + "INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Parse this blob into an encrypted cell structure so we can then extract + * the data we need out of it. */ + enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len, + circ, service); + memwipe(decrypted, 0, encrypted_data_len); + if (enc_cell == NULL) { + goto done; + } + } + + /* XXX: Implement client authorization checks. */ + + /* Extract onion key and rendezvous cookie from the cell used for the + * rendezvous point circuit e2e encryption. */ + memcpy(data->onion_pk.public_key, + trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN); + memcpy(data->rendezvous_cookie, + trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell), + sizeof(data->rendezvous_cookie)); + + /* Extract rendezvous link specifiers. */ + for (size_t idx = 0; + idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { + link_specifier_t *lspec = + trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); + smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + } + + /* Success. */ + ret = 0; + log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); + + done: + memwipe(&client_pk, 0, sizeof(client_pk)); + if (intro_keys) { + memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); + tor_free(intro_keys); + } + tor_free(decrypted); + trn_cell_introduce1_free(cell); + trn_cell_introduce_encrypted_free(enc_cell); + return ret; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 8e34028896..901ff81aae 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -9,14 +9,53 @@ #ifndef TOR_HS_CELL_H #define TOR_HS_CELL_H +#include "or.h" #include "hs_service.h" +/* Onion key type found in the INTRODUCE1 cell. */ +typedef enum { + HS_CELL_ONION_KEY_TYPE_NTOR = 1, +} hs_cell_onion_key_type_t; + +/* This data structure contains data that we need to parse an INTRODUCE2 cell + * which is used by the INTRODUCE2 cell parsing function. On a successful + * parsing, the onion_pk and rendezvous_cookie will be populated with the + * computed key material from the cell data. */ +typedef struct hs_cell_introduce2_data_t { + /*** Immutable Section. ***/ + + /* Introduction point authentication public key. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption keypair for the ntor handshake. */ + const curve25519_keypair_t *enc_kp; + /* Subcredentials of the service. */ + const uint8_t *subcredential; + /* Payload of the received encoded cell. */ + const uint8_t *payload; + /* Size of the payload of the received encoded cell. */ + size_t payload_len; + + /*** Muttable Section. ***/ + + /* Onion public key computed using the INTRODUCE2 encrypted section. */ + curve25519_public_key_t onion_pk; + /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + uint8_t rendezvous_cookie[REND_COOKIE_LEN]; + /* Client public key from the INTRODUCE2 encrypted section. */ + curve25519_public_key_t client_pk; + /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + smartlist_t *link_specifiers; +} hs_cell_introduce2_data_t; + ssize_t hs_cell_build_establish_intro(const char *circ_nonce, const hs_service_intro_point_t *ip, uint8_t *cell_out); ssize_t hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len); +ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service); #endif /* TOR_HS_CELL_H */ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 51c07c0ba7..a11699227c 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -312,6 +312,18 @@ send_establish_intro(const hs_service_t *service, /* Public API */ /* ========== */ +int +hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie) +{ + tor_assert(service); + tor_assert(onion_key); + tor_assert(rendezvous_cookie); + /* XXX: Implement rendezvous launch support. */ + return 0; +} + /* For a given service and a service intro point, launch a circuit to the * extend info ei. If the service is a single onion, a one-hop circuit will be * requested. Return 0 if the circuit was successfully launched and tagged @@ -468,6 +480,60 @@ hs_circ_handle_intro_established(const hs_service_t *service, return ret; } +/* Handle an INTRODUCE2 unparsed payload of payload_len for the given circuit + * and service. This cell is associated with the intro point object ip and the + * subcredential. Return 0 on success else a negative value. */ +int +hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + hs_cell_introduce2_data_t data; + + tor_assert(service); + tor_assert(circ); + tor_assert(ip); + tor_assert(subcredential); + tor_assert(payload); + + /* Populate the data structure with everything we need for the cell to be + * parsed, decrypted and key material computed correctly. */ + data.auth_pk = &ip->auth_key_kp.pubkey; + data.enc_kp = &ip->enc_key_kp; + data.subcredential = subcredential; + data.payload = payload; + data.payload_len = payload_len; + data.link_specifiers = smartlist_new(); + + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { + goto done; + } + + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid + * so increment our counter that we've seen one on this intro point. */ + ip->introduce2_count++; + + /* Launch rendezvous circuit with the onion key and rend cookie. */ + ret = hs_circ_launch_rendezvous_point(service, &data.onion_pk, + data.rendezvous_cookie); + if (ret < 0) { + goto done; + } + + /* Success. */ + ret = 0; + + done: + SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(data.link_specifiers); + memwipe(&data, 0, sizeof(data)); + return ret; +} + /* Circuit circ just finished the rend ntor key exchange. Use the key * exchange output material at ntor_key_seed and setup circ to * serve as a rendezvous end-to-end circuit between the client and the diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index bc29781d8f..1cada0b8a6 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -23,6 +23,9 @@ int hs_circ_service_intro_has_opened(hs_service_t *service, int hs_circ_launch_intro_point(hs_service_t *service, const hs_service_intro_point_t *ip, extend_info_t *ei, time_t now); +int hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie); /* Cell API. */ void hs_circ_send_establish_intro(const hs_service_t *service, @@ -33,6 +36,11 @@ int hs_circ_handle_intro_established(const hs_service_t *service, origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +int hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len); /* e2e circuit API. */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 0b60a33ed1..83b8b507f0 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -34,8 +34,8 @@ /* Trunnel */ #include "ed25519_cert.h" -#include "hs/cell_establish_intro.h" #include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" /* Helper macro. Iterate over every service in the global map. The var is the * name of the service pointer. */ @@ -1845,10 +1845,85 @@ service_handle_intro_established(origin_circuit_t *circ, return -1; } +/* Handle an INTRODUCE2 cell arriving on the given introduction circuit. + * Return 0 on success else a negative value. */ +static int +service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); + + /* We'll need every object associated with this circuit. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_BUG, "Unknown service identity key %s when handling " + "an INTRODUCE2 cell on circuit %u", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_BUG, "Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* If we have an IP object, we MUST have a descriptor object. */ + tor_assert(desc); + + /* XXX: Handle legacy IP connection. */ + + if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + payload, payload_len) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + /* ========== */ /* Public API */ /* ========== */ +/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and + * launch a circuit to the rendezvous point. */ +int +hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Do some initial validation and logging before we parse the cell */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto done; + } + + ret = (circ->hs_ident) ? service_handle_introduce2(circ, payload, + payload_len) : + rend_service_receive_introduction(circ, payload, + payload_len); + done: + return ret; +} + /* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an * established introduction point. Return 0 on success else a negative value * and the circuit is closed. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 3de96d64e0..f12094a927 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -235,6 +235,9 @@ void hs_service_circuit_has_opened(origin_circuit_t *circ); int hs_service_receive_intro_established(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +int hs_service_receive_introduce2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); /* These functions are only used by unit tests and we need to expose them else * hs_service.o ends up with no symbols in libor.a which makes clang throw a diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 2cd66cb9ce..8b555a3164 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -777,7 +777,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = rend_service_receive_introduction(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) From acc7c4ee9578e37a66dff6a09c86bee5777f782d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 8 Mar 2017 17:31:36 -0500 Subject: [PATCH 17/91] prop224: Establish rendezvous circuit for service Signed-off-by: David Goulet --- src/or/hs_cell.c | 5 +- src/or/hs_circuit.c | 276 ++++++++++++++++++++++++++++++++++++++++--- src/or/hs_common.c | 16 +++ src/or/hs_common.h | 16 +++ src/or/rendservice.c | 36 +----- src/or/rendservice.h | 1 - 6 files changed, 293 insertions(+), 57 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index aff6ee04e9..18d15fe0a6 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -363,7 +363,6 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, uint8_t *decrypted = NULL; size_t encrypted_section_len; const uint8_t *encrypted_section; - curve25519_public_key_t client_pk; trn_cell_introduce1_t *cell = NULL; trn_cell_introduce_encrypted_t *enc_cell = NULL; hs_ntor_intro_cell_keys_t *intro_keys = NULL; @@ -404,7 +403,8 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, /* 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, &client_pk); + 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", @@ -490,7 +490,6 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); done: - memwipe(&client_pk, 0, sizeof(client_pk)); if (intro_keys) { memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); tor_free(intro_keys); diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index a11699227c..7184e1e18a 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -11,6 +11,7 @@ #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "policies.h" #include "relay.h" #include "rephist.h" #include "router.h" @@ -22,6 +23,7 @@ #include "hs_service.h" /* Trunnel. */ +#include "ed25519_cert.h" #include "hs/cell_common.h" #include "hs/cell_establish_intro.h" @@ -240,6 +242,46 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, return count; } +/* From a given service, rendezvous cookie and handshake infor, create a + * rendezvous point circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys) +{ + hs_ident_circuit_t *ident; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + tor_assert(service); + tor_assert(rendezvous_cookie); + tor_assert(server_pk); + tor_assert(keys); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ + memcpy(ident->rendezvous_cookie, rendezvous_cookie, + sizeof(ident->rendezvous_cookie)); + /* Build the HANDSHAKE_INFO which looks like this: + * SERVER_PK [32 bytes] + * AUTH_INPUT_MAC [32 bytes] + */ + memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN); + memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac, + DIGEST256_LEN); + tor_assert(sizeof(ident->rendezvous_handshake_info) == + sizeof(handshake_info)); + memcpy(ident->rendezvous_handshake_info, handshake_info, + sizeof(ident->rendezvous_handshake_info)); + /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */ + tor_assert(sizeof(ident->rendezvous_ntor_key_seed) == + sizeof(keys->ntor_key_seed)); + memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed, + sizeof(ident->rendezvous_ntor_key_seed)); + return ident; +} + /* From a given service and service intro point, create an introduction point * circuit identifier. This can't fail. */ static hs_ident_circuit_t * @@ -308,22 +350,225 @@ send_establish_intro(const hs_service_t *service, memwipe(payload, 0, sizeof(payload)); } +/* From a list of link specifier, an onion key and if we are requesting a + * direct connection (ex: single onion service), return a newly allocated + * extend_info_t object. This function checks the firewall policies and if we + * are allowed to extend to the chosen address. + * + * if either IPv4 or legacy ID is missing, error. + * if not direct_conn, IPv4 is prefered. + * if direct_conn, IPv6 is prefered if we have one available. + * if firewall does not allow the chosen address, error. + * + * Return NULL if we can fulfill the conditions. */ +static extend_info_t * +get_rp_extend_info(const smartlist_t *link_specifiers, + const curve25519_public_key_t *onion_key, int direct_conn) +{ + int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0; + char legacy_id[DIGEST_LEN] = {0}; + uint16_t port_v4 = 0, port_v6 = 0, port = 0; + tor_addr_t addr_v4, addr_v6, *addr = NULL; + ed25519_public_key_t ed25519_pk; + extend_info_t *info = NULL; + + tor_assert(link_specifiers); + tor_assert(onion_key); + + SMARTLIST_FOREACH_BEGIN(link_specifiers, const link_specifier_t *, ls) { + switch (link_specifier_get_ls_type(ls)) { + case LS_IPV4: + /* Skip if we already seen a v4. */ + if (have_v4) continue; + tor_addr_from_ipv4h(&addr_v4, + link_specifier_get_un_ipv4_addr(ls)); + port_v4 = link_specifier_get_un_ipv4_port(ls); + have_v4 = 1; + break; + case LS_IPV6: + /* Skip if we already seen a v6. */ + if (have_v6) continue; + tor_addr_from_ipv6_bytes(&addr_v6, + (const char *) link_specifier_getconstarray_un_ipv6_addr(ls)); + port_v6 = link_specifier_get_un_ipv6_port(ls); + have_v6 = 1; + break; + case LS_LEGACY_ID: + /* Make sure we do have enough bytes for the legacy ID. */ + if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { + break; + } + memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls), + sizeof(legacy_id)); + have_legacy_id = 1; + break; + case LS_ED25519_ID: + memcpy(ed25519_pk.pubkey, + link_specifier_getconstarray_un_ed25519_id(ls), + ED25519_PUBKEY_LEN); + have_ed25519_id = 1; + break; + default: + /* Ignore unknown. */ + break; + } + } SMARTLIST_FOREACH_END(ls); + + /* IPv4, legacy ID and ed25519 are mandatory. */ + if (!have_v4 || !have_legacy_id || !have_ed25519_id) { + goto done; + } + /* By default, we pick IPv4 but this might change to v6 if certain + * conditions are met. */ + addr = &addr_v4; port = port_v4; + + /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop + * circuit so we can't extend in IPv6. And at this point, we do have an IPv4 + * address available so go to validation. */ + if (!direct_conn) { + goto validate; + } + + /* From this point on, we have a request for a direct connection to the + * rendezvous point so make sure we can actually connect through our + * firewall. We'll prefer IPv6. */ + + /* IPv6 test. */ + if (have_v6 && + fascist_firewall_allows_address_addr(&addr_v6, port_v6, + FIREWALL_OR_CONNECTION, 1, 1)) { + /* Direct connection and we can reach it in IPv6 so go for it. */ + addr = &addr_v6; port = port_v6; + goto validate; + } + /* IPv4 test and we are sure we have a v4 because of the check above. */ + if (fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, 0, 0)) { + /* Direct connection and we can reach it in IPv4 so go for it. */ + addr = &addr_v4; port = port_v4; + goto validate; + } + + validate: + /* We'll validate now that the address we've picked isn't a private one. If + * it is, are we allowing to extend to private address? */ + if (!extend_info_addr_is_allowed(addr)) { + log_warn(LD_REND, "Rendezvous point address is private and it is not " + "allowed to extend to it: %s:%u", + fmt_addr(&addr_v4), port_v4); + goto done; + } + + /* We do have everything for which we think we can connect successfully. */ + info = extend_info_new(NULL, legacy_id, &ed25519_pk, NULL, onion_key, + addr, port); + done: + return info; +} + +/* For a given service, the ntor onion key and a rendezvous cookie, launch a + * circuit to the rendezvous point specified by the link specifiers. On + * success, a circuit identifier is attached to the circuit with the needed + * data. This function will try to open a circuit for a maximum value of + * MAX_REND_FAILURES then it will give up. */ +static void +launch_rendezvous_point_circuit(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data) +{ + int circ_needs_uptime; + time_t now = time(NULL); + extend_info_t *info = NULL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); + /* Help predict this next time */ + rep_hist_note_used_internal(now, circ_needs_uptime, 1); + + /* Get the extend info data structure for the chosen rendezvous point + * specified by the given link specifiers. */ + info = get_rp_extend_info(data->link_specifiers, &data->onion_pk, + service->config.is_single_onion); + if (info == NULL) { + /* We are done here, we can't extend to the rendezvous point. */ + goto end; + } + + for (int i = 0; i < MAX_REND_FAILURES; i++) { + int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; + if (circ_needs_uptime) { + circ_flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* Firewall and policies are checked when getting the extend info. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, + circ_flags); + if (circ != NULL) { + /* Stop retrying, we have a circuit! */ + break; + } + } + if (circ == NULL) { + log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s " + "for service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(service->onion_address)); + goto end; + } + log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " + "for service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(hex_str((const char *) data->rendezvous_cookie, + REND_COOKIE_LEN)), + safe_str_client(service->onion_address)); + tor_assert(circ->build_state); + /* Rendezvous circuit have a specific timeout for the time spent on trying + * to connect to the rendezvous point. */ + circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; + + /* Create circuit identifier and key material. */ + { + hs_ntor_rend_cell_keys_t keys; + curve25519_keypair_t ephemeral_kp; + /* No need for extra strong, this is only for this circuit life time. This + * key will be used for the RENDEZVOUS1 cell that will be sent on the + * circuit once opened. */ + curve25519_keypair_generate(&ephemeral_kp, 0); + if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, + &ip->enc_key_kp, + &ephemeral_kp, &data->client_pk, + &keys) < 0) { + /* This should not really happened but just in case, don't make tor + * freak out, close the circuit and move on. */ + log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " + "service %s", + safe_str_client(service->onion_address)); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + circ->hs_ident = create_rp_circuit_identifier(service, + data->rendezvous_cookie, + &ephemeral_kp.pubkey, &keys); + memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); + memwipe(&keys, 0, sizeof(keys)); + tor_assert(circ->hs_ident); + } + + end: + extend_info_free(info); +} + /* ========== */ /* Public API */ /* ========== */ -int -hs_circ_launch_rendezvous_point(const hs_service_t *service, - const curve25519_public_key_t *onion_key, - const uint8_t *rendezvous_cookie) -{ - tor_assert(service); - tor_assert(onion_key); - tor_assert(rendezvous_cookie); - /* XXX: Implement rendezvous launch support. */ - return 0; -} - /* For a given service and a service intro point, launch a circuit to the * extend info ei. If the service is a single onion, a one-hop circuit will be * requested. Return 0 if the circuit was successfully launched and tagged @@ -517,12 +762,7 @@ hs_circ_handle_introduce2(const hs_service_t *service, ip->introduce2_count++; /* Launch rendezvous circuit with the onion key and rend cookie. */ - ret = hs_circ_launch_rendezvous_point(service, &data.onion_pk, - data.rendezvous_cookie); - if (ret < 0) { - goto done; - } - + launch_rendezvous_point_circuit(service, ip, &data); /* Success. */ ret = 0; diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 102e4689fa..571f4c5178 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -19,6 +19,7 @@ #include "hs_common.h" #include "hs_service.h" #include "rendcommon.h" +#include "rendservice.h" /* Ed25519 Basepoint value. Taken from section 5 of * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ @@ -724,7 +725,22 @@ hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now) if (valid_after_tm.tm_hour > 0 && valid_after_tm.tm_hour < 12) { return 1; } + return 0; +} +/* Return 1 if any virtual port in ports needs a circuit with good uptime. + * Else return 0. */ +int +hs_service_requires_uptime_circ(const smartlist_t *ports) +{ + tor_assert(ports); + + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, + p->virtual_port)) { + return 1; + } + } SMARTLIST_FOREACH_END(p); return 0; } diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 6abcd98319..f9e3f297a9 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -107,6 +107,21 @@ typedef enum { HS_AUTH_KEY_TYPE_ED25519 = 2, } hs_auth_key_type_t; +/* Represents the mapping from a virtual port of a rendezvous service to a + * real port on some IP. */ +typedef struct rend_service_port_config_t { + /* The incoming HS virtual port we're mapping */ + uint16_t virtual_port; + /* Is this an AF_UNIX port? */ + unsigned int is_unix_addr:1; + /* The outgoing TCP port to use, if !is_unix_addr */ + uint16_t real_port; + /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ + tor_addr_t real_addr; + /* The socket path to connect to, if is_unix_addr */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; +} rend_service_port_config_t; + void hs_init(void); void hs_free_all(void); @@ -128,6 +143,7 @@ void hs_build_blinded_keypair(const ed25519_keypair_t *kp, const uint8_t *secret, size_t secret_len, uint64_t time_period_num, ed25519_keypair_t *kp_out); +int hs_service_requires_uptime_circ(const smartlist_t *ports); void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 8239803fb2..7353a4f99d 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -83,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable( smartlist_t* substitute_service_list); static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted); -/** Represents the mapping from a virtual port of a rendezvous service to - * a real port on some IP. - */ -struct rend_service_port_config_s { - /* The incoming HS virtual port we're mapping */ - uint16_t virtual_port; - /* Is this an AF_UNIX port? */ - unsigned int is_unix_addr:1; - /* The outgoing TCP port to use, if !is_unix_addr */ - uint16_t real_port; - /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ - tor_addr_t real_addr; - /* The socket path to connect to, if is_unix_addr */ - char unix_addr[FLEXIBLE_ARRAY_MEMBER]; -}; - /* Hidden service directory file names: * new file names should be added to rend_service_add_filenames_to_list() * for sandboxing purposes. */ @@ -1694,24 +1678,6 @@ rend_service_get_by_service_id(const char *id) return NULL; } -/** Return 1 if any virtual port in service wants a circuit - * to have good uptime. Else return 0. - */ -static int -rend_service_requires_uptime(rend_service_t *service) -{ - int i; - rend_service_port_config_t *p; - - for (i=0; i < smartlist_len(service->ports); ++i) { - p = smartlist_get(service->ports, i); - if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, - p->virtual_port)) - return 1; - } - return 0; -} - /** Check client authorization of a given descriptor_cookie of * length cookie_len for service. Return 1 for success * and 0 for failure. */ @@ -2029,7 +1995,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, goto err; } - circ_needs_uptime = rend_service_requires_uptime(service); + circ_needs_uptime = hs_service_requires_uptime_circ(service->ports); /* help predict this next time */ rep_hist_note_used_internal(now, circ_needs_uptime, 1); diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 78f4b92c2e..a6d6ec6a46 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -16,7 +16,6 @@ #include "hs_service.h" typedef struct rend_intro_cell_s rend_intro_cell_t; -typedef struct rend_service_port_config_s rend_service_port_config_t; #ifdef RENDSERVICE_PRIVATE From dfa6301aed8f1c7164b1e8513ab64119944d976c Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 9 Mar 2017 12:54:51 -0500 Subject: [PATCH 18/91] prop224: Handle service RENDEZVOUS1 cell Signed-off-by: David Goulet --- src/or/hs_cell.c | 35 ++++++++++++++++++++++++++++ src/or/hs_cell.h | 7 ++++++ src/or/hs_circuit.c | 57 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_circuit.h | 5 ++-- src/or/hs_service.c | 35 +++++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 4 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 18d15fe0a6..68c201b890 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -18,6 +18,7 @@ #include "hs/cell_common.h" #include "hs/cell_establish_intro.h" #include "hs/cell_introduce1.h" +#include "hs/cell_rendezvous.h" /* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is * the cell content up to the ENCRYPTED section of length encoded_cell_len. @@ -500,3 +501,37 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, return ret; } +/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake + * info. The encoded cell is put in cell_out and the length of the data is + * returned. This can't fail. */ +ssize_t +hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_rendezvous1_t *cell; + + tor_assert(rendezvous_cookie); + tor_assert(rendezvous_handshake_info); + tor_assert(cell_out); + + cell = trn_cell_rendezvous1_new(); + /* Set the RENDEZVOUS_COOKIE. */ + memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell), + rendezvous_cookie, rendezvous_cookie_len); + /* Set the HANDSHAKE_INFO. */ + trn_cell_rendezvous1_setlen_handshake_info(cell, + rendezvous_handshake_info_len); + memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell), + rendezvous_handshake_info, rendezvous_handshake_info_len); + /* Encoding. */ + cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + tor_assert(cell_len > 0); + + trn_cell_rendezvous1_free(cell); + return cell_len; +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 901ff81aae..fb4950d519 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -47,10 +47,17 @@ typedef struct hs_cell_introduce2_data_t { smartlist_t *link_specifiers; } hs_cell_introduce2_data_t; +/* Build cell API. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, const hs_service_intro_point_t *ip, uint8_t *cell_out); +ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out); +/* Parse cell API. */ ssize_t hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len); ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 7184e1e18a..22a2c33479 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -685,6 +685,63 @@ hs_circ_service_intro_has_opened(hs_service_t *service, return ret; } +/* Called when a service rendezvous point circuit is done building. Given the + * service and the circuit, this function will send a RENDEZVOUS1 cell on the + * circuit using the information in the circuit identifier. If the cell can't + * be sent, the circuit is closed. */ +void +hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ) +{ + size_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(service); + tor_assert(circ); + tor_assert(circ->hs_ident); + + /* Some useful logging. */ + log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN), + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* This can't fail. */ + payload_len = hs_cell_build_rendezvous1( + circ->hs_ident->rendezvous_cookie, + sizeof(circ->hs_ident->rendezvous_cookie), + circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info), + payload); + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_RENDEZVOUS1, + (const char *) payload, payload_len, + circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Setup end-to-end rendezvous circuit between the client and us. */ + if (hs_circuit_setup_e2e_rend_circ(circ, + circ->hs_ident->rendezvous_ntor_key_seed, + sizeof(circ->hs_ident->rendezvous_ntor_key_seed), + 1) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); + goto done; + } + + done: + memwipe(payload, 0, sizeof(payload)); +} + /* Handle an INTRO_ESTABLISHED cell payload of length payload_len arriving on * the given introduction circuit circ. The service is only used for logging * purposes. Return 0 on success else a negative value. */ diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 1cada0b8a6..ca8f1b2f6a 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -20,6 +20,8 @@ int hs_circ_service_intro_has_opened(hs_service_t *service, hs_service_intro_point_t *ip, const hs_service_descriptor_t *desc, origin_circuit_t *circ); +void hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ); int hs_circ_launch_intro_point(hs_service_t *service, const hs_service_intro_point_t *ip, extend_info_t *ei, time_t now); @@ -28,9 +30,6 @@ int hs_circ_launch_rendezvous_point(const hs_service_t *service, const uint8_t *rendezvous_cookie); /* Cell API. */ -void hs_circ_send_establish_intro(const hs_service_t *service, - hs_service_intro_point_t *ip, - origin_circuit_t *circ); int hs_circ_handle_intro_established(const hs_service_t *service, const hs_service_intro_point_t *ip, origin_circuit_t *circ, diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 83b8b507f0..a2ab0629a8 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1782,11 +1782,44 @@ service_intro_circ_has_opened(origin_circuit_t *circ) return; } +/* Called when a rendezvous circuit is done building and ready to be used. */ static void service_rendezvous_circ_has_opened(origin_circuit_t *circ) { + hs_service_t *service = NULL; + tor_assert(circ); - /* XXX: Implement rendezvous support. */ + tor_assert(circ->cpath); + /* Getting here means this is a v3 intro circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Declare the circuit dirty to avoid reuse, and for path-bias */ + if (!TO_CIRCUIT(circ)->timestamp_dirty) + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the rendezvous " + "circuit %u with cookie %s. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN)); + goto err; + } + + /* If the cell can't be sent, the circuit will be closed within this + * function. */ + hs_circ_service_rp_has_opened(service, circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; } /* Handle an INTRO_ESTABLISHED cell arriving on the given introduction From 100386e659533cfa92c5bfff93a15fb3535f7970 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 5 Apr 2017 12:26:02 -0400 Subject: [PATCH 19/91] prop224: Support legacy INTRODUCE2 cell Also rename some function to follow a bit more the naming convention in that file. Signed-off-by: David Goulet --- src/or/hs_cell.c | 119 +++++++++++++++++++++++++++++++++++++++---- src/or/hs_cell.h | 2 + src/or/hs_circuit.c | 2 + src/or/hs_service.c | 4 +- src/or/rendservice.h | 4 +- 5 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 68c201b890..9ab83525d4 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -207,6 +207,105 @@ build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, return cell_len; } +/* Free the given cell pointer. If is_legacy_cell is set, cell_ptr is cast to + * a rend_intro_cell_t else to a trn_cell_introduce1_t. */ +static void +introduce2_free_cell(void *cell_ptr, unsigned int is_legacy_cell) +{ + if (cell_ptr == NULL) { + return; + } + if (is_legacy_cell) { + rend_intro_cell_t *legacy_cell = cell_ptr; + rend_service_free_intro(legacy_cell); + } else { + trn_cell_introduce1_free((trn_cell_introduce1_t *) cell_ptr); + } +} + +/* Return the length of the encrypted section of the cell_ptr. If + * is_legacy_cell is set, cell_ptr is cast to a rend_intro_cell_t else to a + * trn_cell_introduce1_t. */ +static size_t +get_introduce2_encrypted_section_len(const void *cell_ptr, + unsigned int is_legacy_cell) +{ + tor_assert(cell_ptr); + if (is_legacy_cell) { + return ((const rend_intro_cell_t *) cell_ptr)->ciphertext_len; + } + return trn_cell_introduce1_getlen_encrypted( + (const trn_cell_introduce1_t *) cell_ptr); +} + +/* Return the encrypted section pointer from the the cell_ptr. If + * is_legacy_cell is set, cell_ptr is cast to a rend_intro_cell_t else to a + * trn_cell_introduce1_t. */ +static const uint8_t * +get_introduce2_encrypted_section(const void *cell_ptr, + unsigned int is_legacy_cell) +{ + tor_assert(cell_ptr); + if (is_legacy_cell) { + return ((const rend_intro_cell_t *) cell_ptr)->ciphertext; + } + return trn_cell_introduce1_getconstarray_encrypted( + (const trn_cell_introduce1_t *) cell_ptr); +} + +/* Parse an INTRODUCE2 cell from payload of size payload_len for the given + * service and circuit which are used only for logging purposes. The resulting + * parsed cell is put in cell_ptr_out. If is_legacy_cell is set, the type of + * the returned cell is rend_intro_cell_t else trn_cell_introduce1_t. + * + * Return 0 on success else a negative value and cell_ptr_out is untouched. */ +static int +parse_introduce2_cell(const hs_service_t *service, + const origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len, unsigned int is_legacy_cell, + void **cell_ptr_out) +{ + tor_assert(service); + tor_assert(circ); + tor_assert(payload); + tor_assert(cell_ptr_out); + + /* We parse the cell differently for legacy. */ + if (is_legacy_cell) { + char *err_msg; + rend_intro_cell_t *legacy_cell = NULL; + + legacy_cell = rend_service_begin_parse_intro(payload, payload_len, 2, + &err_msg); + if (legacy_cell == NULL) { + log_info(LD_REND, "Unable to parse legacy INTRODUCE2 cell on " + "circuit %u for service %s: %s", + TO_CIRCUIT(circ)->n_circ_id, err_msg, + safe_str_client(service->onion_address)); + tor_free(err_msg); + goto err; + } + *cell_ptr_out = legacy_cell; + } else { + trn_cell_introduce1_t *cell = NULL; + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + *cell_ptr_out = cell; + } + + /* On success, we must have set the cell pointer. */ + tor_assert(*cell_ptr_out); + return 0; + err: + return -1; +} + /* ========== */ /* Public API */ /* ========== */ @@ -364,21 +463,17 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, uint8_t *decrypted = NULL; size_t encrypted_section_len; const uint8_t *encrypted_section; - trn_cell_introduce1_t *cell = NULL; trn_cell_introduce_encrypted_t *enc_cell = NULL; hs_ntor_intro_cell_keys_t *intro_keys = NULL; + void *cell_ptr = NULL; tor_assert(data); tor_assert(circ); tor_assert(service); - /* Parse the cell so we can start cell validation. */ - if (trn_cell_introduce1_parse(&cell, data->payload, - data->payload_len) < 0) { - log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " - "for service %s", - TO_CIRCUIT(circ)->n_circ_id, - safe_str_client(service->onion_address)); + /* Parse the cell into a decoded data structure pointed by cell_ptr. */ + if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, + data->is_legacy, &cell_ptr) < 0) { goto done; } @@ -389,8 +484,10 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); - encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); - encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + encrypted_section = + get_introduce2_encrypted_section(cell_ptr, data->is_legacy); + encrypted_section_len = + get_introduce2_encrypted_section_len(cell_ptr, data->is_legacy); /* Encrypted section must at least contain the CLIENT_PK and MAC which is * defined in section 3.3.2 of the specification. */ @@ -496,8 +593,8 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, tor_free(intro_keys); } tor_free(decrypted); - trn_cell_introduce1_free(cell); trn_cell_introduce_encrypted_free(enc_cell); + introduce2_free_cell(cell_ptr, data->is_legacy); return ret; } diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index fb4950d519..6114182439 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -34,6 +34,8 @@ typedef struct hs_cell_introduce2_data_t { const uint8_t *payload; /* Size of the payload of the received encoded cell. */ size_t payload_len; + /* Is this a legacy introduction point? */ + unsigned int is_legacy : 1; /*** Muttable Section. ***/ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 22a2c33479..543cbac6d3 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -13,6 +13,7 @@ #include "config.h" #include "policies.h" #include "relay.h" +#include "rendservice.h" #include "rephist.h" #include "router.h" @@ -809,6 +810,7 @@ hs_circ_handle_introduce2(const hs_service_t *service, data.payload = payload; data.payload_len = payload_len; data.link_specifiers = smartlist_new(); + data.is_legacy = ip->base.is_only_legacy; if (hs_cell_parse_introduce2(&data, circ, service) < 0) { goto done; diff --git a/src/or/hs_service.c b/src/or/hs_service.c index a2ab0629a8..48724e45cf 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1914,8 +1914,8 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, /* If we have an IP object, we MUST have a descriptor object. */ tor_assert(desc); - /* XXX: Handle legacy IP connection. */ - + /* The following will parse, decode and launch the rendezvous point circuit. + * Both current and legacy cells are handled. */ if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, payload, payload_len) < 0) { goto err; diff --git a/src/or/rendservice.h b/src/or/rendservice.h index a6d6ec6a46..819dfc176e 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -17,8 +17,6 @@ typedef struct rend_intro_cell_s rend_intro_cell_t; -#ifdef RENDSERVICE_PRIVATE - /* This can be used for both INTRODUCE1 and INTRODUCE2 */ struct rend_intro_cell_s { @@ -63,6 +61,8 @@ struct rend_intro_cell_s { uint8_t dh[DH_KEY_LEN]; }; +#ifdef RENDSERVICE_PRIVATE + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ From 27dd1a716c63bcdda31f24ed08d259b4a91aa1c3 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 6 Apr 2017 14:37:24 -0400 Subject: [PATCH 20/91] prop224: Support INTRODUCE2 cell replay cache Signed-off-by: David Goulet --- src/or/hs_cell.c | 13 +++++++++++-- src/or/hs_cell.h | 2 ++ src/or/hs_circuit.c | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 9ab83525d4..4c476b1388 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -9,6 +9,7 @@ #include "or.h" #include "config.h" #include "rendservice.h" +#include "replaycache.h" #include "hs_cell.h" #include "hs_ntor.h" @@ -460,6 +461,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, const hs_service_t *service) { int ret = -1; + time_t elapsed; uint8_t *decrypted = NULL; size_t encrypted_section_len; const uint8_t *encrypted_section; @@ -477,8 +479,6 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, goto done; } - /* XXX: Add/Test replaycache. */ - log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " "for service %s. Decoding encrypted section...", TO_CIRCUIT(circ)->n_circ_id, @@ -498,6 +498,15 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, goto done; } + /* 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" + "same ENCRYPTED section was seen %ld seconds ago. " + "Dropping cell.", 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, diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 6114182439..1336a399a7 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -47,6 +47,8 @@ typedef struct hs_cell_introduce2_data_t { curve25519_public_key_t client_pk; /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ smartlist_t *link_specifiers; + /* Replay cache of the introduction point. */ + replaycache_t *replay_cache; } hs_cell_introduce2_data_t; /* Build cell API. */ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 543cbac6d3..ee43406c0d 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -811,6 +811,7 @@ hs_circ_handle_introduce2(const hs_service_t *service, data.payload_len = payload_len; data.link_specifiers = smartlist_new(); data.is_legacy = ip->base.is_only_legacy; + data.replay_cache = ip->replay_cache; if (hs_cell_parse_introduce2(&data, circ, service) < 0) { goto done; From 77b279c35c5ecf83c045f9c1d613544d958aef81 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 6 Apr 2017 14:58:13 -0400 Subject: [PATCH 21/91] prop224: Add service replay cache Signed-off-by: David Goulet --- src/or/hs_circuit.c | 17 +++++++++++++++++ src/or/hs_service.c | 8 ++++++++ src/or/hs_service.h | 7 +++++++ 3 files changed, 32 insertions(+) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index ee43406c0d..d9e96c6330 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -794,6 +794,7 @@ hs_circ_handle_introduce2(const hs_service_t *service, const uint8_t *payload, size_t payload_len) { int ret = -1; + time_t elapsed; hs_cell_introduce2_data_t data; tor_assert(service); @@ -817,6 +818,22 @@ hs_circ_handle_introduce2(const hs_service_t *service, goto done; } + /* Check whether we've seen this REND_COOKIE before to detect repeats. */ + if (replaycache_add_test_and_elapsed( + service->state.replay_cache_rend_cookie, + data.rendezvous_cookie, sizeof(data.rendezvous_cookie), + &elapsed)) { + /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE + * as its previous one if its intro circ times out while in state + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first + * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2 + * cell), we are already trying to connect to that rend point (and may + * have already succeeded); drop this cell. */ + log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE " + "field %ld seconds ago. Dropping cell.", elapsed); + goto done; + } + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid * so increment our counter that we've seen one on this intro point. */ ip->introduce2_count++; diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 48724e45cf..567ca0be03 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2080,6 +2080,9 @@ hs_service_new(const or_options_t *options) set_service_default_config(&service->config, options); /* Set the default service version. */ service->config.version = HS_SERVICE_DEFAULT_VERSION; + /* Allocate the CLIENT_PK replay cache in service state. */ + service->state.replay_cache_rend_cookie = + replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); return service; } @@ -2101,6 +2104,11 @@ hs_service_free(hs_service_t *service) /* Free service configuration. */ service_clear_config(&service->config); + /* Free replay cache from state. */ + if (service->state.replay_cache_rend_cookie) { + replaycache_free(service->state.replay_cache_rend_cookie); + } + /* Wipe service keys. */ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); diff --git a/src/or/hs_service.h b/src/or/hs_service.h index f12094a927..8776a4412c 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -181,6 +181,13 @@ typedef struct hs_service_state_t { /* Indicate that the service has entered the overlap period. We use this * flag to check for descriptor rotation. */ unsigned int in_overlap_period : 1; + + /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point + * through two or more different introduction points; when they do, this + * keeps us from launching multiple simultaneous attempts to connect to the + * same rend point. */ + replaycache_t *replay_cache_rend_cookie; } hs_service_state_t; /* Representation of a service running on this tor instance. */ From 267bc7bc3b574f3e60d7836fde5a24652e3ac9c2 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 18 Apr 2017 15:06:44 -0400 Subject: [PATCH 22/91] prop224: Build hsdir index for node_t This hsdir index value is used to give an index value to all node_t (relays) that supports HSDir v3. An index value is then computed using the blinded key to know where to fetch/upload the service descriptor from/to. To avoid computing that index value everytime the client/service needs it, we do that everytime we get a new consensus which then doesn't change until the next one. The downside is that we need to sort them once we need to compute the set of responsible HSDir. Finally, the "hs_index" function is also added but not used. It will be used in later commits to compute which node_t is a responsible HSDir for the service we want to fetch/upload the descriptor. Signed-off-by: David Goulet --- src/or/hs_common.c | 113 +++++++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 31 +++++++++++ src/or/nodelist.c | 78 ++++++++++++++++++++++++++++ src/or/or.h | 6 +++ src/or/shared_random.c | 24 +++++++++ src/or/shared_random.h | 3 ++ 6 files changed, 255 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 571f4c5178..0e3562de87 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -20,6 +20,7 @@ #include "hs_service.h" #include "rendcommon.h" #include "rendservice.h" +#include "shared_random.h" /* Ed25519 Basepoint value. Taken from section 5 of * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ @@ -369,6 +370,25 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* Using the given time period number, compute the disaster shared random + * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ +static void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + crypto_digest_t *digest; + + tor_assert(srv_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + /* Setup payload: H("shared-random-disaster" | INT_8(period_num)) */ + crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX, + HS_SRV_DISASTER_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) &time_period_num, + sizeof(time_period_num)); + crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + /* When creating a blinded key, we need a parameter which construction is as * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). * @@ -744,6 +764,99 @@ hs_service_requires_uptime_circ(const smartlist_t *ports) return 0; } +/* Build hs_index which is used to find the responsible hsdirs. This index + * value is used to select the responsible HSDir where their hsdir_index is + * closest to this value. + * SHA3-256("store-at-idx" | blinded_public_key | + * INT_8(replicanum) | INT_8(period_num) ) + * + * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out) +{ + crypto_digest_t *digest; + + tor_assert(blinded_pk); + tor_assert(hs_index_out); + + /* Build hs_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) &replica, sizeof(replica)); + crypto_digest_add_bytes(digest, (const char *) &period_num, + sizeof(period_num)); + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Build hsdir_index which is used to find the responsible hsdirs. This is the + * index value that is compare to the hs_index when selecting an HSDir. + * SHA3-256("node-idx" | node_identity | + * shared_random_value | INT_8(period_num) ) + * + * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv_value, uint64_t period_num, + uint8_t *hsdir_index_out) +{ + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(srv_value); + tor_assert(hsdir_index_out); + + /* Build hsdir_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); + crypto_digest_add_bytes(digest, (const char *) &period_num, + sizeof(period_num)); + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Return a newly allocated buffer containing the current shared random value + * or if not present, a disaster value is computed using the given time period + * number. This function can't fail. */ +uint8_t * +hs_get_current_srv(uint64_t time_period_num) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *current_srv = sr_get_current(); + + if (current_srv) { + memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return a newly allocated buffer containing the previous shared random + * value or if not present, a disaster value is computed using the given time + * period number. This function can't fail. */ +uint8_t * +hs_get_previous_srv(uint64_t time_period_num) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *previous_srv = sr_get_previous(); + + if (previous_srv) { + memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index f9e3f297a9..a70ddc68da 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -101,6 +101,18 @@ #define HS_SUBCREDENTIAL_PREFIX "subcredential" #define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) +/* Node hidden service stored at index prefix value. */ +#define HS_INDEX_PREFIX "store-at-idx" +#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1) + +/* Node hidden service directory index prefix value. */ +#define HSDIR_INDEX_PREFIX "node-idx" +#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1) + +/* Prefix of the shared random value disaster mode. */ +#define HS_SRV_DISASTER_PREFIX "shared-random-disaster" +#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) + /* Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, @@ -122,6 +134,15 @@ typedef struct rend_service_port_config_t { char unix_addr[FLEXIBLE_ARRAY_MEMBER]; } rend_service_port_config_t; +/* Hidden service directory index used in a node_t which is set once we set + * the consensus. */ +typedef struct hsdir_index_t { + /* The hsdir index for the current time period. */ + uint8_t current[DIGEST256_LEN]; + /* The hsdir index for the next time period. */ + uint8_t next[DIGEST256_LEN]; +} hsdir_index_t; + void hs_init(void); void hs_free_all(void); @@ -172,6 +193,16 @@ link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); int hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now); +uint8_t *hs_get_current_srv(uint64_t time_period_num); +uint8_t *hs_get_previous_srv(uint64_t time_period_num); + +void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv, uint64_t period_num, + uint8_t *hsdir_index_out); +void hs_build_hs_index(uint64_t replica, + const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/or/nodelist.c b/src/or/nodelist.c index dafeb9f12d..117598cf1b 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -45,6 +45,7 @@ #include "dirserv.h" #include "entrynodes.h" #include "geoip.h" +#include "hs_common.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -164,12 +165,71 @@ node_get_or_create(const char *identity_digest) smartlist_add(the_nodelist->nodes, node); node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); node->country = -1; return node; } +/* For a given node for the consensus ns, set the hsdir index + * for the node, both current and next if possible. This can only fails if the + * node_t ed25519 identity key can't be found which would be a bug. */ +static void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns) +{ + time_t now = time(NULL); + const ed25519_public_key_t *node_identity_pk; + uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL; + uint64_t next_time_period_num, current_time_period_num; + + tor_assert(node); + tor_assert(ns); + + node_identity_pk = node_get_ed25519_id(node); + if (node_identity_pk == NULL) { + log_warn(LD_BUG, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); + goto done; + } + + /* Get the current and next time period number, we might use them both. */ + current_time_period_num = hs_get_time_period_num(now); + next_time_period_num = hs_get_next_time_period_num(now); + + /* If NOT in overlap mode, we only need to compute the current hsdir index + * for the ongoing time period and thus the current SRV. If it can't be + * found, the disaster one is returned. */ + current_hsdir_index_srv = hs_get_current_srv(current_time_period_num); + + if (hs_overlap_mode_is_active(ns, now)) { + /* We are in overlap mode, this means that our consensus has just cycled + * from current SRV to previous SRV so for the _next_ upcoming time + * period, we have to use the current SRV and use the previous SRV for the + * current time period. If the current or previous SRV can't be found, the + * disaster one is returned. */ + next_hsdir_index_srv = hs_get_current_srv(next_time_period_num); + /* The following can be confusing so again, in overlap mode, we use our + * previous SRV for our _current_ hsdir index. */ + current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num); + } + + /* Build the current hsdir index. */ + hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv, + current_time_period_num, node->hsdir_index->current); + if (next_hsdir_index_srv) { + /* Build the next hsdir index if we have a next SRV that we can use. */ + hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv, + next_time_period_num, node->hsdir_index->next); + } + + done: + tor_free(current_hsdir_index_srv); + tor_free(next_hsdir_index_srv); + return; +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -216,6 +276,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) dirserv_set_node_flags_from_authoritative_status(node, status); } + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (node->rs && node->rs->supports_v3_hsdir) { + node_set_hsdir_index(node, + networkstatus_get_latest_consensus()); + } + return node; } @@ -246,6 +314,12 @@ nodelist_add_microdesc(microdesc_t *md) node->md->held_by_nodes--; node->md = md; md->held_by_nodes++; + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } } return node; } @@ -283,6 +357,9 @@ nodelist_set_consensus(networkstatus_t *ns) } } + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } node_set_country(node); /* If we're not an authdir, believe others. */ @@ -410,6 +487,7 @@ node_free(node_t *node) if (node->md) node->md->held_by_nodes--; tor_assert(node->nodelist_idx == -1); + tor_free(node->hsdir_index); tor_free(node); } diff --git a/src/or/or.h b/src/or/or.h index f6c42b7a99..a06c816e8b 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -850,6 +850,8 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) struct hs_ident_edge_conn_t; struct hs_ident_dir_conn_t; struct hs_ident_circuit_t; +/* Stub because we can't include hs_common.h. */ +struct hsdir_index_t; /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple @@ -2490,6 +2492,10 @@ typedef struct node_t { time_t last_reachable; /* IPv4. */ time_t last_reachable6; /* IPv6. */ + /* Hidden service directory index data. This is used by a service or client + * in order to know what's the hs directory index for this node at the time + * the consensus is set. */ + struct hsdir_index_t *hsdir_index; } node_t; /** Linked list of microdesc hash lines for a single router in a directory diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 25ca0611cd..ec2533dad2 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1390,6 +1390,30 @@ sr_get_previous_for_control(void) return srv_str; } +/* Return current shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_current(void) +{ + const networkstatus_t *c = networkstatus_get_latest_consensus(); + if (c) { + return c->sr_info.current_srv; + } + return NULL; +} + +/* Return previous shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_previous(void) +{ + const networkstatus_t *c = networkstatus_get_latest_consensus(); + if (c) { + return c->sr_info.previous_srv; + } + return NULL; +} + #ifdef TOR_UNIT_TESTS /* Set the global value of number of SRV agreements so the test can play diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 1f027c70e0..58ea360df0 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp, char *sr_get_current_for_control(void); char *sr_get_previous_for_control(void); +const sr_srv_t *sr_get_current(void); +const sr_srv_t *sr_get_previous(void); + #ifdef SHARED_RANDOM_PRIVATE /* Encode */ From 06909cafef6aee9141541fc85cbea5de0b2e5f6a Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Apr 2017 11:06:19 -0400 Subject: [PATCH 23/91] prop224: Add hsdir consensus parameters Signed-off-by: David Goulet --- src/or/hs_common.c | 30 ++++++++++++++++++++++++++++++ src/or/hs_common.h | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 0e3562de87..4ea92aaeab 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -857,6 +857,36 @@ hs_get_previous_srv(uint64_t time_period_num) return sr_value; } +/* Return the number of replicas defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_n_replicas(void) +{ + /* The [1,16] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_n_replicas", + HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); +} + +/* Return the spread fetch value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_fetch(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_fetch", + HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); +} + +/* Return the spread store value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_store(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_store", + HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a70ddc68da..d367e815e8 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -113,6 +113,13 @@ #define HS_SRV_DISASTER_PREFIX "shared-random-disaster" #define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) +/* Default value of number of hsdir replicas (hsdir_n_replicas). */ +#define HS_DEFAULT_HSDIR_N_REPLICAS 2 +/* Default value of hsdir spread store (hsdir_spread_store). */ +#define HS_DEFAULT_HSDIR_SPREAD_STORE 3 +/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 + /* Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, @@ -203,6 +210,10 @@ void hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, uint64_t period_num, uint8_t *hs_index_out); +int32_t hs_get_hsdir_n_replicas(void); +int32_t hs_get_hsdir_spread_fetch(void); +int32_t hs_get_hsdir_spread_store(void); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS From 0bcc9ad58d18bdd9fb73db33b9a7fe4cfd2ac93b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Apr 2017 12:23:43 -0400 Subject: [PATCH 24/91] prop224: Add a responsible HSDir function Signed-off-by: David Goulet --- src/or/hs_common.c | 152 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 4 ++ 2 files changed, 156 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 4ea92aaeab..4d5417afae 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -15,11 +15,13 @@ #include "config.h" #include "networkstatus.h" +#include "nodelist.h" #include "hs_cache.h" #include "hs_common.h" #include "hs_service.h" #include "rendcommon.h" #include "rendservice.h" +#include "router.h" #include "shared_random.h" /* Ed25519 Basepoint value. Taken from section 5 of @@ -30,6 +32,48 @@ static const char *str_ed25519_basepoint = "463168356949264781694283940034751631413" "07993866256225615783033603165251855960)"; +/* Helper function: The key is a digest that we compare to a node_t object + * current hsdir_index. */ +static int +compare_digest_to_current_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_next_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects current hsdir_index. */ +static int +compare_node_current_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->current, + node2->hsdir_index->current, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_next_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->next, + node2->hsdir_index->next, + DIGEST256_LEN); +} + /* Allocate and return a string containing the path to filename in directory. * This function will never return NULL. The caller must free this path. */ char * @@ -887,6 +931,114 @@ hs_get_hsdir_spread_store(void) HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); } +/* For a given blinded key and time period number, get the responsible HSDir + * and put their routerstatus_t object in the responsible_dirs list. If + * is_next_period is true, the next hsdir_index of the node_t is used. If + * is_client is true, the spread fetch consensus parameter is used else the + * spread store is used which is only for upload. This function can't fail but + * it is possible that the responsible_dirs list contains fewer nodes than + * expected. + * + * This function goes over the latest consensus routerstatus list and sorts it + * by their node_t hsdir_index then does a binary search to find the closest + * node. All of this makes it a bit CPU intensive so use it wisely. */ +void +hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int is_next_period, + int is_client, smartlist_t *responsible_dirs) +{ + smartlist_t *sorted_nodes; + /* The compare function used for the smartlist bsearch. We have two + * different depending on is_next_period. */ + int (*cmp_fct)(const void *, const void **); + + tor_assert(blinded_pk); + tor_assert(responsible_dirs); + + sorted_nodes = smartlist_new(); + + /* Add every node_t that support HSDir v3 for which we do have a valid + * hsdir_index already computed for them for this consensus. */ + { + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (!c || smartlist_len(c->routerstatus_list) == 0) { + log_warn(LD_REND, "No valid consensus so we can't get the responsible " + "hidden service directories."); + goto done; + } + SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) { + /* Even though this node_t object won't be modified and should be const, + * we can't add const object in a smartlist_t. */ + node_t *n = node_get_mutable_by_id(rs->identity_digest); + tor_assert(n); + if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { + if (BUG(n->hsdir_index == NULL)) { + continue; + } + smartlist_add(sorted_nodes, n); + } + } SMARTLIST_FOREACH_END(rs); + } + if (smartlist_len(sorted_nodes) == 0) { + log_warn(LD_REND, "No nodes found to be HSDir or supporting v3."); + goto done; + } + + /* First thing we have to do is sort all node_t by hsdir_index. The + * is_next_period tells us if we want the current or the next one. Set the + * bsearch compare function also while we are at it. */ + if (is_next_period) { + smartlist_sort(sorted_nodes, compare_node_next_hsdir_index); + cmp_fct = compare_digest_to_next_hsdir_index; + } else { + smartlist_sort(sorted_nodes, compare_node_current_hsdir_index); + cmp_fct = compare_digest_to_current_hsdir_index; + } + + /* For all replicas, we'll select a set of HSDirs using the consensus + * parameters and the sorted list. The replica starting at value 1 is + * defined by the specification. */ + for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) { + int idx, start, found, n_added = 0; + uint8_t hs_index[DIGEST256_LEN] = {0}; + /* Number of node to add to the responsible dirs list depends on if we are + * trying to fetch or store. A client always fetches. */ + int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); + + /* Get the index that we should use to select the node. */ + hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); + /* The compare function pointer has been set correctly earlier. */ + start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct, + &found); + /* Getting the length of the list if no member is greater than the key we + * are looking for so start at the first element. */ + if (idx == smartlist_len(sorted_nodes)) { + start = idx = 0; + } + while (n_added < n_to_add) { + const node_t *node = smartlist_get(sorted_nodes, idx); + /* If the node has already been selected which is possible between + * replicas, the specification says to skip over. */ + if (!smartlist_contains(responsible_dirs, node->rs)) { + smartlist_add(responsible_dirs, node->rs); + ++n_added; + } + if (++idx == smartlist_len(sorted_nodes)) { + /* Wrap if we've reached the end of the list. */ + idx = 0; + } + if (idx == start) { + /* We've gone over the whole list, stop and avoid infinite loop. */ + break; + } + } + } + + done: + smartlist_free(sorted_nodes); +} + /* Initialize the entire HS subsytem. This is called in tor_init() before any * torrc options are loaded. Only for >= v3. */ void diff --git a/src/or/hs_common.h b/src/or/hs_common.h index d367e815e8..695f0b8954 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -214,6 +214,10 @@ int32_t hs_get_hsdir_n_replicas(void); int32_t hs_get_hsdir_spread_fetch(void); int32_t hs_get_hsdir_spread_store(void); +void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int is_next_period, + int is_client, smartlist_t *responsible_dirs); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS From bce0c6caadaf27b857f6980f1453798909625267 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Apr 2017 14:36:53 -0400 Subject: [PATCH 25/91] prop224: Directory function to upload descriptor This commit adds a directory command function to make an upload directory request for a service descriptor. It is not used yet, just the groundwork. Signed-off-by: David Goulet --- src/or/directory.c | 94 ++++++++++++++++++++++++++++++++++++++++++++-- src/or/directory.h | 4 ++ src/or/or.h | 11 ++++-- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 13daea354c..fc83c013cb 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -186,6 +186,8 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: case DIR_PURPOSE_UPLOAD_RENDDESC_V2: case DIR_PURPOSE_FETCH_RENDDESC_V2: + case DIR_PURPOSE_FETCH_HSDESC: + case DIR_PURPOSE_UPLOAD_HSDESC: return 1; case DIR_PURPOSE_SERVER: default: @@ -244,6 +246,10 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_HSDESC: + return "hidden-service descriptor fetch"; + case DIR_PURPOSE_UPLOAD_HSDESC: + return "hidden-service descriptor upload"; case DIR_PURPOSE_FETCH_MICRODESC: return "microdescriptor fetch"; } @@ -1034,11 +1040,12 @@ struct directory_request_t { size_t payload_len; /** Value to send in an if-modified-since header, or 0 for none. */ time_t if_modified_since; - /** Hidden-service-specific information */ + /** Hidden-service-specific information v2. */ const rend_data_t *rend_query; /** Extra headers to append to the request */ config_line_t *additional_headers; - /** */ + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; /** Used internally to directory.c: gets informed when the attempt to * connect to the directory succeeds or fails, if that attempt bears on the * directory's usability as a directory guard. */ @@ -1268,6 +1275,21 @@ directory_request_set_rend_query(directory_request_t *req, } req->rend_query = query; } +/** + * Set an object containing HS connection identifier to be associated with + * this request. Note that only an alias to ident is stored, so the + * ident object must outlive the request. + */ +void +directory_request_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC || + req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); + } + req->hs_ident = ident; +} /** Set a static circuit_guard_state_t object to affliate with the request in * req. This object will receive notification when the attempt to * connect to the guard either succeeds or fails. */ @@ -1389,6 +1411,7 @@ directory_initiate_request,(directory_request_t *request)) const dir_indirection_t indirection = request->indirection; const char *resource = request->resource; const rend_data_t *rend_query = request->rend_query; + const hs_ident_dir_conn_t *hs_ident = request->hs_ident; circuit_guard_state_t *guard_state = request->guard_state; tor_assert(or_addr_port->port || dir_addr_port->port); @@ -1476,8 +1499,16 @@ directory_initiate_request,(directory_request_t *request)) conn->dirconn_direct = !anonymized_connection; /* copy rendezvous data, if any */ - if (rend_query) + if (rend_query) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!hs_ident); conn->rend_data = rend_data_dup(rend_query); + } + if (hs_ident) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!rend_query); + conn->hs_ident = hs_ident_dir_conn_dup(hs_ident); + } if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ @@ -1835,6 +1866,12 @@ directory_send_command(dir_connection_t *conn, httpcommand = "POST"; url = tor_strdup("/tor/rendezvous2/publish"); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + tor_assert(resource); + tor_assert(payload); + httpcommand = "POST"; + tor_asprintf(&url, "/tor/hs/%s/publish", resource); + break; default: tor_assert(0); return; @@ -2189,6 +2226,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *, const response_handler_args_t *); static int handle_response_upload_renddesc_v2(dir_connection_t *, const response_handler_args_t *); +static int handle_response_upload_hsdesc(dir_connection_t *, + const response_handler_args_t *); static int dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, @@ -2489,6 +2528,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case DIR_PURPOSE_UPLOAD_RENDDESC_V2: rv = handle_response_upload_renddesc_v2(conn, &args); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + rv = handle_response_upload_hsdesc(conn, &args); + break; default: tor_assert_nonfatal_unreached(); rv = -1; @@ -3180,6 +3222,52 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn, return 0; } +/** + * Handler function: processes a response to a POST request to upload an + * hidden service descriptor. + **/ +static int +handle_response_upload_hsdesc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + + tor_assert(conn); + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC); + + log_info(LD_REND, "Uploaded hidden service descriptor (status %d " + "(%s))", + status_code, escaped(reason)); + /* For this directory response, it MUST have an hidden service identifier on + * this connection. */ + tor_assert(conn->hs_ident); + switch (status_code) { + case 200: + log_info(LD_REND, "Uploading hidden service descriptor: " + "finished with status 200 (%s)", escaped(reason)); + /* XXX: Trigger control event. */ + break; + case 400: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + /* XXX: Trigger control event. */ + break; + default: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + /* XXX: Trigger control event. */ + break; + } + + return 0; +} + /** Called when a directory connection reaches EOF. */ int connection_dir_reached_eof(dir_connection_t *conn) diff --git a/src/or/directory.h b/src/or/directory.h index 3e574cc6ac..6b3102bc33 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -12,6 +12,8 @@ #ifndef TOR_DIRECTORY_H #define TOR_DIRECTORY_H +#include "hs_ident.h" + int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, @@ -71,6 +73,8 @@ void directory_request_set_if_modified_since(directory_request_t *req, time_t if_modified_since); void directory_request_set_rend_query(directory_request_t *req, const rend_data_t *query); +void directory_request_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); void directory_request_set_routerstatus(directory_request_t *req, const routerstatus_t *rs); diff --git a/src/or/or.h b/src/or/or.h index a06c816e8b..09e58b7b18 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -421,15 +421,20 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define DIR_PURPOSE_MAX_ 19 +/** A connetion to a hidden service directory: upload a descriptor. */ +#define DIR_PURPOSE_UPLOAD_HSDESC 20 +/** A connetion to a hidden service directory: fetch a descriptor. */ +#define DIR_PURPOSE_FETCH_HSDESC 21 +#define DIR_PURPOSE_MAX_ 21 /** True iff p is a purpose corresponding to uploading * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ - (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2) + (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ + (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \ + (p)==DIR_PURPOSE_UPLOAD_HSDESC) #define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ From ac848777f9db588c54ce3eb950d41375dc324074 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Apr 2017 15:27:11 -0400 Subject: [PATCH 26/91] prop224: Upload service descriptors Signed-off-by: David Goulet --- src/or/hs_service.c | 326 +++++++++++++++++++++++++++++++++++++++++++- src/or/hs_service.h | 21 ++- 2 files changed, 338 insertions(+), 9 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 567ca0be03..85610de572 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -14,6 +14,7 @@ #include "circuitlist.h" #include "circuituse.h" #include "config.h" +#include "directory.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -172,10 +173,10 @@ static void set_service_default_config(hs_service_config_t *c, const or_options_t *options) { + (void) options; tor_assert(c); c->ports = smartlist_new(); c->directory_path = NULL; - c->descriptor_post_period = options->RendPostPeriod; c->max_streams_per_rdv_circuit = 0; c->max_streams_close_circuit = 0; c->num_intro_points = NUM_INTRO_POINTS_DEFAULT; @@ -532,6 +533,23 @@ get_intro_circuit(const hs_service_intro_point_t *ip) return circ; } +/* Return the number of introduction points that are established for the + * given descriptor. */ +static unsigned int +count_desc_circuit_established(const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + count += ip->circuit_established; + } DIGEST256MAP_FOREACH_END; + + return count; +} + /* Close all rendezvous circuits for the given service. */ static void close_service_rp_circuits(hs_service_t *service) @@ -867,6 +885,8 @@ service_descriptor_free(hs_service_descriptor_t *desc) hs_descriptor_free(desc->desc); memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); + SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); + smartlist_free(desc->hsdir_missing_info); /* Cleanup all intro points. */ digest256map_free(desc->intro_points.map, service_intro_point_free_); tor_free(desc); @@ -880,6 +900,7 @@ service_descriptor_new(void) sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Initialize the intro points map. */ sdesc->intro_points.map = digest256map_new(); + sdesc->hsdir_missing_info = smartlist_new(); return sdesc; } @@ -1140,6 +1161,7 @@ build_service_descriptor(hs_service_t *service, time_t now, tor_assert(desc_out); desc = service_descriptor_new(); + desc->time_period_num = time_period_num; /* Create the needed keys so we can setup the descriptor content. */ if (build_service_desc_keys(service, desc, time_period_num) < 0) { @@ -1315,6 +1337,11 @@ pick_needed_intro_points(hs_service_t *service, /* Valid intro point object, add it to the descriptor current map. */ service_intro_point_add(desc->intro_points.map, ip); } + /* We've successfully picked all our needed intro points thus none are + * missing which will tell our upload process to expect the number of + * circuits to be the number of configured intro points circuits and not the + * number of intro points object that we have. */ + desc->missing_intro_points = 0; /* Success. */ done: @@ -1357,6 +1384,13 @@ update_service_descriptor(hs_service_t *service, * indicate that this descriptor should be uploaded from now on. */ desc->next_upload_time = now; } + /* Were we able to pick all the intro points we needed? If not, we'll + * flag the descriptor that it's missing intro points because it + * couldn't pick enough which will trigger a descriptor upload. */ + if ((num_new_intro_points + num_intro_points) < + service->config.num_intro_points) { + desc->missing_intro_points = 1; + } } } @@ -1710,6 +1744,192 @@ run_build_circuit_event(time_t now) } FOR_EACH_SERVICE_END; } +/* Encode and sign the service descriptor desc and upload it to the given + * hidden service directory. This does nothing if PublishHidServDescriptors + * is false. */ +static void +upload_descriptor_to_hsdir(const hs_service_t *service, + hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char version_str[4] = {0}, *encoded_desc = NULL; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(service); + tor_assert(desc); + tor_assert(hsdir); + + memset(&ident, 0, sizeof(ident)); + + /* Let's avoid doing that if tor is configured to not publish. */ + if (!get_options()->PublishHidServDescriptors) { + log_info(LD_REND, "Service %s not publishing descriptor. " + "PublishHidServDescriptors is set to 1.", + safe_str_client(service->onion_address)); + goto end; + } + + /* First of all, we'll encode the descriptor. This should NEVER fail but + * just in case, let's make sure we have an actual usable descriptor. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto end; + } + + /* Setup the connection identifier. */ + ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk); + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs//publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", + service->config.version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir->rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); + + /* Logging so we know where it was sent. */ + { + int is_next_desc = (service->desc_next == desc); + const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next : + hsdir->hsdir_index->current; + log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 + " initiated upload request to %s with index %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", + desc->desc->plaintext_data.revision_counter, + safe_str_client(node_describe(hsdir)), + safe_str_client(hex_str((const char *) index, 32))); + } + + /* XXX: Inform control port of the upload event (#20699). */ + end: + tor_free(encoded_desc); + return; +} + +/* Encode and sign the service descriptor desc and upload it to the + * responsible hidden service directories. If for_next_period is true, the set + * of directories are selected using the next hsdir_index. This does nothing + * if PublishHidServDescriptors is false. */ +static void +upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc, int for_next_period) +{ + smartlist_t *responsible_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + /* Get our list of responsible HSDir. */ + responsible_dirs = smartlist_new(); + /* The parameter 0 means that we aren't a client so tell the function to use + * the spread store consensus paremeter. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + for_next_period, 0, responsible_dirs); + + /* For each responsible HSDir we have, initiate an upload command. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, + hsdir_rs) { + const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + /* Getting responsible hsdir implies that the node_t object exists for the + * routerstatus_t found in the consensus else we have a problem. */ + tor_assert(hsdir_node); + /* Do not upload to an HSDir we don't have a descriptor for. */ + if (!node_has_descriptor(hsdir_node)) { + log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading " + "descriptor. We'll try later once we have it.", + safe_str_client(node_describe(hsdir_node))); + /* Once we get new directory information, this HSDir will be retried if + * we ever get the descriptor. */ + smartlist_add(desc->hsdir_missing_info, + tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN)); + continue; + } + + /* Upload this descriptor to the chosen directory. */ + upload_descriptor_to_hsdir(service, desc, hsdir_node); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Set the next upload time for this descriptor. Even if we are configured + * to not upload, we still want to follow the right cycle of life for this + * descriptor. */ + desc->next_upload_time = + (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN, + HS_SERVICE_NEXT_UPLOAD_TIME_MAX)); + { + char fmt_next_time[ISO_TIME_LEN+1]; + format_local_iso_time(fmt_next_time, desc->next_upload_time); + log_debug(LD_REND, "Service %s set to upload a descriptor at %s", + safe_str_client(service->onion_address), fmt_next_time); + } + + /* Increment the revision counter so the next update (which will trigger an + * upload) will have the right value. We do this at this stage to only do it + * once because a descriptor can have many updates before being uploaded. By + * doing it at upload, we are sure to only increment by 1 and thus avoid + * leaking how many operations we made on the descriptor from the previous + * one before uploading. */ + desc->desc->plaintext_data.revision_counter += 1; + + smartlist_free(responsible_dirs); + return; +} + +/* Return 1 if the given descriptor from the given service can be uploaded + * else return 0 if it can not. */ +static int +should_service_upload_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + + /* If this descriptors has missing intro points that is that it couldn't get + * them all when it was time to pick them, it means that we should upload + * instead of waiting an arbitrary amount of time breaking the service. + * Else, if we have no missing intro points, we use the value taken from the + * service configuration. */ + (desc->missing_intro_points) ? + (num_intro_points = digest256map_size(desc->intro_points.map)) : + (num_intro_points = service->config.num_intro_points); + + /* This means we tried to pick intro points but couldn't get any so do not + * upload descriptor in this case. We need at least one for the service to + * be reachable. */ + if (desc->missing_intro_points && num_intro_points == 0) { + goto cannot; + } + + /* Check if all our introduction circuit have been established for all the + * intro points we have selected. */ + if (count_desc_circuit_established(desc) != num_intro_points) { + goto cannot; + } + + /* Is it the right time to upload? */ + if (desc->next_upload_time > now) { + goto cannot; + } + + /* Can upload! */ + return 1; + cannot: + return 0; +} + /* Scheduled event run from the main loop. Try to upload the descriptor for * each service. */ static void @@ -1724,10 +1944,35 @@ run_upload_descriptor_event(time_t now) /* Run v3+ check. */ FOR_EACH_SERVICE_BEGIN(service) { - /* XXX: Upload if needed the descriptor(s). Update next upload time. */ - /* XXX: Build the descriptor intro points list with - * build_desc_intro_points() once we have enough circuit opened. */ - build_desc_intro_points(service, NULL, now); + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + int for_next_period = 0; + + /* Can this descriptor be uploaed? */ + if (!should_service_upload_descriptor(service, desc, now)) { + continue; + } + + log_info(LD_REND, "Initiating upload for hidden service %s descriptor " + "for service %s with %u/%u introduction points%s.", + (desc == service->desc_current) ? "current" : "next", + safe_str_client(service->onion_address), + digest256map_size(desc->intro_points.map), + service->config.num_intro_points, + (desc->missing_intro_points) ? " (couldn't pick more)" : ""); + + /* At this point, we have to upload the descriptor so start by building + * the intro points descriptor section which we are now sure to be + * accurate because all circuits have been established. */ + build_desc_intro_points(service, desc, now); + + /* If the service is in the overlap period and this descriptor is the + * next one, it has to be uploaded for the next time period meaning + * we'll use the next node_t hsdir_index to pick the HSDirs. */ + if (desc == service->desc_next) { + for_next_period = 1; + } + upload_descriptor_to_all(service, desc, for_next_period); + } FOR_EACH_DESCRIPTOR_END; } FOR_EACH_SERVICE_END; } @@ -1926,10 +2171,81 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, return -1; } +/* For a given service and a descriptor of that service, consider retrying to + * upload the descriptor to any directories from which we had missing + * information when originally tried to be uploaded. This is called when our + * directory information has changed. */ +static void +consider_hsdir_retry(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + smartlist_t *responsible_dirs = NULL; + smartlist_t *still_missing_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + responsible_dirs = smartlist_new(); + still_missing_dirs = smartlist_new(); + + /* We first need to get responsible directories from the latest consensus so + * we can then make sure that the node that we were missing information for + * is still responsible for this descriptor. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) { + const node_t *node; + const char *id = rs->identity_digest; + if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) { + continue; + } + /* We do need a node_t object and descriptor to perform an upload. If + * found, we remove the id from the missing dir list else we add it to the + * still missing dir list to keep track of id that are still missing. */ + node = node_get_by_id(id); + if (node && node_has_descriptor(node)) { + upload_descriptor_to_hsdir(service, desc, node); + smartlist_remove(desc->hsdir_missing_info, id); + } else { + smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN)); + } + } SMARTLIST_FOREACH_END(rs); + + /* Switch the still missing dir list with the current missing dir list in + * the descriptor. It is possible that the list ends up empty which is what + * we want if we have no more missing dir. */ + SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); + smartlist_free(desc->hsdir_missing_info); + desc->hsdir_missing_info = still_missing_dirs; + + /* No ownership of the routerstatus_t object in this list. */ + smartlist_free(responsible_dirs); +} + /* ========== */ /* Public API */ /* ========== */ +/* Called when our internal view of the directory has changed. We might have + * new descriptors for hidden service directories that we didn't have before + * so try them if it's the case. */ +void +hs_service_dir_info_changed(void) +{ + /* For each service we have, check every descriptor and consider retrying to + * upload it to directories that we might have had missing information + * previously that is missing a router descriptor. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* This cleans up the descriptor missing hsdir information list if a + * successful upload is made or if any of the directory aren't + * responsible anymore for the service descriptor. */ + consider_hsdir_retry(service, desc); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + /* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and * launch a circuit to the rendezvous point. */ int diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 8776a4412c..ba805117eb 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -25,6 +25,11 @@ * present. */ #define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO +/* As described in the specification, service publishes their next descriptor + * at a random time between those two values (in seconds). */ +#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60) +#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60) + /* Service side introduction point. */ typedef struct hs_service_intro_point_t { /* Top level intropoint "shared" data between client/service. */ @@ -106,6 +111,17 @@ typedef struct hs_service_descriptor_t { /* The time period number this descriptor has been created for. */ uint64_t time_period_num; + + /* True iff we have missing intro points for this descriptor because we + * couldn't pick any nodes. */ + unsigned int missing_intro_points : 1; + + /* List of hidden service directories node_t object to which we couldn't + * upload this descriptor because we didn't have its router descriptor at + * the time. If this list is non-empty, only the relays in this list are + * re-tried to upload this descriptor when our directory information have + * been updated. */ + smartlist_t *hsdir_missing_info; } hs_service_descriptor_t; /* Service key material. */ @@ -134,10 +150,6 @@ typedef struct hs_service_config_t { * if the service is ephemeral. Specified by HiddenServiceDir option. */ char *directory_path; - /* The time period after which a descriptor is uploaded to the directories - * in seconds. Specified by RendPostPeriod option. */ - uint32_t descriptor_post_period; - /* The maximum number of simultaneous streams per rendezvous circuit that * are allowed to be created. No limit if 0. Specified by * HiddenServiceMaxStreams option. */ @@ -237,6 +249,7 @@ void hs_service_free(hs_service_t *service); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); +void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); void hs_service_circuit_has_opened(origin_circuit_t *circ); int hs_service_receive_intro_established(origin_circuit_t *circ, From feed375f194d389dbc4c624e09bdd9161931e23a Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 9 May 2017 14:31:17 -0400 Subject: [PATCH 27/91] prop224: Implement a service intro point failure cache Imagine a Tor network where you have only 8 nodes available due to some reasons. And your hidden service wants 8 introduction points. Everything is fine but then a node goes down bringing the network to 7. The service will retry 3 times that node and then give up but keep it in a failure cache for 5 minutes (INTRO_CIRC_RETRY_PERIOD) so it doesn't retry it non stop and exhaust the maximum number of circuit retry. In the real public network today, this is unlikely to happen unless the ExcludeNodes list is extremely restrictive. Signed-off-by: David Goulet --- src/or/hs_service.c | 81 ++++++++++++++++++++++++++++++++++++++++++++- src/or/hs_service.h | 6 ++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 85610de572..42523ed852 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -889,6 +889,7 @@ service_descriptor_free(hs_service_descriptor_t *desc) smartlist_free(desc->hsdir_missing_info); /* Cleanup all intro points. */ digest256map_free(desc->intro_points.map, service_intro_point_free_); + digestmap_free(desc->intro_points.failed_id, tor_free_); tor_free(desc); } @@ -900,10 +901,71 @@ service_descriptor_new(void) sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Initialize the intro points map. */ sdesc->intro_points.map = digest256map_new(); + sdesc->intro_points.failed_id = digestmap_new(); sdesc->hsdir_missing_info = smartlist_new(); return sdesc; } +/* From the given service, remove all expired failing intro points for each + * descriptor. */ +static void +remove_expired_failing_intro(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the failing intro points list. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) { + time_t failure_time = *t; + if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) { + MAP_DEL_CURRENT(key); + tor_free(t); + } + } DIGESTMAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* For the given descriptor desc, put all node_t object found from its failing + * intro point list and put them in the given node_list. */ +static void +setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, + smartlist_t *node_list) +{ + tor_assert(desc); + tor_assert(node_list); + + DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) { + (void) t; /* Make gcc happy. */ + const node_t *node = node_get_by_id(key); + if (node) { + smartlist_add(node_list, (void *) node); + } + } DIGESTMAP_FOREACH_END; +} + +/* For the given failing intro point ip, we add its time of failure to the + * failed map and index it by identity digest (legacy ID) in the descriptor + * desc failed id map. */ +static void +remember_failing_intro_point(const hs_service_intro_point_t *ip, + hs_service_descriptor_t *desc, time_t now) +{ + time_t *time_of_failure, *prev_ptr; + const hs_desc_link_specifier_t *legacy_ls; + + tor_assert(ip); + tor_assert(desc); + + time_of_failure = tor_malloc_zero(sizeof(time_t)); + *time_of_failure = now; + legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + tor_assert(legacy_ls); + prev_ptr = digestmap_set(desc->intro_points.failed_id, + (const char *) legacy_ls->u.legacy_id, + time_of_failure); + tor_free(prev_ptr); +} + /* Copy the descriptor link specifier object from src to dst. */ static void link_specifier_copy(hs_desc_link_specifier_t *dst, @@ -1318,6 +1380,9 @@ pick_needed_intro_points(hs_service_t *service, hs_service_intro_point_t *, ip) { smartlist_add(exclude_nodes, (void *) get_node_from_intro_point(ip)); } DIGEST256MAP_FOREACH_END; + /* Also, add the failing intro points that our descriptor encounteered in + * the exclude node list. */ + setup_intro_point_exclude_list(desc, exclude_nodes); for (i = 0; i < num_needed_ip; i++) { hs_service_intro_point_t *ip; @@ -1462,9 +1527,19 @@ cleanup_intro_points(hs_service_t *service, time_t now) * 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)) { + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES)) { + /* Remove intro point from descriptor map. We'll add it to the failed + * map if we retried it too many times. */ MAP_DEL_CURRENT(key); + + /* We've retried too many times, remember it has a failed intro point + * so we don't pick it up again. It will be retried in + * INTRO_CIRC_RETRY_PERIOD seconds. */ + if (ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, now); + } 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 @@ -1550,6 +1625,10 @@ run_housekeeping_event(time_t now) /* Cleanup invalid intro points from the service descriptor. */ cleanup_intro_points(service, now); + /* Remove expired failing intro point from the descriptor failed list. We + * reset them at each INTRO_CIRC_RETRY_PERIOD. */ + remove_expired_failing_intro(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 diff --git a/src/or/hs_service.h b/src/or/hs_service.h index ba805117eb..be24bb4e31 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -87,6 +87,12 @@ typedef struct hs_service_intropoints_t { /* Contains the current hs_service_intro_point_t objects indexed by * authentication public key. */ digest256map_t *map; + + /* Contains node's identity key digest that were introduction point for this + * descriptor but were retried to many times. We keep those so we avoid + * re-picking them over and over for a circuit retry period. + * XXX: Once we have #22173, change this to only use ed25519 identity. */ + digestmap_t *failed_id; } hs_service_intropoints_t; /* Representation of a service descriptor. */ From 670cecaf669d87bd3bdb52ef1848cf6d73725746 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 9 May 2017 16:05:28 -0400 Subject: [PATCH 28/91] prop224: Make INTRODUCE2 min/max a consensus param Introduction point are rotated either if we get X amounts of INTRODUCE2 cells on it or a time based expiration. This commit adds two consensus parameters which are the min and max value bounding the random value X. Signed-off-by: David Goulet --- src/or/hs_service.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 42523ed852..f72f0f30e6 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -203,6 +203,30 @@ service_clear_config(hs_service_config_t *config) memset(config, 0, sizeof(*config)); } +/* Return the number of minimum INTRODUCE2 cell defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_min_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_introduce2", + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} + +/* Return the number of maximum INTRODUCE2 cell defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_max_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_introduce2", + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} + /* Helper: Function that needs to return 1 for the HT for each loop which * frees every service in an hash map. */ static int @@ -274,10 +298,10 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) * 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); + crypto_rand_int_range(get_intro_point_min_introduce2(), + get_intro_point_max_introduce2()); + /* XXX: These will be controlled by consensus params. (#20961) */ ip->time_to_expire = time(NULL) + crypto_rand_int_range(INTRO_POINT_LIFETIME_MIN_SECONDS, INTRO_POINT_LIFETIME_MAX_SECONDS); From f0e02e3a141150f02bafaab35d6ab48c79d78d6d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 9 May 2017 16:10:14 -0400 Subject: [PATCH 29/91] prop224: Make intro point min/max lifetime a consensus param Signed-off-by: David Goulet --- src/or/hs_service.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index f72f0f30e6..55c9b689f8 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -227,6 +227,30 @@ get_intro_point_max_introduce2(void) 0, INT32_MAX); } +/* Return the minimum lifetime of an introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_min_lifetime(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_lifetime", + INTRO_POINT_LIFETIME_MIN_SECONDS, + 0, INT32_MAX); +} + +/* Return the maximum lifetime of an introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_max_lifetime(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_lifetime", + INTRO_POINT_LIFETIME_MAX_SECONDS, + 0, INT32_MAX); +} + /* Helper: Function that needs to return 1 for the HT for each loop which * frees every service in an hash map. */ static int @@ -301,10 +325,9 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) ip->introduce2_max = crypto_rand_int_range(get_intro_point_min_introduce2(), get_intro_point_max_introduce2()); - /* XXX: These will be controlled by consensus params. (#20961) */ ip->time_to_expire = time(NULL) + - crypto_rand_int_range(INTRO_POINT_LIFETIME_MIN_SECONDS, - INTRO_POINT_LIFETIME_MAX_SECONDS); + crypto_rand_int_range(get_intro_point_min_lifetime(), + get_intro_point_max_lifetime()); ip->replay_cache = replaycache_new(0, 0); /* Initialize the base object. We don't need the certificate object. */ From 848e701f55039b43e90cb1dae226db567876f2d3 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 9 May 2017 16:15:12 -0400 Subject: [PATCH 30/91] prop224: Make the number of extra intro point a consensus param Signed-off-by: David Goulet --- src/or/hs_service.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 55c9b689f8..16ffc94b59 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -251,6 +251,17 @@ get_intro_point_max_lifetime(void) 0, INT32_MAX); } +/* Return the number of extra introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_num_extra(void) +{ + /* The [0, 128] range bounds the number of extra introduction point allowed. + * Above 128 intro points, it's getting a bit crazy. */ + return networkstatus_get_param(NULL, "hs_intro_num_extra", + NUM_INTRO_POINTS_EXTRA, 0, 128); +} + /* Helper: Function that needs to return 1 for the HT for each loop which * frees every service in an hash map. */ static int @@ -1406,18 +1417,18 @@ pick_needed_intro_points(hs_service_t *service, /* 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. + * starting up), we launch get_intro_point_num_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; + num_needed_ip += get_intro_point_num_extra(); } /* Build an exclude list of nodes of our intro point(s). The expiring intro @@ -1780,7 +1791,7 @@ get_max_intro_circ_per_period(const hs_service_t *service) * extra value so we can launch multiple circuits at once and pick the * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll * pick 5 intros and launch 5 circuits. */ - count += (num_wanted_ip + NUM_INTRO_POINTS_EXTRA); + count += (num_wanted_ip + get_intro_point_num_extra()); /* Then we add the number of retries that is possible to do for each intro * point. If we want 3 intros, we'll allow 3 times the number of possible From 5d2506d70cdc73d840e0222d0f007365ae44fac0 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 10 May 2017 11:04:06 -0400 Subject: [PATCH 31/91] prop224: Sandbox support for service Signed-off-by: David Goulet --- src/or/hs_cache.c | 6 ++++-- src/or/hs_service.c | 46 +++++++++++++++++++++++++++++++++++++++++++++ src/or/hs_service.h | 2 ++ src/or/main.c | 3 ++- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 29681b42b5..30215d8681 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -124,8 +124,10 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) if (cache_entry->plaintext_data->revision_counter >= desc->plaintext_data->revision_counter) { log_info(LD_REND, "Descriptor revision counter in our cache is " - "greater or equal than the one we received. " - "Rejecting!"); + "greater or equal than the one we received (%d/%d). " + "Rejecting!", + (int)cache_entry->plaintext_data->revision_counter, + (int)desc->plaintext_data->revision_counter); goto err; } /* We now know that the descriptor we just received is a new one so diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 16ffc94b59..760ba1bc3d 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2360,10 +2360,56 @@ consider_hsdir_retry(const hs_service_t *service, smartlist_free(responsible_dirs); } +/* Add to list every filename used by service. This is used by the sandbox + * subsystem. */ +static void +service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) +{ + const char *s_dir; + char fname[128] = {0}; + + tor_assert(service); + tor_assert(list); + + /* Ease our life. */ + s_dir = service->config.directory_path; + /* The hostname file. */ + smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname)); + /* The key files splitted in two. */ + tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); + tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); +} + /* ========== */ /* Public API */ /* ========== */ +/* Add to file_list every filename used by a configured hidden service, and to + * dir_list every directory path used by a configured hidden service. This is + * used by the sandbox subsystem to whitelist those. */ +void +hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list) +{ + tor_assert(file_list); + tor_assert(dir_list); + + /* Add files and dirs for legacy services. */ + rend_services_add_filenames_to_lists(file_list, dir_list); + + /* Add files and dirs for v3+. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* Skip ephemeral service, they don't touch the disk. */ + if (service->config.is_ephemeral) { + continue; + } + service_add_fnames_to_list(service, file_list); + smartlist_add_strdup(dir_list, service->config.directory_path); + } FOR_EACH_DESCRIPTOR_END; +} + /* Called when our internal view of the directory has changed. We might have * new descriptors for hidden service directories that we didn't have before * so try them if it's the case. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index be24bb4e31..7d026fb354 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -254,6 +254,8 @@ void hs_service_free(hs_service_t *service); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); +void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list); void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); diff --git a/src/or/main.c b/src/or/main.c index a45e64929f..95b0ce6ef7 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3572,7 +3572,7 @@ sandbox_init_filter(void) { smartlist_t *files = smartlist_new(); smartlist_t *dirs = smartlist_new(); - rend_services_add_filenames_to_lists(files, dirs); + hs_service_lists_fnames_for_sandbox(files, dirs); SMARTLIST_FOREACH(files, char *, file_name, { char *tmp_name = NULL; tor_asprintf(&tmp_name, "%s.tmp", file_name); @@ -3581,6 +3581,7 @@ sandbox_init_filter(void) /* steals references */ sandbox_cfg_allow_open_filename(&cfg, file_name); sandbox_cfg_allow_open_filename(&cfg, tmp_name); + tor_free(file_name); }); SMARTLIST_FOREACH(dirs, char *, dir, { /* steals reference */ From 7163ce7f62d6c487193fe43828420ba4fe721b9f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 10 May 2017 13:43:37 -0400 Subject: [PATCH 32/91] hs: Refactor the service exit connection code This commit simply moves the code from the if condition of a rendezvous circuit to a function to handle such a connection. No code was modified _except_ the use or rh.stream_id changed to n_stream->stream_id so we don't have to pass the cell header to the function. This is groundwork for prop224 support which will break down the handle_hs_exit_conn() depending on the version of hidden service the circuit and edge connection is for. Signed-off-by: David Goulet --- src/or/connection_edge.c | 112 +++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index ddcff6aa94..8e447131fd 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3066,6 +3066,64 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } +/** For the given circ and the edge connection n_stream, setup + * the the connection, attach it to the circ and connect it. Return 0 on + * success or END_CIRC_AT_ORIGIN if we can't find the requested hidden + * service port where the caller should close the circuit. */ +static int +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *n_stream) +{ + origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); + log_info(LD_REND,"begin is for rendezvous. configuring stream."); + n_stream->base_.address = tor_strdup("(rendezvous)"); + n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; + n_stream->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(n_stream)); + assert_circuit_ok(circ); + + const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); + if (r < 0) { + log_info(LD_REND,"Didn't find rendezvous service (port %d)", + n_stream->base_.port); + /* Send back reason DONE because we want to make hidden service port + * scanning harder thus instead of returning that the exit policy + * didn't match, which makes it obvious that the port is closed, + * return DONE and kill the circuit. That way, a user (malicious or + * not) needs one circuit per bad port unless it matches the policy of + * the hidden service. */ + relay_send_end_cell_from_edge(n_stream->stream_id, circ, + END_STREAM_REASON_DONE, + origin_circ->cpath->prev); + connection_free(TO_CONN(n_stream)); + + /* Drop the circuit here since it might be someone deliberately + * scanning the hidden service ports. Note that this mitigates port + * scanning by adding more work on the attacker side to successfully + * scan but does not fully solve it. */ + if (r < -1) + return END_CIRC_AT_ORIGIN; + else + return 0; + } + assert_circuit_ok(circ); + log_debug(LD_REND,"Finished assigning addr/port"); + n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ + + /* add it into the linked list of p_streams on this circuit */ + n_stream->next_stream = origin_circ->p_streams; + n_stream->on_circuit = circ; + origin_circ->p_streams = n_stream; + assert_circuit_ok(circ); + + origin_circ->rend_data->nr_streams++; + + connection_exit_connect(n_stream); + + /* For path bias: This circuit was used successfully */ + pathbias_mark_use_success(origin_circ); + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3217,58 +3275,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->deliver_window = STREAMWINDOW_START; if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); - assert_circuit_ok(circ); - - const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); - if (r < 0) { - log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->base_.port); - /* Send back reason DONE because we want to make hidden service port - * scanning harder thus instead of returning that the exit policy - * didn't match, which makes it obvious that the port is closed, - * return DONE and kill the circuit. That way, a user (malicious or - * not) needs one circuit per bad port unless it matches the policy of - * the hidden service. */ - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_DONE, - layer_hint); - connection_free(TO_CONN(n_stream)); - tor_free(address); - - /* Drop the circuit here since it might be someone deliberately - * scanning the hidden service ports. Note that this mitigates port - * scanning by adding more work on the attacker side to successfully - * scan but does not fully solve it. */ - if (r < -1) - return END_CIRC_AT_ORIGIN; - else - return 0; - } - assert_circuit_ok(circ); - log_debug(LD_REND,"Finished assigning addr/port"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; - assert_circuit_ok(circ); - - origin_circ->rend_data->nr_streams++; - - connection_exit_connect(n_stream); - - /* For path bias: This circuit was used successfully */ - pathbias_mark_use_success(origin_circ); - tor_free(address); - return 0; + /* We handle this circuit and stream in this function for all supported + * hidden service version. */ + return handle_hs_exit_conn(circ, n_stream); } tor_strlower(address); n_stream->base_.address = address; From 30b5c6a95ec9932f17f0b171c60229c1d77f829d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 10 May 2017 15:04:40 -0400 Subject: [PATCH 33/91] prop224: Link rendezvous circuit to edge connection This commit refactors the handle_hs_exit_conn() function introduced at a prior commit that connects the rendezvous circuit to the edge connection used to connect to the service virtual port requested in a BEGIN cell. The refactor adds the support for prop224 adding the hs_service_set_conn_addr_port() function that has the same purpose has rend_service_set_connection_addr_port() from the legacy code. The rend_service_set_connection_addr_port() has also been a bit refactored so the common code can be shared between the two HS subsystems (legacy and prop224). In terms of functionallity, nothing has changed, we still close the circuits in case of failure for the same reasons as the legacy system currently does. Signed-off-by: David Goulet --- src/or/connection_edge.c | 89 +++++++++++++++++++++----------- src/or/hs_common.c | 106 +++++++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 2 + src/or/hs_service.c | 85 +++++++++++++++++++++++++++++++ src/or/hs_service.h | 2 + src/or/rendservice.c | 95 ++--------------------------------- 6 files changed, 258 insertions(+), 121 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8e447131fd..41e5f88ab8 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,6 +76,7 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_circuit.h" #include "main.h" #include "nodelist.h" #include "policies.h" @@ -3066,58 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } -/** For the given circ and the edge connection n_stream, setup - * the the connection, attach it to the circ and connect it. Return 0 on - * success or END_CIRC_AT_ORIGIN if we can't find the requested hidden - * service port where the caller should close the circuit. */ +/** For the given circ and the edge connection conn, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ static int -handle_hs_exit_conn(circuit_t *circ, edge_connection_t *n_stream) +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) { - origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); - assert_circuit_ok(circ); + int ret; + origin_circuit_t *origin_circ; - const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); - if (r < 0) { - log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->base_.port); + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); + + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert(0); + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); /* Send back reason DONE because we want to make hidden service port * scanning harder thus instead of returning that the exit policy * didn't match, which makes it obvious that the port is closed, * return DONE and kill the circuit. That way, a user (malicious or * not) needs one circuit per bad port unless it matches the policy of * the hidden service. */ - relay_send_end_cell_from_edge(n_stream->stream_id, circ, + relay_send_end_cell_from_edge(conn->stream_id, circ, END_STREAM_REASON_DONE, origin_circ->cpath->prev); - connection_free(TO_CONN(n_stream)); + connection_free(TO_CONN(conn)); /* Drop the circuit here since it might be someone deliberately * scanning the hidden service ports. Note that this mitigates port * scanning by adding more work on the attacker side to successfully * scan but does not fully solve it. */ - if (r < -1) + if (ret < -1) { return END_CIRC_AT_ORIGIN; - else + } else { return 0; + } } - assert_circuit_ok(circ); - log_debug(LD_REND,"Finished assigning addr/port"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; assert_circuit_ok(circ); - origin_circ->rend_data->nr_streams++; + if (origin_circ->rend_data) { + origin_circ->rend_data->nr_streams++; + } else if (origin_circ->hs_ident) { + origin_circ->hs_ident->num_rdv_streams++; + } else { + /* The previous if/else at the start of the function guarantee that we'll + * never end up in a else situation unless it's freed in between. */ + tor_assert(0); + } - connection_exit_connect(n_stream); + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); /* For path bias: This circuit was used successfully */ pathbias_mark_use_success(origin_circ); diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 4d5417afae..20b53bb91c 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -32,6 +32,60 @@ static const char *str_ed25519_basepoint = "463168356949264781694283940034751631413" "07993866256225615783033603165251855960)"; +#ifdef HAVE_SYS_UN_H + +/** Given ports, a smarlist containing rend_service_port_config_t, + * add the given p, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given conn set it to use the given port p values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* defined(HAVE_SYS_UN_H) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* HAVE_SYS_UN_H */ + /* Helper function: The key is a digest that we compare to a node_t object * current hsdir_index. */ static int @@ -602,6 +656,58 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, crypto_digest_free(digest); } +/* From the given list of hidden service ports, find the matching one from the + * given edge connection conn and set the connection address from the service + * port object. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { + smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } + } + return (chosen_port) ? 0 : -1; +} + /* Using a base32 representation of a service address, parse its content into * the key_out, checksum_out and version_out. Any out variable can be NULL in * case the caller would want only one field. checksum_out MUST at least be 2 diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 695f0b8954..3670ff3790 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -218,6 +218,8 @@ void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, uint64_t time_period_num, int is_next_period, int is_client, smartlist_t *responsible_dirs); +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 760ba1bc3d..293c17f6f1 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2386,6 +2386,91 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) /* Public API */ /* ========== */ +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + /* Add to file_list every filename used by a configured hidden service, and to * dir_list every directory path used by a configured hidden service. This is * used by the sandbox subsystem to whitelist those. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 7d026fb354..f678aa2c0e 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -256,6 +256,8 @@ void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 7353a4f99d..5f4a7c6a79 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -4246,60 +4246,6 @@ rend_service_dump_stats(int severity) } } -#ifdef HAVE_SYS_UN_H - -/** Given ports, a smarlist containing rend_service_port_config_t, - * add the given p, a AF_UNIX port to the list. Return 0 on success - * else return -ENOSYS if AF_UNIX is not supported (see function in the - * #else statement below). */ -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - tor_assert(ports); - tor_assert(p); - tor_assert(p->is_unix_addr); - - smartlist_add(ports, p); - return 0; -} - -/** Given conn set it to use the given port p values. Return 0 - * on success else return -ENOSYS if AF_UNIX is not supported (see function - * in the #else statement below). */ -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - tor_assert(conn); - tor_assert(p); - tor_assert(p->is_unix_addr); - - conn->base_.socket_family = AF_UNIX; - tor_addr_make_unspec(&conn->base_.addr); - conn->base_.port = 1; - conn->base_.address = tor_strdup(p->unix_addr); - return 0; -} - -#else /* defined(HAVE_SYS_UN_H) */ - -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - (void) conn; - (void) p; - return -ENOSYS; -} - -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - (void) ports; - (void) p; - return -ENOSYS; -} - -#endif /* HAVE_SYS_UN_H */ - /** Given conn, a rendezvous exit stream, look up the hidden service for * 'circ', and look up the port and address based on conn-\>port. * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure @@ -4312,9 +4258,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, { rend_service_t *service; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - smartlist_t *matching_ports; - rend_service_port_config_t *chosen_port; - unsigned int warn_once = 0; const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); @@ -4350,41 +4293,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return service->max_streams_close_circuit ? -2 : -1; } } - matching_ports = smartlist_new(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, - { - if (conn->base_.port != p->virtual_port) { - continue; - } - if (!(p->is_unix_addr)) { - smartlist_add(matching_ports, p); - } else { - if (add_unix_port(matching_ports, p)) { - if (!warn_once) { - /* Unix port not supported so warn only once. */ - log_warn(LD_REND, - "Saw AF_UNIX virtual port mapping for port %d on service " - "%s, which is unsupported on this platform. Ignoring it.", - conn->base_.port, serviceid); - } - warn_once++; - } - } - }); - chosen_port = smartlist_choose(matching_ports); - smartlist_free(matching_ports); - if (chosen_port) { - if (!(chosen_port->is_unix_addr)) { - /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ - tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); - conn->base_.port = chosen_port->real_port; - } else { - if (set_unix_port(conn, chosen_port)) { - /* Simply impossible to end up here else we were able to add a Unix - * port without AF_UNIX support... ? */ - tor_assert(0); - } - } + + if (hs_set_conn_addr_port(service->ports, conn) == 0) { + /* Successfully set the port to the connection. We are done. */ return 0; } From 472835d6e94134d7e30e390c4e294bd3a3af5eaa Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 20 Apr 2017 09:58:21 -0400 Subject: [PATCH 34/91] test: Add test_hs_cell unit tests Move ESTABLISH_INTRO tests from test_hs_service.c to this new file. Signed-off-by: David Goulet --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_hs_cell.c | 114 +++++++++++++++++++++++++++++++++++++ src/test/test_hs_service.c | 87 ---------------------------- 5 files changed, 117 insertions(+), 87 deletions(-) create mode 100644 src/test/test_hs_cell.c diff --git a/src/test/include.am b/src/test/include.am index 2e448c8b39..53c723df81 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -115,6 +115,7 @@ src_test_test_SOURCES = \ src/test/test_extorport.c \ src/test/test_hs.c \ src/test/test_hs_config.c \ + src/test/test_hs_cell.c \ src/test/test_hs_service.c \ src/test/test_hs_client.c \ src/test/test_hs_intropoint.c \ diff --git a/src/test/test.c b/src/test/test.c index c5c394900c..2a2d5ba644 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1214,6 +1214,7 @@ struct testgroup_t testgroups[] = { { "extorport/", extorport_tests }, { "legacy_hs/", hs_tests }, { "hs_cache/", hs_cache }, + { "hs_cell/", hs_cell_tests }, { "hs_config/", hs_config_tests }, { "hs_descriptor/", hs_descriptor }, { "hs_service/", hs_service_tests }, diff --git a/src/test/test.h b/src/test/test.h index 9b2a0b842f..b30301d06b 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -207,6 +207,7 @@ extern struct testcase_t guardfraction_tests[]; extern struct testcase_t extorport_tests[]; extern struct testcase_t hs_tests[]; extern struct testcase_t hs_cache[]; +extern struct testcase_t hs_cell_tests[]; extern struct testcase_t hs_config_tests[]; extern struct testcase_t hs_descriptor[]; extern struct testcase_t hs_service_tests[]; diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c new file mode 100644 index 0000000000..0bb8c87355 --- /dev/null +++ b/src/test/test_hs_cell.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_cell.c + * \brief Test hidden service cell functionality. + */ + +#define HS_INTROPOINT_PRIVATE +#define HS_SERVICE_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" + +#include "crypto_ed25519.h" +#include "hs_intropoint.h" +#include "hs_service.h" + +/* Trunnel. */ +#include "hs/cell_establish_intro.h" + +/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we + * parse it from the receiver side. */ +static void +test_gen_establish_intro_cell(void *arg) +{ + (void) arg; + ssize_t retval; + uint8_t circuit_key_material[DIGEST_LEN] = {0}; + uint8_t buf[RELAY_PAYLOAD_SIZE]; + trn_cell_establish_intro_t *cell_out = NULL; + trn_cell_establish_intro_t *cell_in = NULL; + + crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + + /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we + attempt to parse it. */ + { + cell_out = generate_establish_intro_cell(circuit_key_material, + sizeof(circuit_key_material)); + tt_assert(cell_out); + + retval = get_establish_intro_payload(buf, sizeof(buf), cell_out); + tt_int_op(retval, >=, 0); + } + + /* Parse it as the receiver */ + { + ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in, + buf, sizeof(buf)); + tt_int_op(parse_result, >=, 0); + + retval = verify_establish_intro_cell(cell_in, + circuit_key_material, + sizeof(circuit_key_material)); + tt_int_op(retval, >=, 0); + } + + done: + trn_cell_establish_intro_free(cell_out); + trn_cell_establish_intro_free(cell_in); +} + +/* Mocked ed25519_sign_prefixed() function that always fails :) */ +static int +mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t msg_len, + const char *prefix_str, + const ed25519_keypair_t *keypair) { + (void) signature_out; + (void) msg; + (void) msg_len; + (void) prefix_str; + (void) keypair; + return -1; +} + +/** We simulate a failure to create an ESTABLISH_INTRO cell */ +static void +test_gen_establish_intro_cell_bad(void *arg) +{ + (void) arg; + trn_cell_establish_intro_t *cell = NULL; + uint8_t circuit_key_material[DIGEST_LEN] = {0}; + + MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); + + crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + + setup_full_capture_of_logs(LOG_WARN); + /* Easiest way to make that function fail is to mock the + ed25519_sign_prefixed() function and make it fail. */ + cell = generate_establish_intro_cell(circuit_key_material, + sizeof(circuit_key_material)); + expect_log_msg_containing("Unable to gen signature for " + "ESTABLISH_INTRO cell."); + teardown_capture_of_logs(); + tt_assert(!cell); + + done: + trn_cell_establish_intro_free(cell); + UNMOCK(ed25519_sign_prefixed); +} + +struct testcase_t hs_cell_tests[] = { + { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, + NULL, NULL }, + { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index fe4ce42336..277855f759 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -59,89 +59,6 @@ helper_config_service(const char *conf) return ret; } -/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we - * parse it from the receiver side. */ -static void -test_gen_establish_intro_cell(void *arg) -{ - (void) arg; - ssize_t retval; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; - uint8_t buf[RELAY_PAYLOAD_SIZE]; - trn_cell_establish_intro_t *cell_out = NULL; - trn_cell_establish_intro_t *cell_in = NULL; - - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - - /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - { - cell_out = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(cell_out); - - retval = get_establish_intro_payload(buf, sizeof(buf), cell_out); - tt_int_op(retval, >=, 0); - } - - /* Parse it as the receiver */ - { - ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in, - buf, sizeof(buf)); - tt_int_op(parse_result, >=, 0); - - retval = verify_establish_intro_cell(cell_in, - circuit_key_material, - sizeof(circuit_key_material)); - tt_int_op(retval, >=, 0); - } - - done: - trn_cell_establish_intro_free(cell_out); - trn_cell_establish_intro_free(cell_in); -} - -/* Mocked ed25519_sign_prefixed() function that always fails :) */ -static int -mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out, - const uint8_t *msg, size_t msg_len, - const char *prefix_str, - const ed25519_keypair_t *keypair) { - (void) signature_out; - (void) msg; - (void) msg_len; - (void) prefix_str; - (void) keypair; - return -1; -} - -/** We simulate a failure to create an ESTABLISH_INTRO cell */ -static void -test_gen_establish_intro_cell_bad(void *arg) -{ - (void) arg; - trn_cell_establish_intro_t *cell = NULL; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; - - MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); - - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - - setup_full_capture_of_logs(LOG_WARN); - /* Easiest way to make that function fail is to mock the - ed25519_sign_prefixed() function and make it fail. */ - cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - expect_log_msg_containing("Unable to gen signature for " - "ESTABLISH_INTRO cell."); - teardown_capture_of_logs(); - tt_assert(!cell); - - done: - trn_cell_establish_intro_free(cell); - UNMOCK(ed25519_sign_prefixed); -} - /** Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 * cell, and verify the proper derivation of decryption keys on the other end. * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify @@ -601,10 +518,6 @@ test_desc_overlap_period(void *arg) } struct testcase_t hs_service_tests[] = { - { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, - NULL, NULL }, - { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, - NULL, NULL }, { "hs_ntor", test_hs_ntor, TT_FORK, NULL, NULL }, { "time_period", test_time_period, TT_FORK, From 6061f5e2bd8272079e5b72e0d19aa2a6ca342e4e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 20 Apr 2017 10:04:28 -0400 Subject: [PATCH 35/91] test: Add test_hs_ntor unit tests Move the ntor test from test_hs_service.c to this file. Signed-off-by: David Goulet --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_hs_ntor.c | 114 +++++++++++++++++++++++++++++++++++++ src/test/test_hs_service.c | 99 +------------------------------- 5 files changed, 119 insertions(+), 97 deletions(-) create mode 100644 src/test/test_hs_ntor.c diff --git a/src/test/include.am b/src/test/include.am index 53c723df81..5f6753230a 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -116,6 +116,7 @@ src_test_test_SOURCES = \ src/test/test_hs.c \ src/test/test_hs_config.c \ src/test/test_hs_cell.c \ + src/test/test_hs_ntor.c \ src/test/test_hs_service.c \ src/test/test_hs_client.c \ src/test/test_hs_intropoint.c \ diff --git a/src/test/test.c b/src/test/test.c index 2a2d5ba644..716902773e 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1217,6 +1217,7 @@ struct testgroup_t testgroups[] = { { "hs_cell/", hs_cell_tests }, { "hs_config/", hs_config_tests }, { "hs_descriptor/", hs_descriptor }, + { "hs_ntor/", hs_ntor_tests }, { "hs_service/", hs_service_tests }, { "hs_client/", hs_client_tests }, { "hs_intropoint/", hs_intropoint_tests }, diff --git a/src/test/test.h b/src/test/test.h index b30301d06b..f651dc0c24 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -210,6 +210,7 @@ extern struct testcase_t hs_cache[]; extern struct testcase_t hs_cell_tests[]; extern struct testcase_t hs_config_tests[]; extern struct testcase_t hs_descriptor[]; +extern struct testcase_t hs_ntor_tests[]; extern struct testcase_t hs_service_tests[]; extern struct testcase_t hs_client_tests[]; extern struct testcase_t hs_intropoint_tests[]; diff --git a/src/test/test_hs_ntor.c b/src/test/test_hs_ntor.c new file mode 100644 index 0000000000..2544997106 --- /dev/null +++ b/src/test/test_hs_ntor.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_ntor.c + * \brief Test hidden service ntor functionality. + */ + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" + +#include "hs_ntor.h" + +/* Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 + * cell, and verify the proper derivation of decryption keys on the other end. + * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify + * the proper verification on the other end. */ +static void +test_hs_ntor(void *arg) +{ + int retval; + + uint8_t subcredential[DIGEST256_LEN]; + + ed25519_keypair_t service_intro_auth_keypair; + curve25519_keypair_t service_intro_enc_keypair; + curve25519_keypair_t service_ephemeral_rend_keypair; + + curve25519_keypair_t client_ephemeral_enc_keypair; + + hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys; + hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys; + + hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys; + hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys; + + (void) arg; + + /* Generate fake data for this unittest */ + { + /* Generate fake subcredential */ + memset(subcredential, 'Z', DIGEST256_LEN); + + /* service */ + curve25519_keypair_generate(&service_intro_enc_keypair, 0); + ed25519_keypair_generate(&service_intro_auth_keypair, 0); + curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); + /* client */ + curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0); + } + + /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */ + retval = + hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair.pubkey, + &client_ephemeral_enc_keypair, + subcredential, + &client_hs_ntor_intro_cell_keys); + tt_int_op(retval, ==, 0); + + /* Service: Simulate the decryption of the received INTRODUCE1 */ + retval = + hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &client_ephemeral_enc_keypair.pubkey, + subcredential, + &service_hs_ntor_intro_cell_keys); + tt_int_op(retval, ==, 0); + + /* Test that the INTRODUCE1 encryption/mac keys match! */ + tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ, + service_hs_ntor_intro_cell_keys.enc_key, + CIPHER256_KEY_LEN); + tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ, + service_hs_ntor_intro_cell_keys.mac_key, + DIGEST256_LEN); + + /* Service: Simulate creation of RENDEZVOUS1 key material. */ + retval = + hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &service_ephemeral_rend_keypair, + &client_ephemeral_enc_keypair.pubkey, + &service_hs_ntor_rend_cell_keys); + tt_int_op(retval, ==, 0); + + /* Client: Simulate the verification of a received RENDEZVOUS1 cell */ + retval = + hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &client_ephemeral_enc_keypair, + &service_intro_enc_keypair.pubkey, + &service_ephemeral_rend_keypair.pubkey, + &client_hs_ntor_rend_cell_keys); + tt_int_op(retval, ==, 0); + + /* Test that the RENDEZVOUS1 key material match! */ + tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ, + service_hs_ntor_rend_cell_keys.rend_cell_auth_mac, + DIGEST256_LEN); + tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, + service_hs_ntor_rend_cell_keys.ntor_key_seed, + DIGEST256_LEN); + done: + ; +} + +struct testcase_t hs_ntor_tests[] = { + { "hs_ntor", test_hs_ntor, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 277855f759..b132398180 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -59,99 +59,6 @@ helper_config_service(const char *conf) return ret; } -/** Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 - * cell, and verify the proper derivation of decryption keys on the other end. - * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify - * the proper verification on the other end. */ -static void -test_hs_ntor(void *arg) -{ - int retval; - - uint8_t subcredential[DIGEST256_LEN]; - - ed25519_keypair_t service_intro_auth_keypair; - curve25519_keypair_t service_intro_enc_keypair; - curve25519_keypair_t service_ephemeral_rend_keypair; - - curve25519_keypair_t client_ephemeral_enc_keypair; - - hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys; - hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys; - - hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys; - hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys; - - (void) arg; - - /* Generate fake data for this unittest */ - { - /* Generate fake subcredential */ - memset(subcredential, 'Z', DIGEST256_LEN); - - /* service */ - curve25519_keypair_generate(&service_intro_enc_keypair, 0); - ed25519_keypair_generate(&service_intro_auth_keypair, 0); - curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); - /* client */ - curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0); - } - - /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */ - retval = - hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair.pubkey, - &client_ephemeral_enc_keypair, - subcredential, - &client_hs_ntor_intro_cell_keys); - tt_int_op(retval, ==, 0); - - /* Service: Simulate the decryption of the received INTRODUCE1 */ - retval = - hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair, - &client_ephemeral_enc_keypair.pubkey, - subcredential, - &service_hs_ntor_intro_cell_keys); - tt_int_op(retval, ==, 0); - - /* Test that the INTRODUCE1 encryption/mac keys match! */ - tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ, - service_hs_ntor_intro_cell_keys.enc_key, - CIPHER256_KEY_LEN); - tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ, - service_hs_ntor_intro_cell_keys.mac_key, - DIGEST256_LEN); - - /* Service: Simulate creation of RENDEZVOUS1 key material. */ - retval = - hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair, - &service_ephemeral_rend_keypair, - &client_ephemeral_enc_keypair.pubkey, - &service_hs_ntor_rend_cell_keys); - tt_int_op(retval, ==, 0); - - /* Client: Simulate the verification of a received RENDEZVOUS1 cell */ - retval = - hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, - &client_ephemeral_enc_keypair, - &service_intro_enc_keypair.pubkey, - &service_ephemeral_rend_keypair.pubkey, - &client_hs_ntor_rend_cell_keys); - tt_int_op(retval, ==, 0); - - /* Test that the RENDEZVOUS1 key material match! */ - tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ, - service_hs_ntor_rend_cell_keys.rend_cell_auth_mac, - DIGEST256_LEN); - tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, - service_hs_ntor_rend_cell_keys.ntor_key_seed, - DIGEST256_LEN); - done: - ; -} - static void test_validate_address(void *arg) { @@ -518,10 +425,6 @@ test_desc_overlap_period(void *arg) } struct testcase_t hs_service_tests[] = { - { "hs_ntor", test_hs_ntor, TT_FORK, - NULL, NULL }, - { "time_period", test_time_period, TT_FORK, - NULL, NULL }, { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, { "build_address", test_build_address, TT_FORK, @@ -534,6 +437,8 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "desc_overlap_period", test_desc_overlap_period, TT_FORK, NULL, NULL }, + { "time_period", test_time_period, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From 8ffb49422bffd911dbe0b4aea5a59ad589d785c1 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 20 Apr 2017 11:20:02 -0400 Subject: [PATCH 36/91] test: Add test_hs_common unit tests Move tests from test_hs_service.c to this file. Signed-off-by: David Goulet --- src/test/include.am | 1 + src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_hs_common.c | 194 +++++++++++++++++++++++++++++++++++++ src/test/test_hs_service.c | 172 -------------------------------- 5 files changed, 197 insertions(+), 172 deletions(-) create mode 100644 src/test/test_hs_common.c diff --git a/src/test/include.am b/src/test/include.am index 5f6753230a..062dd1e17e 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -114,6 +114,7 @@ src_test_test_SOURCES = \ src/test/test_guardfraction.c \ src/test/test_extorport.c \ src/test/test_hs.c \ + src/test/test_hs_common.c \ src/test/test_hs_config.c \ src/test/test_hs_cell.c \ src/test/test_hs_ntor.c \ diff --git a/src/test/test.c b/src/test/test.c index 716902773e..994a97ab00 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1215,6 +1215,7 @@ struct testgroup_t testgroups[] = { { "legacy_hs/", hs_tests }, { "hs_cache/", hs_cache }, { "hs_cell/", hs_cell_tests }, + { "hs_common/", hs_common_tests }, { "hs_config/", hs_config_tests }, { "hs_descriptor/", hs_descriptor }, { "hs_ntor/", hs_ntor_tests }, diff --git a/src/test/test.h b/src/test/test.h index f651dc0c24..448d253fb5 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -208,6 +208,7 @@ extern struct testcase_t extorport_tests[]; extern struct testcase_t hs_tests[]; extern struct testcase_t hs_cache[]; extern struct testcase_t hs_cell_tests[]; +extern struct testcase_t hs_common_tests[]; extern struct testcase_t hs_config_tests[]; extern struct testcase_t hs_descriptor[]; extern struct testcase_t hs_ntor_tests[]; diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c new file mode 100644 index 0000000000..27bbab8d46 --- /dev/null +++ b/src/test/test_hs_common.c @@ -0,0 +1,194 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_common.c + * \brief Test hidden service common functionalities. + */ + +#define HS_COMMON_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" +#include "hs_test_helpers.h" + +#include "hs_common.h" + +static void +test_validate_address(void *arg) +{ + int ret; + + (void) arg; + + /* Address too short and too long. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid("blah"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + /* Invalid checksum (taken from prop224) */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + /* Non base32 decodable string. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "????????????????????????????????????????????????????????"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("can't be decoded"); + teardown_capture_of_logs(); + + /* Valid address. */ + ret = hs_address_is_valid( + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad"); + tt_int_op(ret, OP_EQ, 1); + + done: + ; +} + +static void +test_build_address(void *arg) +{ + int ret; + char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_public_key_t pubkey; + + (void) arg; + + /* The following has been created with hs_build_address.py script that + * follows proposal 224 specification to build an onion address. */ + static const char *test_addr = + "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid"; + + /* Let's try to build the same onion address that the script can do. Key is + * a long set of very random \x42 :). */ + memset(&pubkey, '\x42', sizeof(pubkey)); + hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); + tt_str_op(test_addr, OP_EQ, onion_addr); + /* Validate that address. */ + ret = hs_address_is_valid(onion_addr); + tt_int_op(ret, OP_EQ, 1); + + done: + ; +} + +/** Test that our HS time period calculation functions work properly */ +static void +test_time_period(void *arg) +{ + (void) arg; + uint64_t tn; + int retval; + time_t fake_time; + + /* Let's do the example in prop224 section [TIME-PERIODS] */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + + /* Check that the time period number is right */ + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16903); + + /* Increase current time to 11:59:59 UTC and check that the time period + number is still the same */ + fake_time += 3599; + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16903); + + /* Now take time to 12:00:00 UTC and check that the time period rotated */ + fake_time += 1; + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, ==, 16904); + + /* Now also check our hs_get_next_time_period_num() function */ + tn = hs_get_next_time_period_num(fake_time); + tt_u64_op(tn, ==, 16905); + + done: + ; +} + +/** Test that our HS overlap period functions work properly. */ +static void +test_desc_overlap_period(void *arg) +{ + (void) arg; + int retval; + time_t now = time(NULL); + networkstatus_t *dummy_consensus = NULL; + + /* First try with a consensus inside the overlap period */ + dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + retval = parse_rfc1123_time("Wed, 13 Apr 2016 10:00:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap + period is still active. */ + dummy_consensus->valid_after += 3600; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it goes to 11:59:59 UTC. Overlap + period is still active. */ + dummy_consensus->valid_after += 3599; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + /* Now increase the valid_after so that it drifts to noon, and check that + overlap mode is not active anymore. */ + dummy_consensus->valid_after += 1; + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + /* Check that overlap mode is also inactive at 23:59:59 UTC */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 23:59:59 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + done: + tor_free(dummy_consensus); +} + +struct testcase_t hs_common_tests[] = { + { "build_address", test_build_address, TT_FORK, + NULL, NULL }, + { "validate_address", test_validate_address, TT_FORK, + NULL, NULL }, + { "time_period", test_time_period, TT_FORK, + NULL, NULL }, + { "desc_overlap_period", test_desc_overlap_period, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index b132398180..d5730f6917 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -59,123 +59,6 @@ helper_config_service(const char *conf) return ret; } -static void -test_validate_address(void *arg) -{ - int ret; - - (void) arg; - - /* Address too short and too long. */ - setup_full_capture_of_logs(LOG_WARN); - ret = hs_address_is_valid("blah"); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg_containing("has an invalid length"); - teardown_capture_of_logs(); - - setup_full_capture_of_logs(LOG_WARN); - ret = hs_address_is_valid( - "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb"); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg_containing("has an invalid length"); - teardown_capture_of_logs(); - - /* Invalid checksum (taken from prop224) */ - setup_full_capture_of_logs(LOG_WARN); - ret = hs_address_is_valid( - "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad"); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg_containing("invalid checksum"); - teardown_capture_of_logs(); - - setup_full_capture_of_logs(LOG_WARN); - ret = hs_address_is_valid( - "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad"); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg_containing("invalid checksum"); - teardown_capture_of_logs(); - - /* Non base32 decodable string. */ - setup_full_capture_of_logs(LOG_WARN); - ret = hs_address_is_valid( - "????????????????????????????????????????????????????????"); - tt_int_op(ret, OP_EQ, 0); - expect_log_msg_containing("can't be decoded"); - teardown_capture_of_logs(); - - /* Valid address. */ - ret = hs_address_is_valid( - "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad"); - tt_int_op(ret, OP_EQ, 1); - - done: - ; -} - -static void -test_build_address(void *arg) -{ - int ret; - char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; - ed25519_public_key_t pubkey; - - (void) arg; - - /* The following has been created with hs_build_address.py script that - * follows proposal 224 specification to build an onion address. */ - static const char *test_addr = - "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid"; - - /* Let's try to build the same onion address that the script can do. Key is - * a long set of very random \x42 :). */ - memset(&pubkey, '\x42', sizeof(pubkey)); - hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); - tt_str_op(test_addr, OP_EQ, onion_addr); - /* Validate that address. */ - ret = hs_address_is_valid(onion_addr); - tt_int_op(ret, OP_EQ, 1); - - done: - ; -} - -/** Test that our HS time period calculation functions work properly */ -static void -test_time_period(void *arg) -{ - (void) arg; - uint64_t tn; - int retval; - time_t fake_time; - - /* Let's do the example in prop224 section [TIME-PERIODS] */ - retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", - &fake_time); - tt_int_op(retval, ==, 0); - - /* Check that the time period number is right */ - tn = hs_get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16903); - - /* Increase current time to 11:59:59 UTC and check that the time period - number is still the same */ - fake_time += 3599; - tn = hs_get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16903); - - /* Now take time to 12:00:00 UTC and check that the time period rotated */ - fake_time += 1; - tn = hs_get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16904); - - /* Now also check our hs_get_next_time_period_num() function */ - tn = hs_get_next_time_period_num(fake_time); - tt_u64_op(tn, ==, 16905); - - done: - ; -} - /* Test: Ensure that setting up rendezvous circuits works correctly. */ static void test_e2e_rend_circuit_setup(void *arg) @@ -377,68 +260,13 @@ test_access_service(void *arg) hs_free_all(); } -/** Test that our HS overlap period functions work properly. */ -static void -test_desc_overlap_period(void *arg) -{ - (void) arg; - int retval; - time_t now = time(NULL); - networkstatus_t *dummy_consensus = NULL; - - /* First try with a consensus inside the overlap period */ - dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); - retval = parse_rfc1123_time("Wed, 13 Apr 2016 10:00:00 UTC", - &dummy_consensus->valid_after); - tt_int_op(retval, ==, 0); - - retval = hs_overlap_mode_is_active(dummy_consensus, now); - tt_int_op(retval, ==, 1); - - /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap - period is still active. */ - dummy_consensus->valid_after += 3600; - retval = hs_overlap_mode_is_active(dummy_consensus, now); - tt_int_op(retval, ==, 1); - - /* Now increase the valid_after so that it goes to 11:59:59 UTC. Overlap - period is still active. */ - dummy_consensus->valid_after += 3599; - retval = hs_overlap_mode_is_active(dummy_consensus, now); - tt_int_op(retval, ==, 1); - - /* Now increase the valid_after so that it drifts to noon, and check that - overlap mode is not active anymore. */ - dummy_consensus->valid_after += 1; - retval = hs_overlap_mode_is_active(dummy_consensus, now); - tt_int_op(retval, ==, 0); - - /* Check that overlap mode is also inactive at 23:59:59 UTC */ - retval = parse_rfc1123_time("Wed, 13 Apr 2016 23:59:59 UTC", - &dummy_consensus->valid_after); - tt_int_op(retval, ==, 0); - retval = hs_overlap_mode_is_active(dummy_consensus, now); - tt_int_op(retval, ==, 0); - - done: - tor_free(dummy_consensus); -} - struct testcase_t hs_service_tests[] = { { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, - { "build_address", test_build_address, TT_FORK, - NULL, NULL }, - { "validate_address", test_validate_address, TT_FORK, - NULL, NULL }, { "load_keys", test_load_keys, TT_FORK, NULL, NULL }, { "access_service", test_access_service, TT_FORK, NULL, NULL }, - { "desc_overlap_period", test_desc_overlap_period, TT_FORK, - NULL, NULL }, - { "time_period", test_time_period, TT_FORK, - NULL, NULL }, END_OF_TESTCASES }; From 559ffd71798765970205d0559c9f5a06dc55cf37 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 28 Apr 2017 13:41:34 -0400 Subject: [PATCH 37/91] test: Refactor HS tests to use the new ESTABLISH_INTRO cell code Signed-off-by: David Goulet --- src/or/hs_service.c | 157 +----------------- src/or/hs_service.h | 15 +- src/test/test_hs_cell.c | 54 ++++--- src/test/test_hs_intropoint.c | 291 +++++++++++++++++++--------------- 4 files changed, 198 insertions(+), 319 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 293c17f6f1..4c7c642e11 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -297,7 +297,7 @@ service_free_all(void) } /* Free a given service intro point object. */ -static void +STATIC void service_intro_point_free(hs_service_intro_point_t *ip) { if (!ip) { @@ -322,7 +322,7 @@ service_intro_point_free_(void *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 * +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; @@ -2747,159 +2747,6 @@ hs_service_free_all(void) service_free_all(); } -/* XXX We don't currently use these functions, apart from generating unittest - data. When we start implementing the service-side support for prop224 we - should revisit these functions and use them. */ - -/** Given an ESTABLISH_INTRO cell, encode it and place its payload in - * buf_out which has size buf_out_len. Return the number of - * bytes written, or a negative integer if there was an error. */ -ssize_t -get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, - const trn_cell_establish_intro_t *cell) -{ - ssize_t bytes_used = 0; - - if (buf_out_len < RELAY_PAYLOAD_SIZE) { - return -1; - } - - bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len, - cell); - return bytes_used; -} - -/* Set the cell extensions of cell. */ -static void -set_trn_cell_extensions(trn_cell_establish_intro_t *cell) -{ - trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new(); - - /* For now, we don't use extensions at all. */ - trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ - trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions); -} - -/** Given the circuit handshake info in circuit_key_material, create and - * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The - * returned cell is allocated on the heap and it's the responsibility of the - * caller to free it. */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len) -{ - trn_cell_establish_intro_t *cell = NULL; - ssize_t encoded_len; - - log_warn(LD_GENERAL, - "Generating ESTABLISH_INTRO cell (key_material_len: %u)", - (unsigned) circuit_key_material_len); - - /* Generate short-term keypair for use in ESTABLISH_INTRO */ - ed25519_keypair_t key_struct; - if (ed25519_keypair_generate(&key_struct, 0) < 0) { - goto err; - } - - cell = trn_cell_establish_intro_new(); - - /* Set AUTH_KEY_TYPE: 2 means ed25519 */ - trn_cell_establish_intro_set_auth_key_type(cell, - HS_INTRO_AUTH_KEY_TYPE_ED25519); - - /* Set AUTH_KEY_LEN field */ - /* Must also set byte-length of AUTH_KEY to match */ - int auth_key_len = ED25519_PUBKEY_LEN; - trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); - trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); - - /* Set AUTH_KEY field */ - uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); - memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len); - - /* No cell extensions needed */ - set_trn_cell_extensions(cell); - - /* Set signature size. - We need to do this up here, because _encode() needs it and we need to call - _encode() to calculate the MAC and signature. - */ - int sig_len = ED25519_SIG_LEN; - trn_cell_establish_intro_set_sig_len(cell, sig_len); - trn_cell_establish_intro_setlen_sig(cell, sig_len); - - /* XXX How to make this process easier and nicer? */ - - /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */ - { - /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive - the MAC from it. */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - uint8_t mac[TRUNNEL_SHA3_256_LEN]; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell."); - goto err; - } - - /* sanity check */ - tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN); - - /* Calculate MAC of all fields before HANDSHAKE_AUTH */ - crypto_mac_sha3_256(mac, sizeof(mac), - circuit_key_material, circuit_key_material_len, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN)); - /* Write the MAC to the cell */ - uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(cell); - memcpy(handshake_ptr, mac, sizeof(mac)); - } - - /* Calculate the cell signature */ - { - /* To calculate the sig we follow the same procedure as above. We first - dump the cell up to the sig, and then calculate the sig */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - ed25519_signature_t sig; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2)."); - goto err; - } - - tor_assert(encoded_len > ED25519_SIG_LEN); - - if (ed25519_sign_prefixed(&sig, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + sizeof(cell->sig_len)), - ESTABLISH_INTRO_SIG_PREFIX, - &key_struct)) { - log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell."); - goto err; - } - - /* And write the signature to the cell */ - uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell); - memcpy(sig_ptr, sig.sig, sig_len); - } - - /* We are done! Return the cell! */ - return cell; - - err: - trn_cell_establish_intro_free(cell); - return NULL; -} - #ifdef TOR_UNIT_TESTS /* Return the global service map size. Only used by unit test. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index f678aa2c0e..fda2ebfc33 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -269,17 +269,6 @@ int hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); -/* These functions are only used by unit tests and we need to expose them else - * hs_service.o ends up with no symbols in libor.a which makes clang throw a - * warning at compile time. See #21825. */ - -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len); -ssize_t -get_establish_intro_payload(uint8_t *buf, size_t buf_len, - const trn_cell_establish_intro_t *cell); - #ifdef HS_SERVICE_PRIVATE #ifdef TOR_UNIT_TESTS @@ -295,6 +284,10 @@ STATIC hs_service_t *find_service(hs_service_ht *map, const ed25519_public_key_t *pk); STATIC void remove_service(hs_service_ht *map, hs_service_t *service); STATIC int register_service(hs_service_ht *map, hs_service_t *service); +STATIC hs_service_intro_point_t *service_intro_point_new( + const extend_info_t *ei, + unsigned int is_legacy); +STATIC void service_intro_point_free(hs_service_intro_point_t *ip); #endif /* TOR_UNIT_TESTS */ diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c index 0bb8c87355..9c963bcb16 100644 --- a/src/test/test_hs_cell.c +++ b/src/test/test_hs_cell.c @@ -14,6 +14,7 @@ #include "log_test_helpers.h" #include "crypto_ed25519.h" +#include "hs_cell.h" #include "hs_intropoint.h" #include "hs_service.h" @@ -26,40 +27,44 @@ static void test_gen_establish_intro_cell(void *arg) { (void) arg; - ssize_t retval; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + ssize_t ret; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t buf[RELAY_PAYLOAD_SIZE]; trn_cell_establish_intro_t *cell_out = NULL; trn_cell_establish_intro_t *cell_in = NULL; - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + crypto_rand(circ_nonce, sizeof(circ_nonce)); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ { - cell_out = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(cell_out); + cell_out = trn_cell_establish_intro_new(); + /* We only need the auth key pair here. */ + hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0); + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ret = hs_cell_build_establish_intro(circ_nonce, ip, buf); + service_intro_point_free(ip); + tt_u64_op(ret, OP_GT, 0); - retval = get_establish_intro_payload(buf, sizeof(buf), cell_out); - tt_int_op(retval, >=, 0); + ret = trn_cell_establish_intro_encode(buf, sizeof(buf), cell_out); + tt_u64_op(ret, OP_GT, 0); } /* Parse it as the receiver */ { - ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in, - buf, sizeof(buf)); - tt_int_op(parse_result, >=, 0); + ret = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf)); + tt_u64_op(ret, OP_GT, 0); - retval = verify_establish_intro_cell(cell_in, - circuit_key_material, - sizeof(circuit_key_material)); - tt_int_op(retval, >=, 0); + ret = verify_establish_intro_cell(cell_in, + (const uint8_t *) circ_nonce, + sizeof(circ_nonce)); + tt_u64_op(ret, OP_EQ, 0); } done: - trn_cell_establish_intro_free(cell_out); trn_cell_establish_intro_free(cell_in); + trn_cell_establish_intro_free(cell_out); } /* Mocked ed25519_sign_prefixed() function that always fails :) */ @@ -81,22 +86,27 @@ static void test_gen_establish_intro_cell_bad(void *arg) { (void) arg; + ssize_t cell_len = 0; trn_cell_establish_intro_t *cell = NULL; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; + hs_service_intro_point_t *ip = NULL; MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + crypto_rand(circ_nonce, sizeof(circ_nonce)); setup_full_capture_of_logs(LOG_WARN); /* Easiest way to make that function fail is to mock the ed25519_sign_prefixed() function and make it fail. */ - cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - expect_log_msg_containing("Unable to gen signature for " + cell = trn_cell_establish_intro_new(); + tt_assert(cell); + ip = service_intro_point_new(NULL, 0); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL); + service_intro_point_free(ip); + expect_log_msg_containing("Unable to make signature for " "ESTABLISH_INTRO cell."); teardown_capture_of_logs(); - tt_assert(!cell); + tt_u64_op(cell_len, OP_EQ, -1); done: trn_cell_establish_intro_free(cell); diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index 076d125ffc..09af10904b 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -17,21 +17,66 @@ #include "log_test_helpers.h" #include "or.h" +#include "circuitlist.h" +#include "circuituse.h" #include "ht.h" +#include "relay.h" +#include "rendservice.h" + +#include "hs_cell.h" +#include "hs_circuitmap.h" +#include "hs_common.h" +#include "hs_intropoint.h" +#include "hs_service.h" /* Trunnel. */ #include "hs/cell_establish_intro.h" #include "hs/cell_introduce1.h" #include "hs/cell_common.h" -#include "hs_service.h" -#include "hs_common.h" -#include "hs_circuitmap.h" -#include "hs_intropoint.h" -#include "circuitlist.h" -#include "circuituse.h" -#include "rendservice.h" -#include "relay.h" +static size_t +new_establish_intro_cell(const char *circ_nonce, + trn_cell_establish_intro_t **cell_out) +{ + ssize_t cell_len = 0; + uint8_t buf[RELAY_PAYLOAD_SIZE] = {0}; + trn_cell_establish_intro_t *cell = NULL; + hs_service_intro_point_t *ip = NULL; + + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf); + tt_u64_op(cell_len, OP_GT, 0); + + cell_len = trn_cell_establish_intro_parse(&cell, buf, sizeof(buf)); + tt_int_op(cell_len, OP_GT, 0); + tt_assert(cell); + *cell_out = cell; + + done: + service_intro_point_free(ip); + return cell_len; +} + +static ssize_t +new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out) +{ + ssize_t cell_len = 0; + hs_service_intro_point_t *ip = NULL; + + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out); + tt_u64_op(cell_len, OP_GT, 0); + + done: + service_intro_point_free(ip); + return cell_len; +} /* Mock function to avoid networking in unittests */ static int @@ -122,29 +167,24 @@ static void test_establish_intro_wrong_purpose(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; + uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; (void)arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - memcpy(intro_circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + memcpy(intro_circ->rend_circ_nonce, circ_nonce, DIGEST_LEN); /* Set a bad circuit purpose!! :) */ circuit_change_purpose(TO_CIRCUIT(intro_circ), CIRCUIT_PURPOSE_INTRO_POINT); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_u64_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); @@ -154,18 +194,16 @@ test_establish_intro_wrong_purpose(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } /* Prepare a circuit for accepting an ESTABLISH_INTRO cell */ static void -helper_prepare_circ_for_intro(or_circuit_t *circ, - uint8_t *circuit_key_material) +helper_prepare_circ_for_intro(or_circuit_t *circ, const char *circ_nonce) { /* Prepare the circuit for the incoming ESTABLISH_INTRO */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR); - memcpy(circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN); + memcpy(circ->rend_circ_nonce, circ_nonce, DIGEST_LEN); } /* Send an empty ESTABLISH_INTRO cell. Should fail. */ @@ -174,17 +212,17 @@ test_establish_intro_wrong_keytype(void *arg) { int retval; or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); - retval = hs_intro_received_establish_intro(intro_circ, (uint8_t*)"", 0); + retval = hs_intro_received_establish_intro(intro_circ, (uint8_t *) "", 0); expect_log_msg_containing("Empty ESTABLISH_INTRO cell."); teardown_capture_of_logs(); tt_int_op(retval, ==, -1); @@ -198,26 +236,21 @@ static void test_establish_intro_wrong_keytype2(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + * attempt to parse it. */ + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_u64_op(cell_len, OP_GT, 0); /* Mutate the auth key type! :) */ cell_body[0] = 42; @@ -230,7 +263,6 @@ test_establish_intro_wrong_keytype2(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -239,26 +271,27 @@ static void test_establish_intro_wrong_mac(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + char circ_nonce[DIGEST_LEN] = {0}; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_u64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle one byte of the MAC. */ uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(establish_intro_cell); + trn_cell_establish_intro_getarray_handshake_mac(cell); handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++; /* We need to resign the payload with that change. */ { @@ -269,26 +302,26 @@ test_establish_intro_wrong_mac(void *arg) retval = ed25519_keypair_generate(&key_struct, 0); tt_int_op(retval, OP_EQ, 0); uint8_t *auth_key_ptr = - trn_cell_establish_intro_getarray_auth_key(establish_intro_cell); + trn_cell_establish_intro_getarray_auth_key(cell); memcpy(auth_key_ptr, key_struct.pubkey.pubkey, ED25519_PUBKEY_LEN); /* Encode payload so we can sign it. */ - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); retval = ed25519_sign_prefixed(&sig, cell_body, cell_len - - (ED25519_SIG_LEN + - sizeof(establish_intro_cell->sig_len)), + (ED25519_SIG_LEN + sizeof(cell->sig_len)), ESTABLISH_INTRO_SIG_PREFIX, &key_struct); tt_int_op(retval, OP_EQ, 0); /* And write the signature to the cell */ uint8_t *sig_ptr = - trn_cell_establish_intro_getarray_sig(establish_intro_cell); - memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len); + trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, cell->sig_len); /* Re-encode with the new signature. */ - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); } /* Receive the cell. Should fail because our MAC is wrong. */ @@ -299,7 +332,7 @@ test_establish_intro_wrong_mac(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -309,32 +342,32 @@ static void test_establish_intro_wrong_auth_key_len(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; size_t bad_auth_key_len = ED25519_PUBKEY_LEN - 1; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_u64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle the auth key length. */ - trn_cell_establish_intro_set_auth_key_len(establish_intro_cell, - bad_auth_key_len); - trn_cell_establish_intro_setlen_auth_key(establish_intro_cell, - bad_auth_key_len); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + trn_cell_establish_intro_set_auth_key_len(cell, bad_auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, bad_auth_key_len); + /* Encode cell. */ + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); @@ -344,7 +377,7 @@ test_establish_intro_wrong_auth_key_len(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -354,30 +387,32 @@ static void test_establish_intro_wrong_sig_len(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; size_t bad_sig_len = ED25519_SIG_LEN - 1; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_u64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle the signature length. */ - trn_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len); - trn_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + trn_cell_establish_intro_set_sig_len(cell, bad_sig_len); + trn_cell_establish_intro_setlen_sig(cell, bad_sig_len); + /* Encode cell. */ + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); @@ -387,7 +422,7 @@ test_establish_intro_wrong_sig_len(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -397,29 +432,24 @@ static void test_establish_intro_wrong_sig(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_u64_op(cell_len, OP_GT, 0); /* Mutate the last byte (signature)! :) */ - cell_body[cell_len-1]++; + cell_body[cell_len - 1]++; /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); @@ -429,7 +459,6 @@ test_establish_intro_wrong_sig(void *arg) tt_int_op(retval, ==, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -439,32 +468,32 @@ static trn_cell_establish_intro_t * helper_establish_intro_v3(or_circuit_t *intro_circ) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; tt_assert(intro_circ); /* Prepare the circuit for the incoming ESTABLISH_INTRO */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_u64_op(cell_len, OP_GT, 0); + tt_assert(cell); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell */ retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); tt_int_op(retval, ==, 0); done: - return establish_intro_cell; + return cell; } /* Helper function: Send a well-formed v2 ESTABLISH_INTRO cell to @@ -476,22 +505,22 @@ helper_establish_intro_v2(or_circuit_t *intro_circ) int retval; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; tt_assert(intro_circ); /* Prepare the circuit for the incoming ESTABLISH_INTRO */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Send legacy establish_intro */ key1 = pk_generate(0); - /* Use old circuit_key_material why not */ + /* Use old circ_nonce why not */ cell_len = rend_service_encode_establish_intro_cell( (char*)cell_body, sizeof(cell_body), key1, - (char *) circuit_key_material); + circ_nonce); tt_int_op(cell_len, >, 0); /* Receive legacy establish_intro */ From b547c5423930a430f70505a12d587735a7c83e1c Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 5 May 2017 14:55:26 -0400 Subject: [PATCH 38/91] test: Add unit test coverage of hs_service.c Signed-off-by: David Goulet --- src/or/hs_common.c | 4 +- src/or/hs_common.h | 3 +- src/or/hs_service.c | 28 +- src/or/hs_service.h | 33 ++ src/test/test_hs_service.c | 956 ++++++++++++++++++++++++++++++++++++- 5 files changed, 1003 insertions(+), 21 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 20b53bb91c..01bd204e11 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -873,8 +873,8 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, /* Return true if overlap mode is active given the date in consensus. If * consensus is NULL, then we use the latest live consensus we can find. */ -int -hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now) +MOCK_IMPL(int, +hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) { struct tm valid_after_tm; diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 3670ff3790..cbf1ac113d 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -198,7 +198,8 @@ uint64_t hs_get_next_time_period_num(time_t now); link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); -int hs_overlap_mode_is_active(const networkstatus_t *consensus, time_t now); +MOCK_DECL(int, hs_overlap_mode_is_active, + (const networkstatus_t *consensus, time_t now)); uint8_t *hs_get_current_srv(uint64_t time_period_num); uint8_t *hs_get_previous_srv(uint64_t time_period_num); diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 4c7c642e11..2a35379664 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -395,7 +395,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) /* 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 +STATIC void service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) { tor_assert(map); @@ -406,7 +406,7 @@ service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) /* For a given service, remove the intro point from that service which will * look in both descriptors. */ -static void +STATIC void service_intro_point_remove(const hs_service_t *service, const hs_service_intro_point_t *ip) { @@ -424,7 +424,7 @@ service_intro_point_remove(const hs_service_t *service, /* For a given service and authentication key, return the intro point or NULL * if not found. This will check both descriptors in the service. */ -static hs_service_intro_point_t * +STATIC hs_service_intro_point_t * service_intro_point_find(const hs_service_t *service, const ed25519_public_key_t *auth_key) { @@ -446,7 +446,7 @@ service_intro_point_find(const hs_service_t *service, /* For a given service and intro point, return the descriptor for which the * intro point is assigned to. NULL is returned if not found. */ -static hs_service_descriptor_t * +STATIC hs_service_descriptor_t * service_desc_find_by_intro(const hs_service_t *service, const hs_service_intro_point_t *ip) { @@ -472,7 +472,7 @@ service_desc_find_by_intro(const hs_service_t *service, * * This is an helper function because we do those lookups often so it's more * convenient to simply call this functions to get all the things at once. */ -static void +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, hs_service_t **service, hs_service_intro_point_t **ip, hs_service_descriptor_t **desc) @@ -525,7 +525,7 @@ get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) /* 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 * +STATIC const node_t * get_node_from_intro_point(const hs_service_intro_point_t *ip) { const hs_desc_link_specifier_t *ls; @@ -952,7 +952,7 @@ service_descriptor_free(hs_service_descriptor_t *desc) } /* Return a newly allocated service descriptor object. */ -static hs_service_descriptor_t * +STATIC hs_service_descriptor_t * service_descriptor_new(void) { hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc)); @@ -1315,7 +1315,7 @@ build_service_descriptor(hs_service_t *service, time_t now, /* Build descriptors for each service if needed. There are conditions to build * a descriptor which are details in the function. */ -static void +STATIC void build_all_descriptors(time_t now) { FOR_EACH_SERVICE_BEGIN(service) { @@ -1518,7 +1518,7 @@ update_service_descriptor(hs_service_t *service, } /* Update descriptors for each service if needed. */ -static void +STATIC void update_all_descriptors(time_t now) { FOR_EACH_SERVICE_BEGIN(service) { @@ -1532,7 +1532,7 @@ update_all_descriptors(time_t now) /* 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 +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, time_t now) { @@ -1642,7 +1642,7 @@ rotate_service_descriptors(hs_service_t *service) * 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 +STATIC void rotate_all_descriptors(time_t now) { FOR_EACH_SERVICE_BEGIN(service) { @@ -1673,7 +1673,7 @@ rotate_all_descriptors(time_t now) /* 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 +STATIC void run_housekeeping_event(time_t now) { /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are @@ -1809,7 +1809,7 @@ get_max_intro_circ_per_period(const hs_service_t *service) /* For the given service, return 1 if the service is allowed to launch more * introduction circuits else 0 if the maximum has been reached for the retry * period of INTRO_CIRC_RETRY_PERIOD. */ -static int +STATIC int can_service_launch_intro_circuit(hs_service_t *service, time_t now) { tor_assert(service); @@ -2069,7 +2069,7 @@ should_service_upload_descriptor(const hs_service_t *service, /* Scheduled event run from the main loop. Try to upload the descriptor for * each service. */ -static void +STATIC void run_upload_descriptor_event(time_t now) { /* v2 services use the same function for descriptor creation and upload so diff --git a/src/or/hs_service.h b/src/or/hs_service.h index fda2ebfc33..dc80622e33 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -15,6 +15,7 @@ #include "hs_common.h" #include "hs_descriptor.h" +#include "hs_ident.h" #include "hs_intropoint.h" /* Trunnel */ @@ -284,10 +285,42 @@ STATIC hs_service_t *find_service(hs_service_ht *map, const ed25519_public_key_t *pk); STATIC void remove_service(hs_service_ht *map, hs_service_t *service); STATIC int register_service(hs_service_ht *map, hs_service_t *service); +/* Service introduction point functions. */ STATIC hs_service_intro_point_t *service_intro_point_new( const extend_info_t *ei, unsigned int is_legacy); STATIC void service_intro_point_free(hs_service_intro_point_t *ip); +STATIC void service_intro_point_add(digest256map_t *map, + hs_service_intro_point_t *ip); +STATIC void service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip); +STATIC hs_service_intro_point_t *service_intro_point_find( + const hs_service_t *service, + const ed25519_public_key_t *auth_key); +STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( + const hs_service_t *service, + const hs_ident_circuit_t *ident); +/* Service descriptor functions. */ +STATIC hs_service_descriptor_t *service_descriptor_new(void); +STATIC hs_service_descriptor_t *service_desc_find_by_intro( + const hs_service_t *service, + const hs_service_intro_point_t *ip); +/* Helper functions. */ +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, + hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc); +STATIC const node_t *get_node_from_intro_point( + const hs_service_intro_point_t *ip); +STATIC int can_service_launch_intro_circuit(hs_service_t *service, + time_t now); +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now); +STATIC void run_housekeeping_event(time_t now); +STATIC void rotate_all_descriptors(time_t now); +STATIC void build_all_descriptors(time_t now); +STATIC void update_all_descriptors(time_t now); +STATIC void run_upload_descriptor_event(time_t now); #endif /* TOR_UNIT_TESTS */ diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index d5730f6917..cb0ae2f455 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -15,24 +15,28 @@ #define HS_SERVICE_PRIVATE #define HS_INTROPOINT_PRIVATE #define MAIN_PRIVATE +#define NETWORKSTATUS_PRIVATE #define TOR_CHANNEL_INTERNAL_ #include "test.h" #include "test_helpers.h" #include "log_test_helpers.h" #include "rend_test_helpers.h" +#include "hs_test_helpers.h" #include "or.h" -#include "channeltls.h" +#include "config.h" #include "circuitbuild.h" #include "circuitlist.h" #include "circuituse.h" -#include "config.h" -#include "connection.h" #include "crypto.h" -#include "hs_circuit.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "relay.h" + #include "hs_common.h" #include "hs_config.h" +#include "hs_circuit.h" #include "hs_ident.h" #include "hs_intropoint.h" #include "hs_ntor.h" @@ -43,6 +47,57 @@ /* Trunnel */ #include "hs/cell_establish_intro.h" +static networkstatus_t mock_ns; + +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return &mock_ns; +} + +/* Mock function because we are not trying to test the close circuit that does + * an awful lot of checks on the circuit object. */ +static void +mock_circuit_mark_for_close(circuit_t *circ, int reason, int line, + const char *file) +{ + (void) circ; + (void) reason; + (void) line; + (void) file; + return; +} + +static int +mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, + uint8_t relay_command, const char *payload, + size_t payload_len, + crypt_path_t *cpath_layer, + const char *filename, int lineno) +{ + (void) stream_id; + (void) circ; + (void) relay_command; + (void) payload; + (void) payload_len; + (void) cpath_layer; + (void) filename; + (void) lineno; + return 0; +} + +/* Mock function that always return true so we can test the descriptor + * creation of the next time period deterministically. */ +static int +mock_hs_overlap_mode_is_active_true(const networkstatus_t *consensus, + time_t now) +{ + (void) consensus; + (void) now; + return 1; +} + /* Helper: from a set of options in conf, configure a service which will add * it to the staging list of the HS subsytem. */ static int @@ -125,6 +180,77 @@ test_e2e_rend_circuit_setup(void *arg) circuit_free(TO_CIRCUIT(or_circ)); } +/* Helper: Return a newly allocated and initialized origin circuit with + * purpose and flags. A default HS identifier is set to an ed25519 + * authentication key for introduction point. */ +static origin_circuit_t * +helper_create_origin_circuit(int purpose, int flags) +{ + origin_circuit_t *circ = NULL; + + circ = origin_circuit_init(purpose, flags); + tt_assert(circ); + circ->cpath = tor_malloc_zero(sizeof(crypt_path_t)); + circ->cpath->magic = CRYPT_PATH_MAGIC; + circ->cpath->state = CPATH_STATE_OPEN; + circ->cpath->package_window = circuit_initial_package_window(); + circ->cpath->deliver_window = CIRCWINDOW_START; + circ->cpath->prev = circ->cpath; + /* Random nonce. */ + crypto_rand(circ->cpath->prev->rend_circ_nonce, DIGEST_LEN); + /* Create a default HS identifier. */ + circ->hs_ident = tor_malloc_zero(sizeof(hs_ident_circuit_t)); + + done: + return circ; +} + +/* Helper: Return a newly allocated service object with the identity keypair + * sets and the current descriptor. Then register it to the global map. + * Caller should us hs_free_all() to free this service or remove it from the + * global map before freeing. */ +static hs_service_t * +helper_create_service(void) +{ + /* Set a service for this circuit. */ + hs_service_t *service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + service->desc_current = service_descriptor_new(); + tt_assert(service->desc_current); + /* Register service to global map. */ + int ret = register_service(get_hs_service_map(), service); + tt_int_op(ret, OP_EQ, 0); + + done: + return service; +} + +/* Helper: Return a newly allocated service intro point with two link + * specifiers, one IPv4 and one legacy ID set to As. */ +static hs_service_intro_point_t * +helper_create_service_ip(void) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + /* Add a first unused link specifier. */ + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = LS_IPV4; + smartlist_add(ip->base.link_specifiers, ls); + /* Add a second link specifier used by a test. */ + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = LS_LEGACY_ID; + memset(ls->u.legacy_id, 'A', sizeof(ls->u.legacy_id)); + smartlist_add(ip->base.link_specifiers, ls); + + done: + return ip; +} + static void test_load_keys(void *arg) { @@ -260,6 +386,808 @@ test_access_service(void *arg) hs_free_all(); } +static void +test_service_intro_point(void *arg) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + (void) arg; + + /* Test simple creation of an object. */ + { + time_t now = time(NULL); + ip = helper_create_service_ip(); + tt_assert(ip); + /* Make sure the authentication keypair is not zeroes. */ + tt_int_op(tor_mem_is_zero((const char *) &ip->auth_key_kp, + sizeof(ed25519_keypair_t)), OP_EQ, 0); + /* The introduce2_max MUST be in that range. */ + tt_u64_op(ip->introduce2_max, OP_GE, + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->introduce2_max, OP_LE, + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS); + /* Time to expire MUST also be in that range. We add 5 seconds because + * there could be a gap between setting now and the time taken in + * service_intro_point_new. On ARM, it can be surprisingly slow... */ + tt_u64_op(ip->time_to_expire, OP_GE, + now + INTRO_POINT_LIFETIME_MIN_SECONDS + 5); + tt_u64_op(ip->time_to_expire, OP_LE, + now + INTRO_POINT_LIFETIME_MAX_SECONDS + 5); + tt_assert(ip->replay_cache); + tt_assert(ip->base.link_specifiers); + /* By default, this is NOT a legacy object. */ + tt_int_op(ip->base.is_only_legacy, OP_EQ, 0); + } + + /* Test functions that uses a service intropoints map with that previously + * created object (non legacy). */ + { + uint8_t garbage[DIGEST256_LEN] = {0}; + hs_service_intro_point_t *query; + + service = hs_service_new(get_options()); + tt_assert(service); + service->desc_current = service_descriptor_new(); + tt_assert(service->desc_current); + /* Add intropoint to descriptor map. */ + service_intro_point_add(service->desc_current->intro_points.map, ip); + query = service_intro_point_find(service, &ip->auth_key_kp.pubkey); + tt_mem_op(query, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + query = service_intro_point_find(service, + (const ed25519_public_key_t *) garbage); + tt_assert(query == NULL); + + /* While at it, can I find the descriptor with the intro point? */ + hs_service_descriptor_t *desc_lookup = + service_desc_find_by_intro(service, ip); + tt_mem_op(service->desc_current, OP_EQ, desc_lookup, + sizeof(hs_service_descriptor_t)); + + /* Remove object from service descriptor and make sure it is out. */ + service_intro_point_remove(service, ip); + query = service_intro_point_find(service, &ip->auth_key_kp.pubkey); + tt_assert(query == NULL); + } + + done: + /* If the test succeed, this object is no longer referenced in the service + * so we can free it without use after free. Else, it might explode because + * it's still in the service descriptor map. */ + service_intro_point_free(ip); + hs_service_free(service); +} + +static node_t mock_node; +static const node_t * +mock_node_get_by_id(const char *digest) +{ + (void) digest; + memset(mock_node.identity, 'A', DIGEST_LEN); + /* Only return the matchin identity of As */ + if (!tor_memcmp(mock_node.identity, digest, DIGEST_LEN)) { + return &mock_node; + } + return NULL; +} + +static void +test_helper_functions(void *arg) +{ + int ret; + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_ident_circuit_t ident; + + (void) arg; + + MOCK(node_get_by_id, mock_node_get_by_id); + + hs_service_init(); + + service = helper_create_service(); + + ip = helper_create_service_ip(); + /* Immediately add the intro point to the service so the free service at the + * end cleans it as well. */ + service_intro_point_add(service->desc_current->intro_points.map, ip); + + /* Setup the circuit identifier. */ + ed25519_pubkey_copy(&ident.intro_auth_pk, &ip->auth_key_kp.pubkey); + ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk); + + /* Testing get_objects_from_ident(). */ + { + hs_service_t *s_lookup = NULL; + hs_service_intro_point_t *ip_lookup = NULL; + hs_service_descriptor_t *desc_lookup = NULL; + + get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup); + tt_mem_op(s_lookup, OP_EQ, service, sizeof(hs_service_t)); + tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + tt_mem_op(desc_lookup, OP_EQ, service->desc_current, + sizeof(hs_service_descriptor_t)); + /* Reset */ + s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL; + + /* NULL parameter should work. */ + get_objects_from_ident(&ident, NULL, &ip_lookup, &desc_lookup); + tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + tt_mem_op(desc_lookup, OP_EQ, service->desc_current, + sizeof(hs_service_descriptor_t)); + /* Reset. */ + s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL; + + /* Break the ident and we should find nothing. */ + memset(&ident, 0, sizeof(ident)); + get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup); + tt_assert(s_lookup == NULL); + tt_assert(ip_lookup == NULL); + tt_assert(desc_lookup == NULL); + } + + /* Testing get_node_from_intro_point() */ + { + const node_t *node = get_node_from_intro_point(ip); + tt_assert(node == &mock_node); + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == LS_LEGACY_ID) { + /* Change legacy id in link specifier which is not the mock node. */ + memset(ls->u.legacy_id, 'B', sizeof(ls->u.legacy_id)); + } + } SMARTLIST_FOREACH_END(ls); + node = get_node_from_intro_point(ip); + tt_assert(node == NULL); + } + + /* Testing can_service_launch_intro_circuit() */ + { + time_t now = time(NULL); + /* Put the start of the retry period back in time, we should be allowed. + * to launch intro circuit. */ + service->state.num_intro_circ_launched = 2; + service->state.intro_circ_retry_started_time = + (now - INTRO_CIRC_RETRY_PERIOD - 1); + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 1); + tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now); + tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0); + /* Call it again, we should still be allowed because we are under + * MAX_INTRO_CIRCS_PER_PERIOD which been set to 0 previously. */ + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 1); + tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now); + tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0); + /* Too many intro circuit launched means we are not allowed. */ + service->state.num_intro_circ_launched = 20; + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 0); + } + + /* Testing intro_point_should_expire(). */ + { + time_t now = time(NULL); + /* Just some basic test of the current state. */ + tt_u64_op(ip->introduce2_max, OP_GE, + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->introduce2_max, OP_LE, + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->time_to_expire, OP_GE, + now + INTRO_POINT_LIFETIME_MIN_SECONDS); + tt_u64_op(ip->time_to_expire, OP_LE, + now + INTRO_POINT_LIFETIME_MAX_SECONDS); + + /* This newly created IP from above shouldn't expire now. */ + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 0); + /* Maximum number of INTRODUCE2 cell reached, it should expire. */ + ip->introduce2_count = INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS + 1; + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 1); + ip->introduce2_count = 0; + /* It should expire if time to expire has been reached. */ + ip->time_to_expire = now - 1000; + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 1); + } + + done: + /* This will free the service and all objects associated to it. */ + hs_service_free_all(); + UNMOCK(node_get_by_id); +} + +static void +test_intro_circuit_opened(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + hs_service_t *service; + origin_circuit_t *circ = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + flags); + + /* No service associated with this circuit. */ + setup_full_capture_of_logs(LOG_WARN); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + { + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + + /* No intro point associated with this circuit. */ + setup_full_capture_of_logs(LOG_WARN); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown introduction point auth key"); + teardown_capture_of_logs(); + } + + /* Set an IP object now for this circuit. */ + { + hs_service_intro_point_t *ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* This one should go all the way. */ + setup_full_capture_of_logs(LOG_INFO); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Introduction circuit 0 established for service"); + teardown_capture_of_logs(); + + done: + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); + UNMOCK(relay_send_command_from_edge_); +} + +static void +test_intro_established(void *arg) +{ + int ret; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + origin_circuit_t *circ = NULL; + hs_service_t *service; + hs_service_intro_point_t *ip = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + flags); + /* Test a wrong purpose. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO; + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose"); + teardown_capture_of_logs(); + + /* Back to normal. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + + /* No service associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* No introduction point associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Introduction circuit established without an " + "intro point object on circuit"); + teardown_capture_of_logs(); + + /* Set an IP object now for this circuit. */ + { + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* Send an empty payload. INTRO_ESTABLISHED cells are basically zeroes. */ + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(ip->circuit_established, OP_EQ, 1); + tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_INTRO); + + done: + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} + +static void +test_rdv_circuit_opened(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ = NULL; + hs_service_t *service; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_CONNECT_REND, flags); + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, REND_COOKIE_LEN); + crypto_rand((char *) circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info)); + + /* No service associated with this circuit. */ + setup_full_capture_of_logs(LOG_WARN); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + /* This should be set to a non zero timestamp. */ + tt_u64_op(TO_CIRCUIT(circ)->timestamp_dirty, OP_NE, 0); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* Should be all good. */ + hs_service_circuit_has_opened(circ); + tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED); + + done: + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); + UNMOCK(relay_send_command_from_edge_); +} + +static void +test_introduce2(void *arg) +{ + int ret; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + origin_circuit_t *circ = NULL; + hs_service_t *service; + hs_service_intro_point_t *ip = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags); + + /* Test a wrong purpose. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose"); + teardown_capture_of_logs(); + + /* Back to normal. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO; + + /* No service associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* No introduction point associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit"); + teardown_capture_of_logs(); + + /* Set an IP object now for this circuit. */ + { + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* This will fail because receiving an INTRODUCE2 cell implies a valid cell + * and then launching circuits so let's not do that and instead test that + * behaviour differently. */ + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + tt_u64_op(ip->introduce2_count, OP_EQ, 0); + + done: + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} + +static void +test_service_event(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + time_t now = time(NULL); + hs_service_t *service; + origin_circuit_t *circ = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + + /* Currently this consists of cleaning invalid intro points. So adding IPs + * here that should get cleaned up. */ + { + hs_service_intro_point_t *ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* This run will remove the IP because we have no circuits nor node_t + * associated with it. */ + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* We'll trigger a removal because we've reached our maximum amount of + * times we should retry a circuit. For this, we need to have a node_t + * that matches the identity of this IP. */ + routerinfo_t ri; + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + /* This triggers a node_t creation. */ + tt_assert(nodelist_set_routerinfo(&ri, NULL)); + ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* No removal but no circuit so this means the IP object will stay in the + * descriptor map so we can retry it. */ + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + ip->circuit_established = 1; /* We'll test that, it MUST be 0 after. */ + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + /* Remove the IP object at once for the next test. */ + ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* Now, we'll create an IP with a registered circuit. The IP object + * shouldn't go away. */ + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + hs_circuitmap_register_intro_circ_v3_service_side( + circ, &ip->auth_key_kp.pubkey); + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + /* We'll mangle the IP object to expire. */ + ip->time_to_expire = now; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + } + + done: + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} + +static void +test_rotate_descriptors(void *arg) +{ + int ret; + time_t now = time(NULL); + hs_service_t *service; + hs_service_descriptor_t *desc_next; + hs_service_intro_point_t *ip; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + /* Setup the valid_after time to be 13:00 UTC, not in overlap period. The + * overlap check doesn't care about the year. */ + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + + /* Create a service with a default descriptor and state. It's added to the + * global map. */ + service = helper_create_service(); + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + + /* Nothing should happen because we are not in the overlap period. */ + rotate_all_descriptors(now); + tt_int_op(service->state.in_overlap_period, OP_EQ, 0); + tt_assert(service->desc_current); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + + /* Entering an overlap period. */ + ret = parse_rfc1123_time("Sat, 26 Oct 1985 01:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + desc_next = service_descriptor_new(); + desc_next->next_upload_time = 42; /* Our marker to recognize it. */ + service->desc_next = desc_next; + /* We should have our state flagged to be in the overlap period, our current + * descriptor cleaned up and the next descriptor becoming the current. */ + rotate_all_descriptors(now); + tt_int_op(service->state.in_overlap_period, OP_EQ, 1); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + tt_assert(service->desc_next == NULL); + /* A second time should do nothing. */ + rotate_all_descriptors(now); + tt_int_op(service->state.in_overlap_period, OP_EQ, 1); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + tt_assert(service->desc_next == NULL); + + /* Going out of the overlap period. */ + ret = parse_rfc1123_time("Sat, 26 Oct 1985 12:00:00 UTC", + &mock_ns.valid_after); + /* This should reset the state and not touch the current descriptor. */ + tt_int_op(ret, OP_EQ, 0); + rotate_all_descriptors(now); + tt_int_op(service->state.in_overlap_period, OP_EQ, 0); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_assert(service->desc_next == NULL); + + done: + hs_free_all(); + UNMOCK(circuit_mark_for_close_); + UNMOCK(networkstatus_get_live_consensus); +} + +static void +test_build_update_descriptors(void *arg) +{ + int ret; + time_t now = time(NULL); + time_t period_num = hs_get_time_period_num(now); + time_t next_period_num = hs_get_next_time_period_num(now); + node_t *node; + hs_service_t *service; + hs_service_intro_point_t *ip_cur, *ip_next; + + (void) arg; + + hs_init(); + MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true); + + /* Create a service without a current descriptor to trigger a build. */ + service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + /* Register service to global map. */ + ret = register_service(get_hs_service_map(), service); + tt_int_op(ret, OP_EQ, 0); + + build_all_descriptors(now); + /* Check *current* descriptor. */ + tt_assert(service->desc_current); + tt_assert(service->desc_current->desc); + tt_assert(service->desc_current->intro_points.map); + tt_u64_op(service->desc_current->time_period_num, OP_EQ, period_num); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0); + + /* Check *next* descriptor. */ + tt_assert(service->desc_next); + tt_assert(service->desc_next->desc); + tt_assert(service->desc_next->intro_points.map); + tt_assert(service->desc_current != service->desc_next); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, next_period_num); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_next->next_upload_time, OP_EQ, 0); + + /* Time to test the update of those descriptors. At first, we have no node + * in the routerlist so this will find NO suitable node for the IPs. */ + setup_full_capture_of_logs(LOG_INFO); + update_all_descriptors(now); + expect_log_msg_containing("Unable to find a suitable node to be an " + "introduction point for service"); + teardown_capture_of_logs(); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + tt_int_op(digest256map_size(service->desc_next->intro_points.map), + OP_EQ, 0); + + /* Now, we'll setup a node_t. */ + { + routerinfo_t ri; + tor_addr_t ipv4_addr; + curve25519_secret_key_t curve25519_secret_key; + + tor_addr_parse(&ipv4_addr, "127.0.0.1"); + ri.addr = tor_addr_to_ipv4h(&ipv4_addr); + ri.or_port = 1337; + ri.purpose = ROUTER_PURPOSE_GENERAL; + /* Ugly yes but we never free the "ri" object so this just makes things + * easier. */ + ri.protocol_list = (char *) "HSDir 1-2"; + ret = curve25519_secret_key_generate(&curve25519_secret_key, 0); + tt_int_op(ret, OP_EQ, 0); + ri.onion_curve25519_pkey = + tor_malloc_zero(sizeof(curve25519_public_key_t)); + curve25519_public_key_generate(ri.onion_curve25519_pkey, + &curve25519_secret_key); + memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + nodelist_set_routerinfo(&ri, NULL); + node = node_get_mutable_by_id(ri.cache_info.identity_digest); + tt_assert(node); + node->is_running = node->is_valid = node->is_fast = node->is_stable = 1; + } + + /* We expect to pick only one intro point from the node above. */ + setup_full_capture_of_logs(LOG_INFO); + update_all_descriptors(now); + tor_free(node->ri->onion_curve25519_pkey); /* Avoid memleak. */ + expect_log_msg_containing("just picked 1 intro points and wanted 3. It " + "currently has 0 intro points. Launching " + "ESTABLISH_INTRO circuit shortly."); + teardown_capture_of_logs(); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + tt_int_op(digest256map_size(service->desc_next->intro_points.map), + OP_EQ, 1); + /* Get the IP object. Because we don't have the auth key of the IP, we can't + * query it so get the first element in the map. */ + { + void *obj = NULL; + const uint8_t *key; + digest256map_iter_t *iter = + digest256map_iter_init(service->desc_current->intro_points.map); + digest256map_iter_get(iter, &key, &obj); + tt_assert(obj); + ip_cur = obj; + /* Get also the IP from the next descriptor. We'll make sure it's not the + * same object as in the current descriptor. */ + iter = digest256map_iter_init(service->desc_next->intro_points.map); + digest256map_iter_get(iter, &key, &obj); + tt_assert(obj); + ip_next = obj; + } + tt_mem_op(ip_cur, OP_NE, ip_next, sizeof(hs_desc_intro_point_t)); + + /* We won't test the service IP object because there is a specific test + * already for this but we'll make sure that the state is coherent.*/ + + /* Three link specifiers are mandatoy so make sure we do have them. */ + tt_int_op(smartlist_len(ip_cur->base.link_specifiers), OP_EQ, 3); + /* Make sure we have a valid encryption keypair generated when we pick an + * intro point in the update process. */ + tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.seckey.secret_key, + CURVE25519_SECKEY_LEN)); + tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.pubkey.public_key, + CURVE25519_PUBKEY_LEN)); + tt_u64_op(ip_cur->time_to_expire, OP_GE, now + + INTRO_POINT_LIFETIME_MIN_SECONDS); + tt_u64_op(ip_cur->time_to_expire, OP_LE, now + + INTRO_POINT_LIFETIME_MAX_SECONDS); + + done: + hs_free_all(); + nodelist_free_all(); + UNMOCK(hs_overlap_mode_is_active); +} + +static void +test_upload_desctriptors(void *arg) +{ + int ret; + time_t now = time(NULL); + hs_service_t *service; + hs_service_intro_point_t *ip; + + (void) arg; + + hs_init(); + MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true); + + /* Create a service with no descriptor. It's added to the global map. */ + service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + /* Register service to global map. */ + ret = register_service(get_hs_service_map(), service); + tt_int_op(ret, OP_EQ, 0); + /* But first, build our descriptor. */ + build_all_descriptors(now); + + /* Nothing should happen because we have 0 introduction circuit established + * and we want (by default) 3 intro points. */ + run_upload_descriptor_event(now); + /* If no upload happened, this should be untouched. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0); + /* We'll simulate that we've opened our intro point circuit and that we only + * want one intro point. */ + service->config.num_intro_points = 1; + + /* Set our next upload time after now which will skip the upload. */ + service->desc_current->next_upload_time = now + 1000; + run_upload_descriptor_event(now); + /* If no upload happened, this should be untouched. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000); + + /* Set our upload time in the past so we trigger an upload. */ + service->desc_current->next_upload_time = now - 1000; + service->desc_next->next_upload_time = now - 1000; + ip = helper_create_service_ip(); + ip->circuit_established = 1; + service_intro_point_add(service->desc_current->intro_points.map, ip); + + setup_full_capture_of_logs(LOG_WARN); + run_upload_descriptor_event(now); + expect_log_msg_containing("No valid consensus so we can't get the"); + teardown_capture_of_logs(); + tt_u64_op(service->desc_current->next_upload_time, OP_GE, + now + HS_SERVICE_NEXT_UPLOAD_TIME_MIN); + tt_u64_op(service->desc_current->next_upload_time, OP_LE, + now + HS_SERVICE_NEXT_UPLOAD_TIME_MAX); + + done: + hs_free_all(); + UNMOCK(hs_overlap_mode_is_active); +} + struct testcase_t hs_service_tests[] = { { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, @@ -267,6 +1195,26 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "access_service", test_access_service, TT_FORK, NULL, NULL }, + { "service_intro_point", test_service_intro_point, TT_FORK, + NULL, NULL }, + { "helper_functions", test_helper_functions, TT_FORK, + NULL, NULL }, + { "intro_circuit_opened", test_intro_circuit_opened, TT_FORK, + NULL, NULL }, + { "intro_established", test_intro_established, TT_FORK, + NULL, NULL }, + { "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK, + NULL, NULL }, + { "introduce2", test_introduce2, TT_FORK, + NULL, NULL }, + { "service_event", test_service_event, TT_FORK, + NULL, NULL }, + { "rotate_descriptors", test_rotate_descriptors, TT_FORK, + NULL, NULL }, + { "build_update_descriptors", test_build_update_descriptors, TT_FORK, + NULL, NULL }, + { "upload_desctriptors", test_upload_desctriptors, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From a6b6227b2141f8d9d36f8555253ec4d56f423b04 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 1 Jun 2017 15:11:03 +0300 Subject: [PATCH 39/91] test: Fix prop224 HS descriptor to use subcredential We used to use NULL subcredential which is a terrible terrible idea. Refactor HS unittests to use subcredentials. Also add some non-fatal asserts to make sure that we always use subcredentials when decoding/encoding descs. Signed-off-by: David Goulet --- src/or/hs_descriptor.c | 14 +++++++++++--- src/test/hs_test_helpers.c | 27 +++++++++++++++++++++++---- src/test/hs_test_helpers.h | 3 +++ src/test/test_hs_cache.c | 8 ++++++-- src/test/test_hs_descriptor.c | 12 +++++++++--- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 5a230759a4..6f304d6d2d 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -1006,6 +1006,11 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(encoded_out); tor_assert(desc->plaintext_data.version == 3); + if (BUG(desc->subcredential == NULL)) { + log_warn(LD_GENERAL, "Asked to encode desc with no subcred. No!"); + goto err; + } + /* Build the non-encrypted values. */ { char *encoded_cert; @@ -2261,7 +2266,7 @@ hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, hs_descriptor_t **desc_out) { - int ret; + int ret = -1; hs_descriptor_t *desc; tor_assert(encoded); @@ -2269,10 +2274,13 @@ hs_desc_decode_descriptor(const char *encoded, desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Subcredentials are optional. */ - if (subcredential) { - memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + if (BUG(!subcredential)) { + log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); + goto err; } + memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); if (ret < 0) { goto err; diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index 24d4a7e91a..2753d29078 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -6,6 +6,7 @@ #include "test.h" #include "torcert.h" +#include "hs_common.h" #include "hs_test_helpers.h" hs_desc_intro_point_t * @@ -93,8 +94,7 @@ static hs_descriptor_t * hs_helper_build_hs_desc_impl(unsigned int no_ip, const ed25519_keypair_t *signing_kp) { - int ret; - time_t now = time(NULL); + time_t now = approx_time(); ed25519_keypair_t blinded_kp; hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); @@ -104,8 +104,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, memcpy(&desc->plaintext_data.signing_pubkey, &signing_kp->pubkey, sizeof(ed25519_public_key_t)); - ret = ed25519_keypair_generate(&blinded_kp, 0); - tt_int_op(ret, ==, 0); + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + hs_build_blinded_keypair(signing_kp, NULL, 0, + current_time_period, &blinded_kp); /* Copy only the public key into the descriptor. */ memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey, sizeof(ed25519_public_key_t)); @@ -118,6 +119,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, desc->plaintext_data.revision_counter = 42; desc->plaintext_data.lifetime_sec = 3 * 60 * 60; + hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, + desc->subcredential); + /* Setup encrypted data section. */ desc->encrypted_data.create2_ntor = 1; desc->encrypted_data.intro_auth_types = smartlist_new(); @@ -141,6 +145,21 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, return descp; } +/** Helper function to get the HS subcredential using the identity keypair of + * an HS. Used to decrypt descriptors in unittests. */ +void +hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, + uint8_t *subcred_out) +{ + ed25519_keypair_t blinded_kp; + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + hs_build_blinded_keypair(signing_kp, NULL, 0, + current_time_period, &blinded_kp); + + hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, + subcred_out); +} + /* Build a descriptor with introduction points. */ hs_descriptor_t * hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp) diff --git a/src/test/hs_test_helpers.h b/src/test/hs_test_helpers.h index a7fedab136..05f5aa7b64 100644 --- a/src/test/hs_test_helpers.h +++ b/src/test/hs_test_helpers.h @@ -17,6 +17,9 @@ hs_descriptor_t *hs_helper_build_hs_desc_with_ip( const ed25519_keypair_t *signing_kp); void hs_helper_desc_equal(const hs_descriptor_t *desc1, const hs_descriptor_t *desc2); +void +hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, + uint8_t *subcred_out); #endif /* TOR_HS_TEST_HELPERS_H */ diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index 40f50b322a..6c2addef9a 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -342,6 +342,7 @@ test_hsdir_revision_counter_check(void *arg) hs_descriptor_t *published_desc = NULL; char *published_desc_str = NULL; + uint8_t subcredential[DIGEST256_LEN]; char *received_desc_str = NULL; hs_descriptor_t *received_desc = NULL; @@ -378,9 +379,11 @@ test_hsdir_revision_counter_check(void *arg) const ed25519_public_key_t *blinded_key; blinded_key = &published_desc->plaintext_data.blinded_pubkey; + hs_get_subcredential(&signing_kp.pubkey, blinded_key, subcredential); received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); - retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc); + retval = hs_desc_decode_descriptor(received_desc_str, + subcredential, &received_desc); tt_int_op(retval, ==, 0); tt_assert(received_desc); @@ -412,7 +415,8 @@ test_hsdir_revision_counter_check(void *arg) blinded_key = &published_desc->plaintext_data.blinded_pubkey; received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); - retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc); + retval = hs_desc_decode_descriptor(received_desc_str, + subcredential, &received_desc); tt_int_op(retval, ==, 0); tt_assert(received_desc); diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index d83f5e4c61..77bdd4be5e 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -296,6 +296,7 @@ test_decode_descriptor(void *arg) hs_descriptor_t *desc = NULL; hs_descriptor_t *decoded = NULL; hs_descriptor_t *desc_no_ip = NULL; + uint8_t subcredential[DIGEST256_LEN]; (void) arg; @@ -303,15 +304,18 @@ test_decode_descriptor(void *arg) tt_int_op(ret, ==, 0); desc = hs_helper_build_hs_desc_with_ip(&signing_kp); + hs_helper_get_subcred_from_identity_keypair(&signing_kp, + subcredential); + /* Give some bad stuff to the decoding function. */ - ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded); + ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded); tt_int_op(ret, OP_EQ, -1); ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); tt_int_op(ret, ==, 0); tt_assert(encoded); - ret = hs_desc_decode_descriptor(encoded, NULL, &decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); tt_int_op(ret, ==, 0); tt_assert(decoded); @@ -322,6 +326,8 @@ test_decode_descriptor(void *arg) ed25519_keypair_t signing_kp_no_ip; ret = ed25519_keypair_generate(&signing_kp_no_ip, 0); tt_int_op(ret, ==, 0); + hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip, + subcredential); desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip); tt_assert(desc_no_ip); tor_free(encoded); @@ -329,7 +335,7 @@ test_decode_descriptor(void *arg) tt_int_op(ret, ==, 0); tt_assert(encoded); hs_descriptor_free(decoded); - ret = hs_desc_decode_descriptor(encoded, NULL, &decoded); + ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); tt_int_op(ret, ==, 0); tt_assert(decoded); } From 4a8cf17897ca23f8352a27c1bffb6ebfd68a1e0e Mon Sep 17 00:00:00 2001 From: David Goulet Date: Mon, 10 Jul 2017 11:31:51 -0400 Subject: [PATCH 40/91] hs: Rename num_rend_services() Renamed to rend_num_services() so it is consistent with the legacy naming. Signed-off-by: David Goulet --- src/or/circuituse.c | 2 +- src/or/hs_service.c | 6 +++--- src/or/rendservice.c | 2 +- src/or/rendservice.h | 2 +- src/test/test_hs_config.c | 2 +- src/test/test_hs_service.c | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 4d450f1147..247c5bbbb0 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1115,7 +1115,7 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) STATIC int needs_hs_server_circuits(int num_uptime_internal) { - return (num_rend_services() && + return (rend_num_services() && num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 2a35379664..9f496b6151 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1864,7 +1864,7 @@ run_build_circuit_event(time_t now) } /* Run v2 check. */ - if (num_rend_services() > 0) { + if (rend_num_services() > 0) { rend_consider_services_intro_points(now); } @@ -2074,7 +2074,7 @@ run_upload_descriptor_event(time_t now) { /* v2 services use the same function for descriptor creation and upload so * we do everything here because the intro circuits were checked before. */ - if (num_rend_services() > 0) { + if (rend_num_services() > 0) { rend_consider_services_upload(now); rend_consider_descriptor_republication(); } @@ -2605,7 +2605,7 @@ int hs_service_load_all_keys(void) { /* Load v2 service keys if we have v2. */ - if (num_rend_services() != 0) { + if (rend_num_services() != 0) { if (rend_service_load_all_keys(NULL) < 0) { goto err; } diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 5f4a7c6a79..7c4c6edc42 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -148,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s) /** Return the number of rendezvous services we have configured. */ int -num_rend_services(void) +rend_num_services(void) { if (!rend_service_list) return 0; diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 819dfc176e..ed1044f04a 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -135,7 +135,7 @@ STATIC void rend_service_prune_list_impl_(void); #endif /* RENDSERVICE_PRIVATE */ -int num_rend_services(void); +int rend_num_services(void); int rend_config_service(const config_line_t *line_, const or_options_t *options, hs_service_config_t *config); diff --git a/src/test/test_hs_config.c b/src/test/test_hs_config.c index 4d4bbb8891..2a85a7db07 100644 --- a/src/test/test_hs_config.c +++ b/src/test/test_hs_config.c @@ -453,7 +453,7 @@ test_staging_service_v3(void *arg) /* Ok, we have a service in our map! Registration went well. */ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); /* Make sure we don't have a magic v2 service out of this. */ - tt_int_op(num_rend_services(), OP_EQ, 0); + tt_int_op(rend_num_services(), OP_EQ, 0); done: hs_free_all(); diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index cb0ae2f455..1b8d8252eb 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -279,7 +279,7 @@ test_load_keys(void *arg) tt_int_op(ret, OP_EQ, 0); /* This one should now be registered into the v2 list. */ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 0); - tt_int_op(num_rend_services(), OP_EQ, 1); + tt_int_op(rend_num_services(), OP_EQ, 1); /* v3 service. */ tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE); From 15864a1b70c1061a89f97a2054b1c19788af7dbc Mon Sep 17 00:00:00 2001 From: David Goulet Date: Thu, 25 May 2017 10:28:00 -0400 Subject: [PATCH 41/91] prop224: Add a circuit has closed callback When the circuit is about to be freed which has been marked close before, for introduction circuit we now call this has_closed() callback so we can cleanup any introduction point that have retried to many times or at least flag them that their circuit is not established anymore. Signed-off-by: David Goulet --- src/or/circuitlist.c | 7 +++++ src/or/hs_service.c | 64 ++++++++++++++++++++++++++++++++++---------- src/or/hs_service.h | 2 ++ 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 39ebeb5f3d..d891c89f38 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1949,6 +1949,13 @@ circuit_about_to_free(circuit_t *circ) orig_reason); } + /* Notify the HS subsystem for any intro point circuit closing so it can be + * dealt with cleanly. */ + if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { + hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + } + if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); int timed_out = (reason == END_CIRC_REASON_TIMEOUT); diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 9f496b6151..0b524a3a8d 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1584,18 +1584,10 @@ cleanup_intro_points(hs_service_t *service, time_t now) * 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)) { + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { /* Remove intro point from descriptor map. We'll add it to the failed * map if we retried it too many times. */ MAP_DEL_CURRENT(key); - - /* We've retried too many times, remember it has a failed intro point - * so we don't pick it up again. It will be retried in - * INTRO_CIRC_RETRY_PERIOD seconds. */ - if (ip->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES) { - remember_failing_intro_point(ip, desc, now); - } service_intro_point_free(ip); /* XXX: Legacy code does NOT do that, it keeps the circuit open until @@ -1611,11 +1603,6 @@ cleanup_intro_points(hs_service_t *service, time_t now) } 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; } @@ -2386,6 +2373,55 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) /* Public API */ /* ========== */ +/* Called once an introduction circuit is closed. If the circuit doesn't have + * a v3 identifier, it is ignored. */ +void +hs_service_intro_circ_has_closed(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + if (circ->hs_ident == NULL) { + /* This is not a v3 circuit, ignore. */ + goto end; + } + + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on intro circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + goto end; + } + if (ip == NULL) { + /* The introduction point object has already been removed probably by our + * cleanup process so ignore. */ + goto end; + } + /* Can't have an intro point object without a descriptor. */ + tor_assert(desc); + + /* 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; + + /* We've retried too many times, remember it as a failed intro point so we + * don't pick it up again. It will be retried in INTRO_CIRC_RETRY_PERIOD + * seconds. */ + if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, approx_time()); + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + end: + return; +} + /* Given conn, a rendezvous edge connection acting as an exit stream, look up * the hidden service for the circuit circ, and look up the port and address * based on the connection port. Assign the actual connection address. diff --git a/src/or/hs_service.h b/src/or/hs_service.h index dc80622e33..bc29c3d82b 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -270,6 +270,8 @@ int hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +void hs_service_intro_circ_has_closed(origin_circuit_t *circ); + #ifdef HS_SERVICE_PRIVATE #ifdef TOR_UNIT_TESTS From 1b403a83821d86ac358e49ae24bd1284ed0dcfab Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 26 May 2017 14:20:00 -0400 Subject: [PATCH 42/91] prop224: Different intro point timings with TestingNetwork Change the timing for intro point's lifetime and maximum amount of circuit we are allowed to launch in a TestingNetwork. This is particurlarly useful for chutney testing to test intro point rotation. Signed-off-by: David Goulet --- src/or/hs_service.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 0b524a3a8d..d717b9ce2f 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -232,6 +232,11 @@ get_intro_point_max_introduce2(void) static int32_t get_intro_point_min_lifetime(void) { +#define MIN_INTRO_POINT_LIFETIME_TESTING 10 + if (get_options()->TestingTorNetwork) { + return MIN_INTRO_POINT_LIFETIME_TESTING; + } + /* The [0, 2147483647] range is quite large to accomodate anything we decide * in the future. */ return networkstatus_get_param(NULL, "hs_intro_min_lifetime", @@ -244,6 +249,11 @@ get_intro_point_min_lifetime(void) static int32_t get_intro_point_max_lifetime(void) { +#define MAX_INTRO_POINT_LIFETIME_TESTING 30 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_LIFETIME_TESTING; + } + /* The [0, 2147483647] range is quite large to accomodate anything we decide * in the future. */ return networkstatus_get_param(NULL, "hs_intro_max_lifetime", @@ -1771,6 +1781,13 @@ get_max_intro_circ_per_period(const hs_service_t *service) tor_assert(service->config.num_intro_points <= HS_CONFIG_V3_MAX_INTRO_POINTS); +/* For a testing network, allow to do it for the maximum amount so circuit + * creation and rotation and so on can actually be tested without limit. */ +#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING; + } + num_wanted_ip = service->config.num_intro_points; /* The calculation is as follow. We have a number of intro points that we From 713eb08bc9582b49e8073122fb68c3fac5bae188 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 30 May 2017 16:11:59 -0400 Subject: [PATCH 43/91] prop224: Add service rendezvous circuit relaunch This introduces a callback to relaunch a service rendezvous circuit when a previous one failed to build or expired. It unifies the legacy function rend_service_relaunch_rendezvous() with one for specific to prop224. There is now only one entry point for that which is hs_circ_retry_service_rendezvous_point() supporting both legacy and prop224 circuits. Signed-off-by: David Goulet --- src/or/circuituse.c | 5 +- src/or/hs_circuit.c | 121 +++++++++++++++++++++++++++++++++++++++++++ src/or/hs_circuit.h | 1 + src/or/hs_ident.c | 10 ++++ src/or/hs_ident.h | 1 + src/or/rendservice.c | 23 -------- 6 files changed, 136 insertions(+), 25 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 247c5bbbb0..b19f9ed46f 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -43,6 +43,7 @@ #include "entrynodes.h" #include "hs_common.h" #include "hs_client.h" +#include "hs_circuit.h" #include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" @@ -782,7 +783,7 @@ circuit_expire_building(void) victim->state, circuit_state_to_string(victim->state), victim->purpose); TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1; - rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim)); + hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim)); continue; } @@ -1790,7 +1791,7 @@ circuit_build_failed(origin_circuit_t *circ) "(%s hop failed).", escaped(build_state_get_exit_nickname(circ->build_state)), failed_at_last_hop?"last":"non-last"); - rend_service_relaunch_rendezvous(circ); + hs_circ_retry_service_rendezvous_point(circ); break; /* default: * This won't happen in normal operation, but might happen if the diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index d9e96c6330..ce85229470 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -566,10 +566,131 @@ launch_rendezvous_point_circuit(const hs_service_t *service, extend_info_free(info); } +/* Return true iff the given service rendezvous circuit circ is allowed for a + * relaunch to the rendezvous point. */ +static int +can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* XXX: Retrying under certain condition. This is related to #22455. */ + + /* Avoid to relaunch twice a circuit to the same rendezvous point at the + * same time. */ + if (circ->hs_service_side_rend_circ_has_been_relaunched) { + log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " + "Skipping retry.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + goto disallow; + } + + /* A failure count that has reached maximum allowed or circuit that expired, + * we skip relaunching. */ + if (circ->build_state->failure_count > MAX_REND_FAILURES || + circ->build_state->expiry_time <= time(NULL)) { + log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " + "failed with %d attempts and expiry time %ld. " + "Giving up building.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit)), + circ->build_state->failure_count, + circ->build_state->expiry_time); + goto disallow; + } + + /* Allowed to relaunch. */ + return 1; + disallow: + return 0; +} + +/* Retry the rendezvous point of circ by launching a new circuit to it. */ +static void +retry_service_rendezvous_point(const origin_circuit_t *circ) +{ + int flags = 0; + origin_circuit_t *new_circ; + cpath_build_state_t *bstate; + + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Ease our life. */ + bstate = circ->build_state; + + log_info(LD_REND, "Retrying rendezvous point circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + + /* Get the current build state flags for the next circuit. */ + flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; + flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; + flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; + + /* We do NOT add the onehop tunnel flag even though it might be a single + * onion service. The reason is that if we failed once to connect to the RP + * with a direct connection, we consider that chances are that we will fail + * again so try a 3-hop circuit and hope for the best. Because the service + * has no anonymity (single onion), this change of behavior won't affect + * security directly. */ + + /* Help predict this next time */ + rep_hist_note_used_internal(time(NULL), bstate->need_uptime, + bstate->need_capacity); + + new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, + bstate->chosen_exit, flags); + if (new_circ == NULL) { + log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + goto done; + } + + /* Transfer build state information to the new circuit state in part to + * catch any other failures. */ + new_circ->build_state->failure_count = bstate->failure_count++; + new_circ->build_state->expiry_time = bstate->expiry_time; + new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); + + done: + return; +} + /* ========== */ /* Public API */ /* ========== */ +/* Called when we fail building a rendezvous circuit at some point other than + * the last hop: launches a new circuit to the same rendezvous point. This + * supports legacy service. */ +void +hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Check if we are allowed to relaunch to the rendezvous point of circ. */ + if (!can_relaunch_service_rendezvous_point(circ)) { + goto done; + } + + /* Flag the circuit that we are relaunching so to avoid to relaunch twice a + * circuit to the same rendezvous point at the same time. */ + circ->hs_service_side_rend_circ_has_been_relaunched = 1; + + /* Legacy service don't have an hidden service ident. */ + (circ->hs_ident) ? retry_service_rendezvous_point(circ) : + rend_service_relaunch_rendezvous(circ); + + done: + return; +} + /* For a given service and a service intro point, launch a circuit to the * extend info ei. If the service is a single onion, a one-hop circuit will be * requested. Return 0 if the circuit was successfully launched and tagged diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index ca8f1b2f6a..8301dea227 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -28,6 +28,7 @@ int hs_circ_launch_intro_point(hs_service_t *service, int hs_circ_launch_rendezvous_point(const hs_service_t *service, const curve25519_public_key_t *onion_key, const uint8_t *rendezvous_cookie); +void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); /* Cell API. */ int hs_circ_handle_intro_established(const hs_service_t *service, diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c index c3f789db56..e69350d82e 100644 --- a/src/or/hs_ident.c +++ b/src/or/hs_ident.c @@ -34,6 +34,16 @@ hs_ident_circuit_free(hs_ident_circuit_t *ident) tor_free(ident); } +/* For a given circuit identifier src, return a newly allocated copy of it. + * This can't fail. */ +hs_ident_circuit_t * +hs_ident_circuit_dup(const hs_ident_circuit_t *src) +{ + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + /* For a given directory connection identifier src, return a newly allocated * copy of it. This can't fail. */ hs_ident_dir_conn_t * diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h index ca1fa3d707..a3ebd07da4 100644 --- a/src/or/hs_ident.h +++ b/src/or/hs_ident.h @@ -113,6 +113,7 @@ hs_ident_circuit_t *hs_ident_circuit_new( const ed25519_public_key_t *identity_pk, hs_ident_circuit_type_t circuit_type); void hs_ident_circuit_free(hs_ident_circuit_t *ident); +hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src); /* Directory connection identifier API. */ hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 7c4c6edc42..a205b00c6b 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -2892,29 +2892,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); - /* Don't relaunch the same rend circ twice. */ - if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) { - log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; " - "not relaunching it again.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1; - - if (!oldcirc->build_state || - oldcirc->build_state->failure_count > MAX_REND_FAILURES || - oldcirc->build_state->expiry_time < time(NULL)) { - log_info(LD_REND, - "Attempt to build circuit to %s for rendezvous has failed " - "too many times or expired; giving up.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldstate = oldcirc->build_state; tor_assert(oldstate); From 6c3d525c361adac1ad769f87a343fe3e9d2b050b Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 11 Jul 2017 11:18:23 -0400 Subject: [PATCH 44/91] prop224: Make circuit prediction aware of v3 services Signed-off-by: David Goulet --- src/or/circuituse.c | 2 +- src/or/hs_service.c | 10 ++++++++++ src/or/hs_service.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index b19f9ed46f..5292dc01db 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1116,7 +1116,7 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) STATIC int needs_hs_server_circuits(int num_uptime_internal) { - return (rend_num_services() && + return ((rend_num_services() || hs_service_get_num_services()) && num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index d717b9ce2f..a20de94b11 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2390,6 +2390,16 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) /* Public API */ /* ========== */ +/* Return the number of service we have configured and usable. */ +unsigned int +hs_service_get_num_services(void) +{ + if (hs_service_map == NULL) { + return 0; + } + return HT_SIZE(hs_service_map); +} + /* Called once an introduction circuit is closed. If the circuit doesn't have * a v3 identifier, it is ignored. */ void diff --git a/src/or/hs_service.h b/src/or/hs_service.h index bc29c3d82b..f46c4f51a6 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -253,6 +253,7 @@ void hs_service_free_all(void); hs_service_t *hs_service_new(const or_options_t *options); void hs_service_free(hs_service_t *service); +unsigned int hs_service_get_num_services(void); void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, From 2cae4f41006cd1885c33870232a040f98ffd6597 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 19 Jul 2017 11:42:04 -0400 Subject: [PATCH 45/91] prop224: Move get_intro_circuit() to hs_circuit.c Make this function public so we can use it both in hs_circuit.c and hs_service.c to avoid code duplication. Signed-off-by: David Goulet --- src/or/hs_circuit.c | 35 ++++++++++++++++++++++++----------- src/or/hs_circuit.h | 3 +++ src/or/hs_service.c | 29 +++-------------------------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index ce85229470..adca189ad8 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -215,17 +215,7 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, DIGEST256MAP_FOREACH(desc->intro_points.map, key, const hs_service_intro_point_t *, ip) { circuit_t *circ; - origin_circuit_t *ocirc; - if (ip->base.is_only_legacy) { - uint8_t digest[DIGEST_LEN]; - if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { - continue; - } - ocirc = hs_circuitmap_get_intro_circ_v2_service_side(digest); - } else { - ocirc = - hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); - } + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); if (ocirc == NULL) { continue; } @@ -665,6 +655,29 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) /* Public API */ /* ========== */ +/* Return an introduction point circuit matching the given intro point object. + * NULL is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_intro_circ(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; +} + /* Called when we fail building a rendezvous circuit at some point other than * the last hop: launches a new circuit to the same rendezvous point. This * supports legacy service. */ diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 8301dea227..8706e6b0ed 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -30,6 +30,9 @@ int hs_circ_launch_rendezvous_point(const hs_service_t *service, const uint8_t *rendezvous_cookie); void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); +origin_circuit_t *hs_circ_service_get_intro_circ( + const hs_service_intro_point_t *ip); + /* Cell API. */ int hs_circ_handle_intro_established(const hs_service_t *service, const hs_service_intro_point_t *ip, diff --git a/src/or/hs_service.c b/src/or/hs_service.c index a20de94b11..f656592d5f 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -578,29 +578,6 @@ get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, return info; } -/* 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; -} - /* Return the number of introduction points that are established for the * given descriptor. */ static unsigned int @@ -656,7 +633,7 @@ 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 = get_intro_circuit(ip); + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); if (ocirc) { /* Reason is FINISHED because service has been removed and thus the * circuit is considered old/uneeded. When freed, the circuit is removed @@ -1587,7 +1564,7 @@ cleanup_intro_points(hs_service_t *service, time_t now) 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); + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(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 @@ -1733,7 +1710,7 @@ launch_intro_point_circuits(hs_service_t *service, time_t now) /* Skip the intro point that already has an existing circuit * (established or not). */ - if (get_intro_circuit(ip)) { + if (hs_circ_service_get_intro_circ(ip)) { continue; } From 85c80adf4a75e1f44250379a4806796a26e861e3 Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 14 Jul 2017 16:37:13 -0400 Subject: [PATCH 46/91] prop224: HSDir v3 support is >= 0.3.0.8 Because of bug #22447, we have to select nodes that are at least this version. Signed-off-by: David Goulet --- src/or/nodelist.c | 5 +++++ src/or/routerparse.c | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 117598cf1b..1666fffb7a 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -55,6 +55,7 @@ #include "rendservice.h" #include "router.h" #include "routerlist.h" +#include "routerparse.h" #include "routerset.h" #include "torcert.h" @@ -797,6 +798,10 @@ node_supports_v3_hsdir(const node_t *node) if (node->ri->protocol_list == NULL) { return 0; } + if (node->ri->platform) { + /* Bug #22447 forces us to filter on this version. */ + return tor_version_as_new_as(node->ri->platform, "0.3.0.8"); + } return protocol_list_supports_protocol(node->ri->protocol_list, PRT_HSDIR, PROTOVER_HSDIR_V3); } diff --git a/src/or/routerparse.c b/src/or/routerparse.c index f4e87a00d8..ec63aef4d4 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -2718,6 +2718,11 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); rs->protocols_known = 1; } + if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { + /* Bug #22447 forces us to filter on this version. */ + rs->supports_v3_hsdir = + tor_version_as_new_as(tok->args[0], "0.3.0.8"); + } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); } From 2af254096f68f0cefadb5cb06b010d19edd2e6e1 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 17 Jul 2017 14:45:14 +0300 Subject: [PATCH 47/91] SR: Compute the start time of the current protocol run. This function will be used to make the HS desc overlap function be independent of absolute times. --- src/or/shared_random_state.c | 20 ++++++++++ src/or/shared_random_state.h | 2 + src/test/test_shared_random.c | 73 +++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 89d2e8d7f6..1fc1440d0d 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -156,6 +156,26 @@ get_start_time_of_current_round(time_t now) return curr_start; } +/** Return the start time of the current SR protocol run. For example, if the + * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this + * function should return 23/06/2017 00:00:00. */ +time_t +sr_state_get_start_time_of_current_protocol_run(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + time_t beginning_of_current_round = get_start_time_of_current_round(now); + + /* Get current SR protocol round */ + int current_round = (now / voting_interval) % total_rounds; + + /* Get start time by subtracting the time elapsed from the beginning of the + protocol run */ + time_t time_elapsed_since_start_of_run = current_round * voting_interval; + return beginning_of_current_round - time_elapsed_since_start_of_run; +} + /* Return the time we should expire the state file created at now. * We expire the state file in the beginning of the next protocol run. */ STATIC time_t diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 3526ad47d3..ae1c5ac219 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -121,6 +121,8 @@ int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); +time_t sr_state_get_start_time_of_current_protocol_run(time_t now); + #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 026a0f3825..3eb47dfbc3 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -189,6 +189,77 @@ test_get_state_valid_until_time(void *arg) ; } +/** Test the function that calculates the start time of the current SRV + * protocol run. */ +static void +test_get_start_time_of_current_run(void *arg) +{ + int retval; + char tbuf[ISO_TIME_LEN + 1]; + time_t current_time, run_start_time; + + (void) arg; + + { + /* Get start time if called at 00:00:01 */ + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:59 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + /* Now let's alter the voting schedule and check the correctness of the + * function. Voting interval of 10 seconds, means that an SRV protocol run + * takes 10 seconds * 24 rounds = 4 mins */ + { + or_options_t *options = get_options_mutable(); + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:15:32 UTC", + ¤t_time); + + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:12:00", OP_EQ, tbuf); + } + + done: + ; +} + /* Mock function to immediately return our local 'mock_consensus'. */ static networkstatus_t * mock_networkstatus_get_live_consensus(time_t now) @@ -1272,6 +1343,8 @@ struct testcase_t sr_tests[] = { NULL, NULL }, { "get_next_valid_after_time", test_get_next_valid_after_time, TT_FORK, NULL, NULL }, + { "get_start_time_of_current_run", test_get_start_time_of_current_run, + TT_FORK, NULL, NULL }, { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK, NULL, NULL }, { "vote", test_vote, TT_FORK, From 0b22b7fce3f1ce97a85f4022533b206f847b7307 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 24 Jul 2017 13:30:04 +0300 Subject: [PATCH 48/91] SR: Calculate current SRV phase/run duration. This is also needed to make the HS desc overlap mode function independent of absolute hours. --- src/or/shared_random_state.c | 16 ++++++++++++++++ src/or/shared_random_state.h | 2 ++ src/test/test_shared_random.c | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 1fc1440d0d..7f8094dafd 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -176,6 +176,22 @@ sr_state_get_start_time_of_current_protocol_run(time_t now) return beginning_of_current_round - time_elapsed_since_start_of_run; } +/** Return the time (in seconds) it takes to complete a full SR protocol phase + * (e.g. the commit phase). */ +unsigned int +sr_state_get_phase_duration(void) +{ + return SHARED_RANDOM_N_ROUNDS * get_voting_interval(); +} + +/** Return the time (in seconds) it takes to complete a full SR protocol run */ +unsigned int +sr_state_get_protocol_run_duration(void) +{ + int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + return total_protocol_rounds * get_voting_interval(); +} + /* Return the time we should expire the state file created at now. * We expire the state file in the beginning of the next protocol run. */ STATIC time_t diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index ae1c5ac219..03dd5eb37e 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -122,6 +122,8 @@ void sr_state_save(void); void sr_state_free(void); time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +unsigned int sr_state_get_phase_duration(void); +unsigned int sr_state_get_protocol_run_duration(void); #ifdef SHARED_RANDOM_STATE_PRIVATE diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 3eb47dfbc3..ea037d417b 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -260,6 +260,25 @@ test_get_start_time_of_current_run(void *arg) ; } +static void +test_get_sr_protocol_duration(void *arg) +{ + (void) arg; + + /* Check that by default an SR phase is 12 hours */ + tt_int_op(sr_state_get_phase_duration(), ==, 12*60*60); + tt_int_op(sr_state_get_protocol_run_duration(), ==, 24*60*60); + + /* Now alter the voting interval and check that the SR phase is 2 mins long + * if voting happens every 10 seconds (10*12 seconds = 2 mins) */ + or_options_t *options = get_options_mutable(); + options->V3AuthVotingInterval = 10; + tt_int_op(sr_state_get_phase_duration(), ==, 2*60); + tt_int_op(sr_state_get_protocol_run_duration(), ==, 4*60); + + done: ; +} + /* Mock function to immediately return our local 'mock_consensus'. */ static networkstatus_t * mock_networkstatus_get_live_consensus(time_t now) @@ -1345,6 +1364,8 @@ struct testcase_t sr_tests[] = { NULL, NULL }, { "get_start_time_of_current_run", test_get_start_time_of_current_run, TT_FORK, NULL, NULL }, + { "get_sr_protocol_duration", test_get_sr_protocol_duration, TT_FORK, + NULL, NULL }, { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK, NULL, NULL }, { "vote", test_vote, TT_FORK, From 2cd5f9a2fc2765539899b6e84ed4b1c9e02febad Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 18 Jul 2017 16:44:03 +0300 Subject: [PATCH 49/91] prop224: Compute start time of next time period. --- src/or/hs_common.c | 16 ++++++++++ src/or/hs_common.h | 1 + src/test/test_hs_common.c | 61 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 01bd204e11..0d81063cf7 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -23,6 +23,7 @@ #include "rendservice.h" #include "router.h" #include "shared_random.h" +#include "shared_random_state.h" /* Ed25519 Basepoint value. Taken from section 5 of * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ @@ -216,6 +217,21 @@ hs_get_next_time_period_num(time_t now) return hs_get_time_period_num(now) + 1; } +/* Return the start time of the upcoming time period based on now. */ +time_t +hs_get_start_time_of_next_time_period(time_t now) +{ + uint64_t time_period_length = get_time_period_length(); + + /* Get start time of next time period */ + uint64_t next_time_period_num = hs_get_next_time_period_num(now); + uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length; + + /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + return start_of_next_tp_in_mins * 60 + time_period_rotation_offset; +} + /* Create a new rend_data_t for a specific given version. * Return a pointer to the newly allocated data structure. */ static rend_data_t * diff --git a/src/or/hs_common.h b/src/or/hs_common.h index cbf1ac113d..519485d576 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -195,6 +195,7 @@ void hs_get_subcredential(const ed25519_public_key_t *identity_pk, uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); +time_t hs_get_start_time_of_next_time_period(time_t now); link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 27bbab8d46..e41d68d42e 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -14,6 +14,7 @@ #include "hs_test_helpers.h" #include "hs_common.h" +#include "config.h" static void test_validate_address(void *arg) @@ -132,6 +133,64 @@ test_time_period(void *arg) ; } +static void +test_start_time_of_next_time_period(void *arg) +{ + (void) arg; + int retval; + time_t fake_time; + char tbuf[ISO_TIME_LEN + 1]; + time_t next_tp_start_time; + + /* Do some basic tests */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 12:00:00", OP_EQ, tbuf); + + /* Another test with an edge-case time (start of TP) */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-14 12:00:00", OP_EQ, tbuf); + + { + /* Now pretend we are on a testing network and alter the voting schedule to + be every 10 seconds. This means that a time period has length 10*24 + seconds (4 minutes). It also means that we apply a rotational offset of + 120 seconds to the time period, so that it starts at 00:02:00 instead of + 00:00:00. */ + or_options_t *options = get_options_mutable(); + options->TestingTorNetwork = 1; + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 00:02:00", OP_EQ, tbuf); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC", + &fake_time); + tt_int_op(retval, ==, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 00:06:00", OP_EQ, tbuf); + } + + done: + ; +} + /** Test that our HS overlap period functions work properly. */ static void test_desc_overlap_period(void *arg) @@ -186,6 +245,8 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "time_period", test_time_period, TT_FORK, NULL, NULL }, + { "start_time_of_next_time_period", test_start_time_of_next_time_period, + TT_FORK, NULL, NULL }, { "desc_overlap_period", test_desc_overlap_period, TT_FORK, NULL, NULL }, From cf58451a8ba03e869a805f973ef5b5f3f6d82b6b Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 24 Jul 2017 13:31:17 +0300 Subject: [PATCH 50/91] prop224: Refactor hs_get_time_period_num() to not use absolute time. Instead use the SRV protocol duration to calculate the rotation offset that was previously hardcoded to 12 hours. --- src/or/hs_common.c | 13 +++++++++---- src/or/hs_common.h | 2 -- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 0d81063cf7..0ae196d93e 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -196,13 +196,18 @@ uint64_t hs_get_time_period_num(time_t now) { uint64_t time_period_num; + + /* Start by calculating minutes since the epoch */ uint64_t time_period_length = get_time_period_length(); uint64_t minutes_since_epoch = now / 60; - /* Now subtract half a day to fit the prop224 time period schedule (see - * section [TIME-PERIODS]). */ - tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET); - minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET; + /* Apply the rotation offset as specified by prop224 (section + * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV + * publication */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + time_period_rotation_offset /= 60; /* go from seconds to minutes */ + tor_assert(minutes_since_epoch > time_period_rotation_offset); + minutes_since_epoch -= time_period_rotation_offset; /* Calculate the time period */ time_period_num = minutes_since_epoch / time_period_length; diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 519485d576..268a69bb52 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -52,8 +52,6 @@ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ /* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ -/* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ -#define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ /* Prefix of the onion address checksum. */ #define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" From 6c00bd1f10f4683824deeaa7dd8a23aaf6b9a40e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 18 Jul 2017 18:10:26 +0300 Subject: [PATCH 51/91] prop224: Make prop224 time periods smaller in testnets. It used to be that time periods were 24 hours long even on chutney, which made testing harder. With this commit, time periods have the same length as a full SRV protocol run, which means that they will change every 4 minutes in a 10-second voting interval chutney network! --- src/or/hs_common.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 0ae196d93e..9c3d2c1711 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -181,6 +181,17 @@ hs_check_service_private_dir(const char *username, const char *path, STATIC uint64_t get_time_period_length(void) { + /* If we are on a test network, make the time period smaller than normal so + that we actually see it rotate. Specifically, make it the same length as + an SRV protocol run. */ + if (get_options()->TestingTorNetwork) { + unsigned run_duration = sr_state_get_protocol_run_duration(); + /* An SRV run should take more than a minute (it's 24 rounds) */ + tor_assert_nonfatal(run_duration > 60); + /* Turn it from seconds to minutes before returning: */ + return sr_state_get_protocol_run_duration() / 60; + } + int32_t time_period_length = networkstatus_get_param(NULL, "hsdir-interval", HS_TIME_PERIOD_LENGTH_DEFAULT, HS_TIME_PERIOD_LENGTH_MIN, From 2e5a2d64bd4d26323a226d1069b960b28bd25440 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 18 Jul 2017 16:06:12 +0300 Subject: [PATCH 52/91] prop224: Refactor the overlap function to not use absolute time. We consider to be in overlap mode when we are in the period of time between a fresh SRV and the beginning of the new time period (in the normal network this is between 00:00 and 12:00 UTC). This commit edits that function to use the above semantic logic instead of absolute times. Signed-off-by: David Goulet --- src/or/hs_common.c | 19 +++++----- src/test/test_hs_common.c | 72 ++++++++++++++++++++++++++++++++++++-- src/test/test_hs_service.c | 6 ++++ 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 9c3d2c1711..20b470ff6c 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -908,7 +908,8 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, MOCK_IMPL(int, hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) { - struct tm valid_after_tm; + time_t valid_after; + time_t srv_start_time, tp_start_time; if (!consensus) { consensus = networkstatus_get_live_consensus(now); @@ -917,16 +918,18 @@ hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) } } - /* XXX: Futur commits will change this to a slot system so it can be - * fine tuned better for testing networks in terms of timings. */ + /* We consider to be in overlap mode when we are in the period of time + * between a fresh SRV and the beginning of the new time period (in the + * normal network this is between 00:00 (inclusive) and 12:00 UTC + * (exclusive)) */ + valid_after = consensus->valid_after; + srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after); + tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); - /* From the spec: "Specifically, when a hidden service fetches a consensus - * with "valid-after" between 00:00UTC and 12:00UTC, it goes into - * "descriptor overlap" mode." */ - tor_gmtime_r(&consensus->valid_after, &valid_after_tm); - if (valid_after_tm.tm_hour > 0 && valid_after_tm.tm_hour < 12) { + if (valid_after >= srv_start_time && valid_after < tp_start_time) { return 1; } + return 0; } diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index e41d68d42e..a5c499112e 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -200,9 +200,9 @@ test_desc_overlap_period(void *arg) time_t now = time(NULL); networkstatus_t *dummy_consensus = NULL; - /* First try with a consensus inside the overlap period */ + /* First try with a consensus just inside the overlap period */ dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); - retval = parse_rfc1123_time("Wed, 13 Apr 2016 10:00:00 UTC", + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC", &dummy_consensus->valid_after); tt_int_op(retval, ==, 0); @@ -211,7 +211,7 @@ test_desc_overlap_period(void *arg) /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap period is still active. */ - dummy_consensus->valid_after += 3600; + dummy_consensus->valid_after += 3600*11; retval = hs_overlap_mode_is_active(dummy_consensus, now); tt_int_op(retval, ==, 1); @@ -238,6 +238,70 @@ test_desc_overlap_period(void *arg) tor_free(dummy_consensus); } +/* Test the overlap period functions on a testnet with altered voting + * schedule */ +static void +test_desc_overlap_period_testnet(void *arg) +{ + int retval; + time_t now = approx_time(); + networkstatus_t *dummy_consensus = NULL; + or_options_t *options = get_options_mutable(); + + (void) arg; + + /* Set the testnet option and a 10-second voting interval */ + options->TestingTorNetwork = 1; + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + + dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + + /* A 10-second voting interval means that the lengths of an SRV run and of a + * time period are both 10*24 seconds (4 minutes). The SRV gets published at + * 00:00:00 and the TP starts at 00:02:00 (rotation offset: 2 mins). Those + * two minutes between SRV publish and TP start is the overlap period + * window. Let's test it: */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:01:59 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:04:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:05:59 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 1); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:06:00 UTC", + &dummy_consensus->valid_after); + tt_int_op(retval, ==, 0); + retval = hs_overlap_mode_is_active(dummy_consensus, now); + tt_int_op(retval, ==, 0); + + done: + tor_free(dummy_consensus); +} + struct testcase_t hs_common_tests[] = { { "build_address", test_build_address, TT_FORK, NULL, NULL }, @@ -249,6 +313,8 @@ struct testcase_t hs_common_tests[] = { TT_FORK, NULL, NULL }, { "desc_overlap_period", test_desc_overlap_period, TT_FORK, NULL, NULL }, + { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 1b8d8252eb..30f1682d8c 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -936,6 +936,8 @@ test_rotate_descriptors(void *arg) * overlap check doesn't care about the year. */ ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", &mock_ns.valid_after); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); tt_int_op(ret, OP_EQ, 0); /* Create a service with a default descriptor and state. It's added to the @@ -954,6 +956,8 @@ test_rotate_descriptors(void *arg) /* Entering an overlap period. */ ret = parse_rfc1123_time("Sat, 26 Oct 1985 01:00:00 UTC", &mock_ns.valid_after); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 02:00:00 UTC", + &mock_ns.fresh_until); tt_int_op(ret, OP_EQ, 0); desc_next = service_descriptor_new(); desc_next->next_upload_time = 42; /* Our marker to recognize it. */ @@ -977,6 +981,8 @@ test_rotate_descriptors(void *arg) /* Going out of the overlap period. */ ret = parse_rfc1123_time("Sat, 26 Oct 1985 12:00:00 UTC", &mock_ns.valid_after); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.fresh_until); /* This should reset the state and not touch the current descriptor. */ tt_int_op(ret, OP_EQ, 0); rotate_all_descriptors(now); From 6f046b2191ed1a10e9058fcc49491b8db5a96280 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 21 Jul 2017 15:53:17 +0300 Subject: [PATCH 53/91] prop224: Use state file to save/load revision counters Signed-off-by: David Goulet --- src/or/hs_service.c | 209 ++++++++++++++++++++++++++++++++++++++++++-- src/or/or.h | 3 + src/or/statefile.c | 3 + 3 files changed, 208 insertions(+), 7 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index f656592d5f..6519cb1b61 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -23,6 +23,7 @@ #include "router.h" #include "routerkeys.h" #include "routerlist.h" +#include "statefile.h" #include "hs_circuit.h" #include "hs_common.h" @@ -71,6 +72,8 @@ static const char *address_tld = "onion"; * loading keys requires that we are an actual running tor process. */ static smartlist_t *hs_service_staging_list; +static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); + /* Helper: Function to compare two objects in the service map. Return 1 if the * two service have the same master public identity key. */ static inline int @@ -1283,6 +1286,9 @@ build_service_descriptor(hs_service_t *service, time_t now, goto err; } + /* Set the revision counter for this descriptor */ + set_descriptor_revision_counter(desc->desc); + /* 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. */ @@ -1936,6 +1942,200 @@ upload_descriptor_to_hsdir(const hs_service_t *service, return; } +/** Return a newly-allocated string for our state file which contains revision + * counter information for desc. The format is: + * + * HidServRevCounter + */ +static char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) +{ + char *state_str = NULL; + char blinded_pubkey_b64[ED25519_BASE64_LEN+1]; + uint64_t rev_counter = desc->desc->plaintext_data.revision_counter; + const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey; + + /* Turn the blinded key into b64 so that we save it on state */ + tor_assert(blinded_pubkey); + if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) { + goto done; + } + + /* Format is: */ + tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter); + + log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!", + rev_counter, blinded_pubkey_b64); + + done: + return state_str; +} + +/** Update HS descriptor revision counters in our state by removing the old + * ones and writing down the ones that are currently active. */ +static void +update_revision_counters_in_state(void) +{ + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + or_state_t *state = get_or_state(); + + /* Prepare our state structure with the rev counters */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We don't want to save zero counters */ + if (desc->desc->plaintext_data.revision_counter == 0) { + continue; + } + + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("HidServRevCounter"); + (*nextline)->value = encode_desc_rev_counter_for_state(desc); + nextline = &(*nextline)->next; + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* Remove the old rev counters, and replace them with the new ones */ + config_free_lines(state->HidServRevCounters); + state->HidServRevCounters = lines; + + /* Set the state as dirty since we just edited it */ + if (!get_options()->AvoidDiskWrites) { + or_state_mark_dirty(state, 0); + } +} + +/** Scan the string state_line for the revision counter of the service + * with blinded_pubkey. Set service_found_out to True if the + * line is relevant to this service, and return the cached revision + * counter. Else set service_found_out to False. */ +static uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + ed25519_public_key_t *blinded_pubkey, + int *service_found_out) +{ + smartlist_t *items = NULL; + int ok; + ed25519_public_key_t pubkey_in_state; + uint64_t rev_counter = 0; + + tor_assert(service_found_out); + tor_assert(state_line); + tor_assert(blinded_pubkey); + + /* Assume that the line is not for this service */ + *service_found_out = 0; + + /* Start parsing the state line */ + items = smartlist_new(); + smartlist_split_string(items, state_line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 2) { + log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring."); + goto done; + } + + char *b64_key_str = smartlist_get(items, 0); + char *saved_rev_counter_str = smartlist_get(items, 1); + + /* Parse blinded key to check if it's for this hidden service */ + if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) { + log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring."); + goto done; + } + /* State line not for this hidden service */ + if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) { + goto done; + } + + rev_counter = tor_parse_uint64(saved_rev_counter_str, + 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring."); + goto done; + } + + /* Since we got this far, the line was for this service */ + *service_found_out = 1; + + log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64, + b64_key_str, rev_counter); + + done: + if (items) { + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + } + + return rev_counter; +} + +/** Dig into our state file and find the current revision counter for the + * service with blinded key blinded_pubkey. If no revision counter is + * found, return 0. */ +static uint64_t +get_rev_counter_for_service(ed25519_public_key_t *blinded_pubkey) +{ + or_state_t *state = get_or_state(); + config_line_t *line; + + /* Set default value for rev counters (if not found) to 0 */ + uint64_t final_rev_counter = 0; + + for (line = state->HidServRevCounters ; line ; line = line->next) { + int service_found = 0; + uint64_t rev_counter = 0; + + tor_assert(!strcmp(line->key, "HidServRevCounter")); + + /* Scan all the HidServRevCounters lines till we find the line for this + service: */ + rev_counter = check_state_line_for_service_rev_counter(line->value, + blinded_pubkey, + &service_found); + if (service_found) { + final_rev_counter = rev_counter; + goto done; + } + } + + done: + return final_rev_counter; +} + +/** Update the value of the revision counter for hs_desc and save it on + our state file. */ +static void +update_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + /* Increment the revision counter of hs_desc so the next update (which + * will trigger an upload) will have the right value. We do this at this + * stage to only do it once because a descriptor can have many updates before + * being uploaded. By doing it at upload, we are sure to only increment by 1 + * and thus avoid leaking how many operations we made on the descriptor from + * the previous one before uploading. */ + rev_counter++; + hs_desc->plaintext_data.revision_counter = rev_counter; + + update_revision_counters_in_state(); +} + +/** Set the revision counter in hs_desc, using the state file to find + * the current counter value if it exists. */ +static void +set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + hs_desc->plaintext_data.revision_counter = rev_counter; +} + /* Encode and sign the service descriptor desc and upload it to the * responsible hidden service directories. If for_next_period is true, the set * of directories are selected using the next hsdir_index. This does nothing @@ -1992,13 +2192,8 @@ upload_descriptor_to_all(const hs_service_t *service, safe_str_client(service->onion_address), fmt_next_time); } - /* Increment the revision counter so the next update (which will trigger an - * upload) will have the right value. We do this at this stage to only do it - * once because a descriptor can have many updates before being uploaded. By - * doing it at upload, we are sure to only increment by 1 and thus avoid - * leaking how many operations we made on the descriptor from the previous - * one before uploading. */ - desc->desc->plaintext_data.revision_counter += 1; + /* Update the revision counter of this descriptor */ + update_descriptor_revision_counter(desc->desc); smartlist_free(responsible_dirs); return; diff --git a/src/or/or.h b/src/or/or.h index 09e58b7b18..d8aea38278 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4626,6 +4626,9 @@ typedef struct { config_line_t *TransportProxies; + /** Cached revision counters for active hidden services on this host */ + config_line_t *HidServRevCounters; + /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the * bandwidth usage. The "Interval" fields hold the granularity, in seconds, diff --git a/src/or/statefile.c b/src/or/statefile.c index d0606b3012..6b759960cb 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -85,6 +85,9 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), + VAR("HidServRevCounter", LINELIST_S, HidServRevCounters, NULL), + V(HidServRevCounters, LINELIST_V, NULL), + V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), V(BWHistoryReadValues, CSV, ""), From b47139d7583ff247fcdd07904144dc99c412028a Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 24 Jul 2017 13:03:19 +0300 Subject: [PATCH 54/91] test: Unit tests for the revision counter state file codethe Signed-off-by: David Goulet --- src/or/hs_service.c | 6 ++-- src/or/hs_service.h | 10 +++++++ src/test/test_hs_service.c | 61 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 6519cb1b61..131c4ff9f0 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -924,7 +924,7 @@ load_service_keys(hs_service_t *service) } /* Free a given service descriptor object and all key material is wiped. */ -static void +STATIC void service_descriptor_free(hs_service_descriptor_t *desc) { if (!desc) { @@ -1947,7 +1947,7 @@ upload_descriptor_to_hsdir(const hs_service_t *service, * * HidServRevCounter */ -static char * +STATIC char * encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) { char *state_str = NULL; @@ -2009,7 +2009,7 @@ update_revision_counters_in_state(void) * with blinded_pubkey. Set service_found_out to True if the * line is relevant to this service, and return the cached revision * counter. Else set service_found_out to False. */ -static uint64_t +STATIC uint64_t check_state_line_for_service_rev_counter(const char *state_line, ed25519_public_key_t *blinded_pubkey, int *service_found_out) diff --git a/src/or/hs_service.h b/src/or/hs_service.h index f46c4f51a6..cb2a7aa80e 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -325,6 +325,16 @@ STATIC void build_all_descriptors(time_t now); STATIC void update_all_descriptors(time_t now); STATIC void run_upload_descriptor_event(time_t now); +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc); + +STATIC void service_descriptor_free(hs_service_descriptor_t *desc); + +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + ed25519_public_key_t *blinded_pubkey, + int *service_found_out); + #endif /* TOR_UNIT_TESTS */ #endif /* HS_SERVICE_PRIVATE */ diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 30f1682d8c..664277b198 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1194,6 +1194,65 @@ test_upload_desctriptors(void *arg) UNMOCK(hs_overlap_mode_is_active); } +/** Test the functions that save and load HS revision counters to state. */ +static void +test_revision_counter_state(void *arg) +{ + char *state_line_one = NULL; + char *state_line_two = NULL; + + hs_service_descriptor_t *desc_one = service_descriptor_new(); + hs_service_descriptor_t *desc_two = service_descriptor_new(); + + (void) arg; + + /* Prepare both descriptors */ + desc_one->desc->plaintext_data.revision_counter = 42; + desc_two->desc->plaintext_data.revision_counter = 240; + memset(&desc_one->blinded_kp.pubkey.pubkey, '\x42', + sizeof(desc_one->blinded_kp.pubkey.pubkey)); + memset(&desc_two->blinded_kp.pubkey.pubkey, '\xf0', + sizeof(desc_one->blinded_kp.pubkey.pubkey)); + + /* Turn the descriptor rev counters into state lines */ + state_line_one = encode_desc_rev_counter_for_state(desc_one); + tt_str_op(state_line_one, OP_EQ, + "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI 42"); + + state_line_two = encode_desc_rev_counter_for_state(desc_two); + tt_str_op(state_line_two, OP_EQ, + "8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PA 240"); + + /* Now let's test our state parsing function: */ + int service_found; + uint64_t cached_rev_counter; + + /* First's try with wrong pubkey and check that no service was found */ + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one, + &desc_two->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 0); + + /* Now let's try with the right pubkeys */ + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one, + &desc_one->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 1); + tt_int_op(cached_rev_counter, OP_EQ, 42); + + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_two, + &desc_two->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 1); + tt_int_op(cached_rev_counter, OP_EQ, 240); + + done: + tor_free(state_line_one); + tor_free(state_line_two); + service_descriptor_free(desc_one); + service_descriptor_free(desc_two); +} + struct testcase_t hs_service_tests[] = { { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, @@ -1221,6 +1280,8 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "upload_desctriptors", test_upload_desctriptors, TT_FORK, NULL, NULL }, + { "revision_counter_state", test_revision_counter_state, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; From ec0da9a6f150c3e96a5aa777dea38105350a2b53 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 24 Jul 2017 13:17:59 +0300 Subject: [PATCH 55/91] test: Unbreak test_upload_descriptors() To upload the descriptor we needed a state file to write the rev counters in, but that test did not have a state file initialized. Also fix the typo in its func name. Signed-off-by: David Goulet --- src/test/test_hs_service.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 664277b198..6d5ea7ed7d 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -16,6 +16,7 @@ #define HS_INTROPOINT_PRIVATE #define MAIN_PRIVATE #define NETWORKSTATUS_PRIVATE +#define STATEFILE_PRIVATE #define TOR_CHANNEL_INTERNAL_ #include "test.h" @@ -43,6 +44,7 @@ #include "hs_service.h" #include "main.h" #include "rendservice.h" +#include "statefile.h" /* Trunnel */ #include "hs/cell_establish_intro.h" @@ -56,6 +58,15 @@ mock_networkstatus_get_live_consensus(time_t now) return &mock_ns; } +static or_state_t *dummy_state = NULL; + +/* Mock function to get fake or state (used for rev counters) */ +static or_state_t * +get_or_state_replacement(void) +{ + return dummy_state; +} + /* Mock function because we are not trying to test the close circuit that does * an awful lot of checks on the circuit object. */ static void @@ -779,6 +790,10 @@ test_introduce2(void *arg) hs_init(); MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(get_or_state, + get_or_state_replacement); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags); @@ -830,6 +845,8 @@ test_introduce2(void *arg) tt_u64_op(ip->introduce2_count, OP_EQ, 0); done: + or_state_free(dummy_state); + dummy_state = NULL; circuit_free(TO_CIRCUIT(circ)); hs_free_all(); UNMOCK(circuit_mark_for_close_); @@ -1011,6 +1028,10 @@ test_build_update_descriptors(void *arg) hs_init(); MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true); + MOCK(get_or_state, + get_or_state_replacement); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); /* Create a service without a current descriptor to trigger a build. */ service = hs_service_new(get_options()); @@ -1133,7 +1154,7 @@ test_build_update_descriptors(void *arg) } static void -test_upload_desctriptors(void *arg) +test_upload_descriptors(void *arg) { int ret; time_t now = time(NULL); @@ -1144,6 +1165,10 @@ test_upload_desctriptors(void *arg) hs_init(); MOCK(hs_overlap_mode_is_active, mock_hs_overlap_mode_is_active_true); + MOCK(get_or_state, + get_or_state_replacement); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); /* Create a service with no descriptor. It's added to the global map. */ service = hs_service_new(get_options()); @@ -1278,7 +1303,7 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "build_update_descriptors", test_build_update_descriptors, TT_FORK, NULL, NULL }, - { "upload_desctriptors", test_upload_desctriptors, TT_FORK, + { "upload_descriptors", test_upload_descriptors, TT_FORK, NULL, NULL }, { "revision_counter_state", test_revision_counter_state, TT_FORK, NULL, NULL }, From 708789025da9369f45ff18a6bcf743f005d9abff Mon Sep 17 00:00:00 2001 From: David Goulet Date: Tue, 1 Aug 2017 13:30:04 -0400 Subject: [PATCH 56/91] prop224: Remove INTRODUCE2 legacy handling Turns out that introduction points don't care about the INTRODUCE2 cell format as long as the top field is LEGACY_KEY_ID as expected. So let's use a single INTRODUCE format regardless of the introduction point being legacy or not. This also removes the polymorphic void* situation. Signed-off-by: David Goulet --- src/or/hs_cell.c | 105 ++++++++------------------------------------ src/or/hs_cell.h | 2 - src/or/hs_circuit.c | 1 - 3 files changed, 19 insertions(+), 89 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 4c476b1388..712faa3358 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -208,100 +208,35 @@ build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, return cell_len; } -/* Free the given cell pointer. If is_legacy_cell is set, cell_ptr is cast to - * a rend_intro_cell_t else to a trn_cell_introduce1_t. */ -static void -introduce2_free_cell(void *cell_ptr, unsigned int is_legacy_cell) -{ - if (cell_ptr == NULL) { - return; - } - if (is_legacy_cell) { - rend_intro_cell_t *legacy_cell = cell_ptr; - rend_service_free_intro(legacy_cell); - } else { - trn_cell_introduce1_free((trn_cell_introduce1_t *) cell_ptr); - } -} - -/* Return the length of the encrypted section of the cell_ptr. If - * is_legacy_cell is set, cell_ptr is cast to a rend_intro_cell_t else to a - * trn_cell_introduce1_t. */ -static size_t -get_introduce2_encrypted_section_len(const void *cell_ptr, - unsigned int is_legacy_cell) -{ - tor_assert(cell_ptr); - if (is_legacy_cell) { - return ((const rend_intro_cell_t *) cell_ptr)->ciphertext_len; - } - return trn_cell_introduce1_getlen_encrypted( - (const trn_cell_introduce1_t *) cell_ptr); -} - -/* Return the encrypted section pointer from the the cell_ptr. If - * is_legacy_cell is set, cell_ptr is cast to a rend_intro_cell_t else to a - * trn_cell_introduce1_t. */ -static const uint8_t * -get_introduce2_encrypted_section(const void *cell_ptr, - unsigned int is_legacy_cell) -{ - tor_assert(cell_ptr); - if (is_legacy_cell) { - return ((const rend_intro_cell_t *) cell_ptr)->ciphertext; - } - return trn_cell_introduce1_getconstarray_encrypted( - (const trn_cell_introduce1_t *) cell_ptr); -} - /* Parse an INTRODUCE2 cell from payload of size payload_len for the given * service and circuit which are used only for logging purposes. The resulting - * parsed cell is put in cell_ptr_out. If is_legacy_cell is set, the type of - * the returned cell is rend_intro_cell_t else trn_cell_introduce1_t. + * parsed cell is put in cell_ptr_out. * * Return 0 on success else a negative value and cell_ptr_out is untouched. */ static int parse_introduce2_cell(const hs_service_t *service, const origin_circuit_t *circ, const uint8_t *payload, - size_t payload_len, unsigned int is_legacy_cell, - void **cell_ptr_out) + size_t payload_len, + trn_cell_introduce1_t **cell_ptr_out) { + trn_cell_introduce1_t *cell = NULL; + tor_assert(service); tor_assert(circ); tor_assert(payload); tor_assert(cell_ptr_out); - /* We parse the cell differently for legacy. */ - if (is_legacy_cell) { - char *err_msg; - rend_intro_cell_t *legacy_cell = NULL; - - legacy_cell = rend_service_begin_parse_intro(payload, payload_len, 2, - &err_msg); - if (legacy_cell == NULL) { - log_info(LD_REND, "Unable to parse legacy INTRODUCE2 cell on " - "circuit %u for service %s: %s", - TO_CIRCUIT(circ)->n_circ_id, err_msg, - safe_str_client(service->onion_address)); - tor_free(err_msg); - goto err; - } - *cell_ptr_out = legacy_cell; - } else { - trn_cell_introduce1_t *cell = NULL; - /* Parse the cell so we can start cell validation. */ - if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { - log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " - "for service %s", - TO_CIRCUIT(circ)->n_circ_id, - safe_str_client(service->onion_address)); - goto err; - } - *cell_ptr_out = cell; + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; } - /* On success, we must have set the cell pointer. */ - tor_assert(*cell_ptr_out); + /* Success. */ + *cell_ptr_out = cell; return 0; err: return -1; @@ -465,9 +400,9 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, uint8_t *decrypted = NULL; size_t encrypted_section_len; const uint8_t *encrypted_section; + trn_cell_introduce1_t *cell = NULL; trn_cell_introduce_encrypted_t *enc_cell = NULL; hs_ntor_intro_cell_keys_t *intro_keys = NULL; - void *cell_ptr = NULL; tor_assert(data); tor_assert(circ); @@ -475,7 +410,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, /* Parse the cell into a decoded data structure pointed by cell_ptr. */ if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, - data->is_legacy, &cell_ptr) < 0) { + &cell) < 0) { goto done; } @@ -484,10 +419,8 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); - encrypted_section = - get_introduce2_encrypted_section(cell_ptr, data->is_legacy); - encrypted_section_len = - get_introduce2_encrypted_section_len(cell_ptr, data->is_legacy); + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); /* Encrypted section must at least contain the CLIENT_PK and MAC which is * defined in section 3.3.2 of the specification. */ @@ -603,7 +536,7 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, } tor_free(decrypted); trn_cell_introduce_encrypted_free(enc_cell); - introduce2_free_cell(cell_ptr, data->is_legacy); + trn_cell_introduce1_free(cell); return ret; } diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index 1336a399a7..b7c3d11275 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -34,8 +34,6 @@ typedef struct hs_cell_introduce2_data_t { const uint8_t *payload; /* Size of the payload of the received encoded cell. */ size_t payload_len; - /* Is this a legacy introduction point? */ - unsigned int is_legacy : 1; /*** Muttable Section. ***/ diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index adca189ad8..c78ac6057f 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -945,7 +945,6 @@ hs_circ_handle_introduce2(const hs_service_t *service, data.payload = payload; data.payload_len = payload_len; data.link_specifiers = smartlist_new(); - data.is_legacy = ip->base.is_only_legacy; data.replay_cache = ip->replay_cache; if (hs_cell_parse_introduce2(&data, circ, service) < 0) { From 1397ac11d69b9a48ef5f17485ac1fd5e3ada922e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 12:01:52 +0300 Subject: [PATCH 57/91] Use htonll() when INT_8 is used. Also prepend period_length to any period_num, as specified by the spec. --- src/or/hs_common.c | 69 +++++++++++++++++++++++++++++++++--------- src/or/hs_descriptor.c | 2 +- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 20b470ff6c..f3604d67c3 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -510,11 +510,26 @@ get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) tor_assert(srv_out); digest = crypto_digest256_new(DIGEST_SHA3_256); - /* Setup payload: H("shared-random-disaster" | INT_8(period_num)) */ + + /* Start setting up payload: + * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */ crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX, HS_SRV_DISASTER_PREFIX_LEN); - crypto_digest_add_bytes(digest, (const char *) &time_period_num, - sizeof(time_period_num)); + + /* Setup INT_8(period_length) | INT_8(period_num) */ + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(time_period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN); crypto_digest_free(digest); } @@ -532,7 +547,7 @@ get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) static void build_blinded_key_param(const ed25519_public_key_t *pubkey, const uint8_t *secret, size_t secret_len, - uint64_t period_num, uint64_t start_time_period, + uint64_t period_num, uint64_t period_length, uint8_t *param_out) { size_t offset = 0; @@ -543,12 +558,12 @@ build_blinded_key_param(const ed25519_public_key_t *pubkey, tor_assert(param_out); /* Create the nonce N. The construction is as follow: - * N = "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ + * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */ memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); offset += HS_KEYBLIND_NONCE_PREFIX_LEN; - set_uint64(nonce + offset, period_num); + set_uint64(nonce + offset, tor_htonll(period_num)); offset += sizeof(uint64_t); - set_uint64(nonce + offset, start_time_period); + set_uint64(nonce + offset, tor_htonll(period_length)); offset += sizeof(uint64_t); tor_assert(offset == HS_KEYBLIND_NONCE_LEN); @@ -953,7 +968,7 @@ hs_service_requires_uptime_circ(const smartlist_t *ports) * value is used to select the responsible HSDir where their hsdir_index is * closest to this value. * SHA3-256("store-at-idx" | blinded_public_key | - * INT_8(replicanum) | INT_8(period_num) ) + * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) ) * * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ void @@ -970,9 +985,23 @@ hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, ED25519_PUBKEY_LEN); - crypto_digest_add_bytes(digest, (const char *) &replica, sizeof(replica)); - crypto_digest_add_bytes(digest, (const char *) &period_num, - sizeof(period_num)); + + /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */ + { + uint64_t period_length = get_time_period_length(); + char buf[sizeof(uint64_t)*3]; + size_t offset = 0; + set_uint64(buf, tor_htonll(replica)); + offset += sizeof(uint64_t); + set_uint64(buf, tor_htonll(period_length)); + offset += sizeof(uint64_t); + set_uint64(buf, tor_htonll(period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(buf)); + + crypto_digest_add_bytes(digest, buf, sizeof(buf)); + } + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); crypto_digest_free(digest); } @@ -980,7 +1009,7 @@ hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, /* Build hsdir_index which is used to find the responsible hsdirs. This is the * index value that is compare to the hs_index when selecting an HSDir. * SHA3-256("node-idx" | node_identity | - * shared_random_value | INT_8(period_num) ) + * shared_random_value | INT_8(period_length) | INT_8(period_num) ) * * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ void @@ -1000,8 +1029,20 @@ hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, ED25519_PUBKEY_LEN); crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); - crypto_digest_add_bytes(digest, (const char *) &period_num, - sizeof(period_num)); + + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); crypto_digest_free(digest); } diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 6f304d6d2d..e18e25b26c 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -236,7 +236,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); offset += sizeof(desc->subcredential); /* Copy revision counter value. */ - set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter)); offset += sizeof(uint64_t); tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); } From 434112df4bfd4f94e553d5a41579a70765949904 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 31 Jul 2017 13:27:16 +0300 Subject: [PATCH 58/91] Fix ternary operator abuse. --- src/or/hs_service.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 131c4ff9f0..6fa714e5e5 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2215,9 +2215,11 @@ should_service_upload_descriptor(const hs_service_t *service, * instead of waiting an arbitrary amount of time breaking the service. * Else, if we have no missing intro points, we use the value taken from the * service configuration. */ - (desc->missing_intro_points) ? - (num_intro_points = digest256map_size(desc->intro_points.map)) : - (num_intro_points = service->config.num_intro_points); + if (desc->missing_intro_points) { + num_intro_points = digest256map_size(desc->intro_points.map); + } else { + num_intro_points = service->config.num_intro_points; + } /* This means we tried to pick intro points but couldn't get any so do not * upload descriptor in this case. We need at least one for the service to @@ -2768,10 +2770,12 @@ hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, goto done; } - ret = (circ->hs_ident) ? service_handle_introduce2(circ, payload, - payload_len) : - rend_service_receive_introduction(circ, payload, - payload_len); + if (circ->hs_ident) { + ret = service_handle_introduce2(circ, payload, payload_len); + } else { + ret = rend_service_receive_introduction(circ, payload, payload_len); + } + done: return ret; } @@ -2798,10 +2802,12 @@ hs_service_receive_intro_established(origin_circuit_t *circ, /* Handle both version. v2 uses rend_data and v3 uses the hs circuit * identifier hs_ident. Can't be both. */ - ret = (circ->hs_ident) ? service_handle_intro_established(circ, payload, - payload_len) : - rend_service_intro_established(circ, payload, - payload_len); + if (circ->hs_ident) { + ret = service_handle_intro_established(circ, payload, payload_len); + } else { + ret = rend_service_intro_established(circ, payload, payload_len); + } + if (ret < 0) { goto err; } @@ -2822,12 +2828,18 @@ hs_service_circuit_has_opened(origin_circuit_t *circ) * identifier hs_ident. Can't be both. */ switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: - (circ->hs_ident) ? service_intro_circ_has_opened(circ) : + if (circ->hs_ident) { + service_intro_circ_has_opened(circ); + } else { rend_service_intro_has_opened(circ); + } break; case CIRCUIT_PURPOSE_S_CONNECT_REND: - (circ->hs_ident) ? service_rendezvous_circ_has_opened(circ) : + if (circ->hs_ident) { + service_rendezvous_circ_has_opened(circ); + } else { rend_service_rendezvous_has_opened(circ); + } break; default: tor_assert(0); From 29b3dd1c054e8dda464c046c701d6946f53434fa Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 13:24:50 +0300 Subject: [PATCH 59/91] Fix 32-bit bug when writing address to descriptor. We used to sizeof() a pointer. Let's just use asprintf to avoid having to be smart. --- src/or/hs_service.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 6fa714e5e5..30f693108b 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -62,9 +62,9 @@ #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"; -static const char *address_tld = "onion"; +static const char fname_keyfile_prefix[] = "hs_ed25519"; +static const char fname_hostname[] = "hostname"; +static const char address_tld[] = "onion"; /* Staging list of service object. When configuring service, we add them to * this list considered a staging area and they will get added to our global @@ -817,20 +817,17 @@ write_address_to_file(const hs_service_t *service, const char *fname_) { int ret = -1; char *fname = NULL; - /* Length of an address plus the sizeof the address tld (onion) which counts - * the NUL terminated byte so we keep it for the "." and the newline. */ - char buf[HS_SERVICE_ADDR_LEN_BASE32 + sizeof(address_tld) + 1]; + char *addr_buf = NULL; tor_assert(service); tor_assert(fname_); /* Construct the full address with the onion tld and write the hostname file * to disk. */ - tor_snprintf(buf, sizeof(buf), "%s.%s\n", service->onion_address, - address_tld); + tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld); /* Notice here that we use the given "fname_". */ fname = hs_path_from_filename(service->config.directory_path, fname_); - if (write_str_to_file(fname, buf, 0) < 0) { + if (write_str_to_file(fname, addr_buf, 0) < 0) { log_warn(LD_REND, "Could not write onion address to hostname file %s", escaped(fname)); goto end; @@ -850,6 +847,7 @@ write_address_to_file(const hs_service_t *service, const char *fname_) ret = 0; end: tor_free(fname); + tor_free(addr_buf); return ret; } From 74981d1f133f0ecb1050715af4ee5d409fcebd41 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:42:30 +0300 Subject: [PATCH 60/91] memwipe interesting unused memory --- src/or/hs_cell.c | 15 +++++++++------ src/or/hs_common.c | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 712faa3358..922ff73468 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -193,18 +193,16 @@ build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, uint8_t *cell_out) { ssize_t cell_len; - char buf[RELAY_PAYLOAD_SIZE] = {0}; tor_assert(circ_nonce); tor_assert(enc_key); tor_assert(cell_out); - cell_len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), + memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE); + + cell_len = rend_service_encode_establish_intro_cell((char*)cell_out, + RELAY_PAYLOAD_SIZE, enc_key, circ_nonce); - tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); - if (cell_len >= 0) { - memcpy(cell_out, buf, cell_len); - } return cell_len; } @@ -326,6 +324,9 @@ hs_cell_build_establish_intro(const char *circ_nonce, tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); memcpy(handshake_ptr, mac, sizeof(mac)); + + memwipe(mac, 0, sizeof(mac)); + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); } /* Calculate the cell signature SIG. */ @@ -353,6 +354,8 @@ hs_cell_build_establish_intro(const char *circ_nonce, /* Copy the signature into the cell. */ sig_ptr = trn_cell_establish_intro_getarray_sig(cell); memcpy(sig_ptr, sig.sig, sig_len); + + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); } /* Encode the cell. Can't be bigger than a standard cell. */ diff --git a/src/or/hs_common.c b/src/or/hs_common.c index f3604d67c3..f63adf51bd 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -582,6 +582,8 @@ build_blinded_key_param(const ed25519_public_key_t *pubkey, /* Extract digest and put it in the param. */ crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); crypto_digest_free(digest); + + memwipe(nonce, 0, sizeof(nonce)); } /* Using an ed25519 public key and version to build the checksum of an @@ -701,6 +703,8 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, ED25519_PUBKEY_LEN); crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); crypto_digest_free(digest); + + memwipe(credential, 0, sizeof(credential)); } /* From the given list of hidden service ports, find the matching one from the @@ -892,6 +896,8 @@ hs_build_blinded_pubkey(const ed25519_public_key_t *pk, build_blinded_key_param(pk, secret, secret_len, time_period_num, get_time_period_length(), param); ed25519_public_blind(blinded_pk_out, pk, param); + + memwipe(param, 0, sizeof(param)); } /* From a given ed25519 keypair kp and an optional secret, compute a blinded @@ -916,6 +922,8 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, build_blinded_key_param(&kp->pubkey, secret, secret_len, time_period_num, get_time_period_length(), param); ed25519_keypair_blind(blinded_kp_out, kp, param); + + memwipe(param, 0, sizeof(param)); } /* Return true if overlap mode is active given the date in consensus. If From e42c55626abf7447a154f5271e3bc35743340a21 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 2 Aug 2017 16:50:15 +0300 Subject: [PATCH 61/91] prop224: Don't use nodes as HSDirs if they don't have an HSDir index. --- src/or/hs_common.c | 28 +++++++++++++++++++++++++++- src/or/nodelist.c | 6 +++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index f63adf51bd..d68c446fd0 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1121,6 +1121,30 @@ hs_get_hsdir_spread_store(void) HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); } +/** node is an HSDir so make sure that we have assigned an hsdir index. + * Return 0 if everything is as expected, else return -1. */ +static int +node_has_hsdir_index(const node_t *node) +{ + tor_assert(node_supports_v3_hsdir(node)); + + /* A node can't have an HSDir index without a descriptor since we need desc + * to get its ed25519 key */ + if (!node_has_descriptor(node)) { + return 0; + } + + /* At this point, since the node has a desc, this node must also have an + * hsdir index. If not, something went wrong, so BUG out. */ + if (BUG(node->hsdir_index == NULL) || + BUG(tor_mem_is_zero((const char*)node->hsdir_index->current, + DIGEST256_LEN))) { + return 0; + } + + return 1; +} + /* For a given blinded key and time period number, get the responsible HSDir * and put their routerstatus_t object in the responsible_dirs list. If * is_next_period is true, the next hsdir_index of the node_t is used. If @@ -1162,7 +1186,9 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, node_t *n = node_get_mutable_by_id(rs->identity_digest); tor_assert(n); if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { - if (BUG(n->hsdir_index == NULL)) { + if (!node_has_hsdir_index(n)) { + log_info(LD_GENERAL, "Node %s was found without hsdir index.", + node_describe(n)); continue; } smartlist_add(sorted_nodes, n); diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 1666fffb7a..c5a5979f39 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -189,9 +189,9 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) node_identity_pk = node_get_ed25519_id(node); if (node_identity_pk == NULL) { - log_warn(LD_BUG, "ed25519 identity public key not found when " - "trying to build the hsdir indexes for node %s", - node_describe(node)); + log_debug(LD_GENERAL, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); goto done; } From 7c507a1f7f58adb48be887cd26686190c3b22cfd Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:47:06 +0300 Subject: [PATCH 62/91] Relax assertions: turn them to BUGs and non-fatal asserts. --- src/or/connection_edge.c | 4 +++- src/or/hs_service.c | 33 ++++++++++++++++++++++++--------- src/or/hs_service.h | 4 ++-- src/test/test_hs_service.c | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 41e5f88ab8..9f0cc061e1 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3098,10 +3098,12 @@ handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) /* Setup the identifier to be the one for the circuit service. */ conn->hs_ident = hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + tor_assert(connection_edge_is_rendezvous_stream(conn)); ret = hs_service_set_conn_addr_port(origin_circ, conn); } else { /* We should never get here if the circuit's purpose is rendezvous. */ - tor_assert(0); + tor_assert_nonfatal_unreached(); + return -1; } if (ret < 0) { log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 30f693108b..22739334dd 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -377,12 +377,16 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) * 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); + if (BUG(!ls)) { + goto err; + } 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); + if (BUG(!ls)) { + goto err; + } 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 @@ -546,8 +550,9 @@ get_node_from_intro_point(const hs_service_intro_point_t *ip) 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); + if (BUG(!ls)) { + return NULL; + } /* XXX In the future, we want to only use the ed25519 ID (#22173). */ return node_get_by_id((const char *) ls->u.legacy_id); } @@ -1427,7 +1432,10 @@ pick_needed_intro_points(hs_service_t *service, * 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)); + const node_t *intro_node = get_node_from_intro_point(ip); + if (intro_node) { + smartlist_add(exclude_nodes, (void*)intro_node); + } } DIGEST256MAP_FOREACH_END; /* Also, add the failing intro points that our descriptor encounteered in * the exclude node list. */ @@ -2299,10 +2307,17 @@ service_intro_circ_has_opened(origin_circuit_t *circ) hs_service_descriptor_t *desc = NULL; tor_assert(circ); - tor_assert(circ->cpath); - /* Getting here means this is a v3 intro circuit. */ - tor_assert(circ->hs_ident); - tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* Let's do some basic sanity checking of the circ state */ + if (BUG(!circ->cpath)) { + return; + } + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + return; + } + if (BUG(!circ->hs_ident)) { + return; + } /* Get the corresponding service and intro point. */ get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); diff --git a/src/or/hs_service.h b/src/or/hs_service.h index cb2a7aa80e..cf2e1fa6f4 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -313,8 +313,8 @@ STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, hs_service_t **service, hs_service_intro_point_t **ip, hs_service_descriptor_t **desc); -STATIC const node_t *get_node_from_intro_point( - const hs_service_intro_point_t *ip); +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip); STATIC int can_service_launch_intro_circuit(hs_service_t *service, time_t now); STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 6d5ea7ed7d..2ad8393e84 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -540,7 +540,7 @@ test_helper_functions(void *arg) /* Testing get_node_from_intro_point() */ { const node_t *node = get_node_from_intro_point(ip); - tt_assert(node == &mock_node); + tt_ptr_op(node, OP_EQ, &mock_node); SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, hs_desc_link_specifier_t *, ls) { if (ls->type == LS_LEGACY_ID) { From 3bc52dae8932b42c809e2b233d5c194b74fa4f9b Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:49:42 +0300 Subject: [PATCH 63/91] Validate intro point limits to avoid asserts. --- src/or/hs_service.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 22739334dd..430fb36a52 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -346,12 +346,25 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) * term keys. */ ed25519_keypair_generate(&ip->auth_key_kp, 0); - ip->introduce2_max = - crypto_rand_int_range(get_intro_point_min_introduce2(), - get_intro_point_max_introduce2()); - ip->time_to_expire = time(NULL) + - crypto_rand_int_range(get_intro_point_min_lifetime(), - get_intro_point_max_lifetime()); + { /* Set introduce2 max cells limit */ + int32_t min_introduce2_cells = get_intro_point_min_introduce2(); + int32_t max_introduce2_cells = get_intro_point_max_introduce2(); + if (BUG(max_introduce2_cells < min_introduce2_cells)) { + goto err; + } + ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells, + max_introduce2_cells); + } + { /* Set intro point lifetime */ + int32_t intro_point_min_lifetime = get_intro_point_min_lifetime(); + int32_t intro_point_max_lifetime = get_intro_point_max_lifetime(); + if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { + goto err; + } + ip->time_to_expire = time(NULL) + + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); + } + ip->replay_cache = replaycache_new(0, 0); /* Initialize the base object. We don't need the certificate object. */ From 3ce69a58ce908ea88443c1321a7e4378f059a897 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:51:24 +0300 Subject: [PATCH 64/91] Rename some free() functions that are actually clear(). --- src/or/hs_descriptor.c | 2 +- src/or/hs_descriptor.h | 2 +- src/or/hs_intropoint.c | 5 +++-- src/or/hs_intropoint.h | 2 +- src/or/hs_service.c | 14 +++++++------- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index e18e25b26c..700d1b0cfc 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -2490,7 +2490,7 @@ hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) /* From the given descriptor, remove and free every introduction point. */ void -hs_descriptor_free_intro_points(hs_descriptor_t *desc) +hs_descriptor_clear_intro_points(hs_descriptor_t *desc) { smartlist_t *ips; diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 2f1b0160ef..d9c632b589 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -209,7 +209,7 @@ void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls); hs_desc_link_specifier_t *hs_desc_link_specifier_new( const extend_info_t *info, uint8_t type); -void hs_descriptor_free_intro_points(hs_descriptor_t *desc); +void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); int hs_desc_encode_descriptor(const hs_descriptor_t *desc, const ed25519_keypair_t *signing_kp, diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index 25c43b3ad3..a0453841f9 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -595,9 +595,10 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } -/* Free the given intropoint object ip. */ +/* Clear memory allocated by the given intropoint object ip (but don't free the + * object itself). */ void -hs_intro_free_content(hs_intropoint_t *ip) +hs_intropoint_clear(hs_intropoint_t *ip) { if (ip == NULL) { return; diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index 2e11de1b54..5c77f07ec3 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -51,7 +51,7 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)); int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); hs_intropoint_t *hs_intro_new(void); -void hs_intro_free_content(hs_intropoint_t *ip); +void hs_intropoint_clear(hs_intropoint_t *ip); #ifdef HS_INTROPOINT_PRIVATE diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 430fb36a52..2a3217a5b6 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -320,7 +320,7 @@ service_intro_point_free(hs_service_intro_point_t *ip) 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); + hs_intropoint_clear(&ip->base); tor_free(ip); } @@ -1141,7 +1141,7 @@ build_desc_intro_points(const hs_service_t *service, /* 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); + hs_descriptor_clear_intro_points(desc->desc); DIGEST256MAP_FOREACH(desc->intro_points.map, key, const hs_service_intro_point_t *, ip) { @@ -2125,7 +2125,7 @@ get_rev_counter_for_service(ed25519_public_key_t *blinded_pubkey) /** Update the value of the revision counter for hs_desc and save it on our state file. */ static void -update_descriptor_revision_counter(hs_descriptor_t *hs_desc) +increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) { /* Find stored rev counter if it exists */ uint64_t rev_counter = @@ -2212,7 +2212,7 @@ upload_descriptor_to_all(const hs_service_t *service, } /* Update the revision counter of this descriptor */ - update_descriptor_revision_counter(desc->desc); + increment_descriptor_revision_counter(desc->desc); smartlist_free(responsible_dirs); return; @@ -2517,8 +2517,8 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, * information when originally tried to be uploaded. This is called when our * directory information has changed. */ static void -consider_hsdir_retry(const hs_service_t *service, - hs_service_descriptor_t *desc) +consider_hsdir_upload_retry(const hs_service_t *service, + hs_service_descriptor_t *desc) { smartlist_t *responsible_dirs = NULL; smartlist_t *still_missing_dirs = NULL; @@ -2772,7 +2772,7 @@ hs_service_dir_info_changed(void) /* This cleans up the descriptor missing hsdir information list if a * successful upload is made or if any of the directory aren't * responsible anymore for the service descriptor. */ - consider_hsdir_retry(service, desc); + consider_hsdir_upload_retry(service, desc); } FOR_EACH_DESCRIPTOR_END; } FOR_EACH_SERVICE_END; } From 706392e6b5967c8a7766a6b68b2428a17c1bbe8f Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:52:01 +0300 Subject: [PATCH 65/91] Make HidServRevCounter be a LINELIST as it should. --- src/or/hs_service.c | 8 ++++---- src/or/or.h | 2 +- src/or/statefile.c | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 2a3217a5b6..86e7d40cb7 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2015,8 +2015,8 @@ update_revision_counters_in_state(void) } FOR_EACH_SERVICE_END; /* Remove the old rev counters, and replace them with the new ones */ - config_free_lines(state->HidServRevCounters); - state->HidServRevCounters = lines; + config_free_lines(state->HidServRevCounter); + state->HidServRevCounter = lines; /* Set the state as dirty since we just edited it */ if (!get_options()->AvoidDiskWrites) { @@ -2101,13 +2101,13 @@ get_rev_counter_for_service(ed25519_public_key_t *blinded_pubkey) /* Set default value for rev counters (if not found) to 0 */ uint64_t final_rev_counter = 0; - for (line = state->HidServRevCounters ; line ; line = line->next) { + for (line = state->HidServRevCounter ; line ; line = line->next) { int service_found = 0; uint64_t rev_counter = 0; tor_assert(!strcmp(line->key, "HidServRevCounter")); - /* Scan all the HidServRevCounters lines till we find the line for this + /* Scan all the HidServRevCounter lines till we find the line for this service: */ rev_counter = check_state_line_for_service_rev_counter(line->value, blinded_pubkey, diff --git a/src/or/or.h b/src/or/or.h index d8aea38278..14dd4e0d63 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4627,7 +4627,7 @@ typedef struct { config_line_t *TransportProxies; /** Cached revision counters for active hidden services on this host */ - config_line_t *HidServRevCounters; + config_line_t *HidServRevCounter; /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the diff --git a/src/or/statefile.c b/src/or/statefile.c index 6b759960cb..fc564ce146 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -85,8 +85,7 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), - VAR("HidServRevCounter", LINELIST_S, HidServRevCounters, NULL), - V(HidServRevCounters, LINELIST_V, NULL), + V(HidServRevCounter, LINELIST, NULL), V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), From d88984a137b9f06fd72f57636b6ec321044c8908 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 15:54:42 +0300 Subject: [PATCH 66/91] Improve setting hsdir index procedure. - Fix memleak. --- src/or/nodelist.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index c5a5979f39..abbe15ecc2 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -199,11 +199,6 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) current_time_period_num = hs_get_time_period_num(now); next_time_period_num = hs_get_next_time_period_num(now); - /* If NOT in overlap mode, we only need to compute the current hsdir index - * for the ongoing time period and thus the current SRV. If it can't be - * found, the disaster one is returned. */ - current_hsdir_index_srv = hs_get_current_srv(current_time_period_num); - if (hs_overlap_mode_is_active(ns, now)) { /* We are in overlap mode, this means that our consensus has just cycled * from current SRV to previous SRV so for the _next_ upcoming time @@ -214,6 +209,11 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) /* The following can be confusing so again, in overlap mode, we use our * previous SRV for our _current_ hsdir index. */ current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num); + } else { + /* If NOT in overlap mode, we only need to compute the current hsdir index + * for the ongoing time period and thus the current SRV. If it can't be + * found, the disaster one is returned. */ + current_hsdir_index_srv = hs_get_current_srv(current_time_period_num); } /* Build the current hsdir index. */ @@ -223,6 +223,8 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) /* Build the next hsdir index if we have a next SRV that we can use. */ hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv, next_time_period_num, node->hsdir_index->next); + } else { + memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next)); } done: From f106af3c41dffdc8576c52399a61d34116b78f38 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 16:00:18 +0300 Subject: [PATCH 67/91] Make ed25519 id keys optional for IPs and RPs. --- src/or/hs_circuit.c | 6 ++++-- src/or/hs_descriptor.c | 8 ++++++++ src/or/hs_service.c | 11 +++++++---- src/test/test_hs_service.c | 11 ++++++++++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index c78ac6057f..3d67f24cb8 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -406,7 +406,7 @@ get_rp_extend_info(const smartlist_t *link_specifiers, } SMARTLIST_FOREACH_END(ls); /* IPv4, legacy ID and ed25519 are mandatory. */ - if (!have_v4 || !have_legacy_id || !have_ed25519_id) { + if (!have_v4 || !have_legacy_id) { goto done; } /* By default, we pick IPv4 but this might change to v6 if certain @@ -451,7 +451,9 @@ get_rp_extend_info(const smartlist_t *link_specifiers, } /* We do have everything for which we think we can connect successfully. */ - info = extend_info_new(NULL, legacy_id, &ed25519_pk, NULL, onion_key, + info = extend_info_new(NULL, legacy_id, + have_ed25519_id ? &ed25519_pk : NULL, + NULL, onion_key, addr, port); done: return info; diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 700d1b0cfc..430e2f6f99 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -2471,9 +2471,17 @@ hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) ls->u.ap.port = info->port; break; case LS_LEGACY_ID: + /* Bug out if the identity digest is not set */ + if (BUG(tor_mem_is_zero(info->identity_digest, + sizeof(info->identity_digest)))) { + goto err; + } memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); break; case LS_ED25519_ID: + if (ed25519_public_key_is_zero(&info->ed_identity)) { + goto err; + } memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, sizeof(ls->u.ed25519_id)); break; diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 86e7d40cb7..a6f548d319 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -394,6 +394,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) goto err; } 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. */ @@ -401,11 +402,13 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) goto err; } smartlist_add(ip->base.link_specifiers, ls); + + /* ed25519 identity key is optional */ 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); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + /* IPv6 is optional. */ ls = hs_desc_link_specifier_new(ei, LS_IPV6); if (ls) { diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 2ad8393e84..4ee2cdac88 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1086,14 +1086,21 @@ test_build_update_descriptors(void *arg) ri.purpose = ROUTER_PURPOSE_GENERAL; /* Ugly yes but we never free the "ri" object so this just makes things * easier. */ - ri.protocol_list = (char *) "HSDir 1-2"; + ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3"; ret = curve25519_secret_key_generate(&curve25519_secret_key, 0); tt_int_op(ret, OP_EQ, 0); ri.onion_curve25519_pkey = tor_malloc_zero(sizeof(curve25519_public_key_t)); + ri.onion_pkey = crypto_pk_new(); curve25519_public_key_generate(ri.onion_curve25519_pkey, &curve25519_secret_key); memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + /* Setup ed25519 identity */ + ed25519_keypair_t kp1; + ed25519_keypair_generate(&kp1, 0); + ri.cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t)); + tt_assert(ri.cache_info.signing_key_cert); + ed25519_pubkey_copy(&ri.cache_info.signing_key_cert->signing_key, &kp1.pubkey); nodelist_set_routerinfo(&ri, NULL); node = node_get_mutable_by_id(ri.cache_info.identity_digest); tt_assert(node); @@ -1104,6 +1111,8 @@ test_build_update_descriptors(void *arg) setup_full_capture_of_logs(LOG_INFO); update_all_descriptors(now); tor_free(node->ri->onion_curve25519_pkey); /* Avoid memleak. */ + tor_free(node->ri->cache_info.signing_key_cert); + crypto_pk_free(node->ri->onion_pkey); expect_log_msg_containing("just picked 1 intro points and wanted 3. It " "currently has 0 intro points. Launching " "ESTABLISH_INTRO circuit shortly."); From 5ca9b830eacf46acde56469e5f19f977e5f02668 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 16:02:51 +0300 Subject: [PATCH 68/91] Improve documentation all around the codebase. --- src/or/hs_cell.h | 17 +++++++++----- src/or/hs_circuit.c | 18 +++++++++------ src/or/hs_common.c | 9 ++++---- src/or/hs_ident.h | 4 +++- src/or/hs_service.c | 47 ++++++++++++++++++++++++++------------ src/or/hs_service.h | 10 ++++---- src/or/or.h | 4 ++-- src/test/test_hs_common.c | 3 +++ src/test/test_hs_service.c | 12 ++++++++++ 9 files changed, 84 insertions(+), 40 deletions(-) diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h index b7c3d11275..f32f7a4216 100644 --- a/src/or/hs_cell.h +++ b/src/or/hs_cell.h @@ -20,22 +20,27 @@ typedef enum { /* This data structure contains data that we need to parse an INTRODUCE2 cell * which is used by the INTRODUCE2 cell parsing function. On a successful * parsing, the onion_pk and rendezvous_cookie will be populated with the - * computed key material from the cell data. */ + * computed key material from the cell data. This structure is only used during + * INTRO2 parsing and discarded after that. */ typedef struct hs_cell_introduce2_data_t { - /*** Immutable Section. ***/ + /*** Immutable Section: Set on structure init. ***/ - /* Introduction point authentication public key. */ + /* Introduction point authentication public key. Pointer owned by the + introduction point object through which we received the INTRO2 cell. */ const ed25519_public_key_t *auth_pk; - /* Introduction point encryption keypair for the ntor handshake. */ + /* Introduction point encryption keypair for the ntor handshake. Pointer + owned by the introduction point object through which we received the + INTRO2 cell*/ const curve25519_keypair_t *enc_kp; - /* Subcredentials of the service. */ + /* Subcredentials of the service. Pointer owned by the descriptor that owns + the introduction point through which we received the INTRO2 cell. */ const uint8_t *subcredential; /* Payload of the received encoded cell. */ const uint8_t *payload; /* Size of the payload of the received encoded cell. */ size_t payload_len; - /*** Muttable Section. ***/ + /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ /* Onion public key computed using the INTRODUCE2 encrypted section. */ curve25519_public_key_t onion_pk; diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 3d67f24cb8..2a753f659d 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -233,7 +233,7 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, return count; } -/* From a given service, rendezvous cookie and handshake infor, create a +/* From a given service, rendezvous cookie and handshake info, create a * rendezvous point circuit identifier. This can't fail. */ static hs_ident_circuit_t * create_rp_circuit_identifier(const hs_service_t *service, @@ -351,7 +351,7 @@ send_establish_intro(const hs_service_t *service, * if direct_conn, IPv6 is prefered if we have one available. * if firewall does not allow the chosen address, error. * - * Return NULL if we can fulfill the conditions. */ + * Return NULL if we can't fulfill the conditions. */ static extend_info_t * get_rp_extend_info(const smartlist_t *link_specifiers, const curve25519_public_key_t *onion_key, int direct_conn) @@ -778,6 +778,8 @@ hs_circ_service_intro_has_opened(hs_service_t *service, tor_assert(desc); tor_assert(circ); + /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already + * established introduction circuits */ num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); num_needed_circ = service->config.num_intro_points; if (num_intro_circ > num_needed_circ) { @@ -879,8 +881,9 @@ hs_circ_service_rp_has_opened(const hs_service_t *service, memwipe(payload, 0, sizeof(payload)); } -/* Handle an INTRO_ESTABLISHED cell payload of length payload_len arriving on - * the given introduction circuit circ. The service is only used for logging +/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle + * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the + * given introduction circuit circ. The service is only used for logging * purposes. Return 0 on success else a negative value. */ int hs_circ_handle_intro_established(const hs_service_t *service, @@ -919,9 +922,10 @@ hs_circ_handle_intro_established(const hs_service_t *service, return ret; } -/* Handle an INTRODUCE2 unparsed payload of payload_len for the given circuit - * and service. This cell is associated with the intro point object ip and the - * subcredential. Return 0 on success else a negative value. */ +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the INTRODUCE2 payload of size payload_len for the given + * circuit and service. This cell is associated with the intro point object ip + * and the subcredential. Return 0 on success else a negative value. */ int hs_circ_handle_introduce2(const hs_service_t *service, const origin_circuit_t *circ, diff --git a/src/or/hs_common.c b/src/or/hs_common.c index d68c446fd0..570c1132c3 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -669,7 +669,8 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, } /* Using the given identity public key and a blinded public key, compute the - * subcredential and put it in subcred_out. This can't fail. */ + * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * This can't fail. */ void hs_get_subcredential(const ed25519_public_key_t *identity_pk, const ed25519_public_key_t *blinded_pk, @@ -707,9 +708,9 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, memwipe(credential, 0, sizeof(credential)); } -/* From the given list of hidden service ports, find the matching one from the - * given edge connection conn and set the connection address from the service - * port object. Return 0 on success or -1 if none. */ +/* From the given list of hidden service ports, find the ones that much the + * given edge connection conn, pick one at random and use it to set the + * connection address. Return 0 on success or -1 if none. */ int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) { diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h index a3ebd07da4..e259fde54d 100644 --- a/src/or/hs_ident.h +++ b/src/or/hs_ident.h @@ -53,7 +53,9 @@ typedef struct hs_ident_circuit_t { hs_ident_circuit_type_t circuit_type; /* (All circuit) Introduction point authentication key. It's also needed on - * the rendezvous circuit for the ntor handshake. */ + * the rendezvous circuit for the ntor handshake. It's used as the unique key + * of the introduction point so it should not be shared between multiple + * intro points. */ ed25519_public_key_t intro_auth_pk; /* (Only client rendezvous circuit) Introduction point encryption public diff --git a/src/or/hs_service.c b/src/or/hs_service.c index a6f548d319..b46fd6329d 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -206,8 +206,9 @@ service_clear_config(hs_service_config_t *config) memset(config, 0, sizeof(*config)); } -/* Return the number of minimum INTRODUCE2 cell defined by a consensus - * parameter or the default value. */ +/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ static int32_t get_intro_point_min_introduce2(void) { @@ -218,8 +219,9 @@ get_intro_point_min_introduce2(void) 0, INT32_MAX); } -/* Return the number of maximum INTRODUCE2 cell defined by a consensus - * parameter or the default value. */ +/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ static int32_t get_intro_point_max_introduce2(void) { @@ -230,8 +232,8 @@ get_intro_point_max_introduce2(void) 0, INT32_MAX); } -/* Return the minimum lifetime of an introduction point defined by a consensus - * parameter or the default value. */ +/* Return the minimum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ static int32_t get_intro_point_min_lifetime(void) { @@ -247,8 +249,8 @@ get_intro_point_min_lifetime(void) 0, INT32_MAX); } -/* Return the maximum lifetime of an introduction point defined by a consensus - * parameter or the default value. */ +/* Return the maximum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ static int32_t get_intro_point_max_lifetime(void) { @@ -466,7 +468,16 @@ service_intro_point_find(const hs_service_t *service, tor_assert(service); tor_assert(auth_key); - /* Trying all descriptors. */ + /* Trying all descriptors to find the right intro point. + * + * Even if we use the same node as intro point in both descriptors, the node + * will have a different intro auth key for each descriptor since we generate + * a new one everytime we pick an intro point. + * + * After #22893 gets implemented, intro points will be moved to be + * per-service instead of per-descriptor so this function will need to + * change. + */ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { if ((ip = digest256map_get(desc->intro_points.map, auth_key->pubkey)) != NULL) { @@ -1647,6 +1658,10 @@ rotate_service_descriptors(hs_service_t *service) STATIC void rotate_all_descriptors(time_t now) { + /* XXX We rotate all our service descriptors at once. In the future it might + * be wise, to rotate service descriptors independently to hide that all + * those descriptors are on the same tor instance */ + FOR_EACH_SERVICE_BEGIN(service) { /* We are _not_ in the overlap period so skip rotation. */ if (!hs_overlap_mode_is_active(NULL, now)) { @@ -2379,7 +2394,7 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ) tor_assert(circ); tor_assert(circ->cpath); - /* Getting here means this is a v3 intro circuit. */ + /* Getting here means this is a v3 rendezvous circuit. */ tor_assert(circ->hs_ident); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); @@ -2411,8 +2426,9 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ) return; } -/* Handle an INTRO_ESTABLISHED cell arriving on the given introduction - * circuit. Return 0 on success else a negative value. */ +/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just + * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given + * introduction circuit. Return 0 on success else a negative value. */ static int service_handle_intro_established(origin_circuit_t *circ, const uint8_t *payload, @@ -2446,7 +2462,8 @@ service_handle_intro_established(origin_circuit_t *circ, } /* Try to parse the payload into a cell making sure we do actually have a - * valid cell. On success, the ip object is updated. */ + * valid cell. On success, the ip object and circuit purpose is updated to + * reflect the fact that the introduction circuit is established. */ if (hs_circ_handle_intro_established(service, ip, circ, payload, payload_len) < 0) { goto err; @@ -2467,8 +2484,8 @@ service_handle_intro_established(origin_circuit_t *circ, return -1; } -/* Handle an INTRODUCE2 cell arriving on the given introduction circuit. - * Return 0 on success else a negative value. */ +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the cell and return 0 on success else a negative value. */ static int service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len) diff --git a/src/or/hs_service.h b/src/or/hs_service.h index cf2e1fa6f4..1ab0c1a4f4 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -123,11 +123,11 @@ typedef struct hs_service_descriptor_t { * couldn't pick any nodes. */ unsigned int missing_intro_points : 1; - /* List of hidden service directories node_t object to which we couldn't - * upload this descriptor because we didn't have its router descriptor at - * the time. If this list is non-empty, only the relays in this list are - * re-tried to upload this descriptor when our directory information have - * been updated. */ + /* List of identity digests for hidden service directories to which we + * couldn't upload this descriptor because we didn't have its router + * descriptor at the time. If this list is non-empty, only the relays in this + * list are re-tried to upload this descriptor when our directory information + * have been updated. */ smartlist_t *hsdir_missing_info; } hs_service_descriptor_t; diff --git a/src/or/or.h b/src/or/or.h index 14dd4e0d63..1e498aecf5 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -421,9 +421,9 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -/** A connetion to a hidden service directory: upload a descriptor. */ +/** A connection to a hidden service directory: upload a v3 descriptor. */ #define DIR_PURPOSE_UPLOAD_HSDESC 20 -/** A connetion to a hidden service directory: fetch a descriptor. */ +/** A connection to a hidden service directory: fetch a v3 descriptor. */ #define DIR_PURPOSE_FETCH_HSDESC 21 #define DIR_PURPOSE_MAX_ 21 diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index a5c499112e..60577a2a50 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -16,6 +16,7 @@ #include "hs_common.h" #include "config.h" +/** Test the validation of HS v3 addresses */ static void test_validate_address(void *arg) { @@ -69,6 +70,7 @@ test_validate_address(void *arg) ; } +/** Test building HS v3 onion addresses */ static void test_build_address(void *arg) { @@ -133,6 +135,7 @@ test_time_period(void *arg) ; } +/** Test that we can correctly find the start time of the next time period */ static void test_start_time_of_next_time_period(void *arg) { diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 4ee2cdac88..10eeaa4321 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -397,6 +397,7 @@ test_access_service(void *arg) hs_free_all(); } +/** Test that we can create intro point objects, index them and find them */ static void test_service_intro_point(void *arg) { @@ -609,6 +610,7 @@ test_helper_functions(void *arg) UNMOCK(node_get_by_id); } +/** Test that we do the right operations when an intro circuit opens */ static void test_intro_circuit_opened(void *arg) { @@ -666,6 +668,8 @@ test_intro_circuit_opened(void *arg) UNMOCK(relay_send_command_from_edge_); } +/** Test the operations we do on a circuit after we learn that we successfuly + * established an intro point on it */ static void test_intro_established(void *arg) { @@ -735,6 +739,8 @@ test_intro_established(void *arg) UNMOCK(circuit_mark_for_close_); } +/** Check the operations we do on a rendezvous circuit after we learn it's + * open */ static void test_rdv_circuit_opened(void *arg) { @@ -776,6 +782,7 @@ test_rdv_circuit_opened(void *arg) UNMOCK(relay_send_command_from_edge_); } +/** Test sending and receiving introduce2 cells */ static void test_introduce2(void *arg) { @@ -852,6 +859,8 @@ test_introduce2(void *arg) UNMOCK(circuit_mark_for_close_); } +/** Test basic hidden service housekeeping operations (maintaining intro + * points, etc) */ static void test_service_event(void *arg) { @@ -933,6 +942,7 @@ test_service_event(void *arg) UNMOCK(circuit_mark_for_close_); } +/** Test that we rotate descriptors correctly in overlap period */ static void test_rotate_descriptors(void *arg) { @@ -1013,6 +1023,8 @@ test_rotate_descriptors(void *arg) UNMOCK(networkstatus_get_live_consensus); } +/** Test building descriptors: picking intro points, setting up their link + * specifiers, etc. */ static void test_build_update_descriptors(void *arg) { From 2c6f2e9be9db6d3889a0756be93d7203888eaa72 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 16:03:30 +0300 Subject: [PATCH 69/91] Constify functions that can be constified. --- src/or/hs_circuit.c | 4 ++-- src/or/hs_service.c | 6 +++--- src/or/hs_service.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 2a753f659d..75c946799f 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -214,8 +214,8 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, DIGEST256MAP_FOREACH(desc->intro_points.map, key, const hs_service_intro_point_t *, ip) { - circuit_t *circ; - origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + const circuit_t *circ; + const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); if (ocirc == NULL) { continue; } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index b46fd6329d..592b1f2552 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2048,8 +2048,8 @@ update_revision_counters_in_state(void) * counter. Else set service_found_out to False. */ STATIC uint64_t check_state_line_for_service_rev_counter(const char *state_line, - ed25519_public_key_t *blinded_pubkey, - int *service_found_out) + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out) { smartlist_t *items = NULL; int ok; @@ -2111,7 +2111,7 @@ check_state_line_for_service_rev_counter(const char *state_line, * service with blinded key blinded_pubkey. If no revision counter is * found, return 0. */ static uint64_t -get_rev_counter_for_service(ed25519_public_key_t *blinded_pubkey) +get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey) { or_state_t *state = get_or_state(); config_line_t *line; diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 1ab0c1a4f4..93d2710cdc 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -332,8 +332,8 @@ STATIC void service_descriptor_free(hs_service_descriptor_t *desc); STATIC uint64_t check_state_line_for_service_rev_counter(const char *state_line, - ed25519_public_key_t *blinded_pubkey, - int *service_found_out); + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out); #endif /* TOR_UNIT_TESTS */ From a561a10da726b426d326515ca7f75988b405bab7 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 16:04:25 +0300 Subject: [PATCH 70/91] Fix small easy bugs all around - Fix log message format string. - Do extra circuit purpose check. - wipe memory in a clear function - Make sure we don't double add intro points in our list - Make sure we don't double close intro circuits. - s/tt_u64_op/tt_i64_op/ --- src/or/hs_cell.c | 4 ++-- src/or/hs_circuit.c | 4 ++++ src/or/hs_intropoint.c | 1 + src/or/hs_service.c | 12 ++++++++---- src/test/test_hs_cell.c | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 922ff73468..064e26334d 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -169,9 +169,9 @@ parse_introduce2_encrypted(const uint8_t *decrypted_data, if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != CURVE25519_PUBKEY_LEN) { - log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %ld but " + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but " "expected %d on circuit %u for service %s", - trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); goto err; diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 75c946799f..527439599a 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -898,6 +898,10 @@ hs_circ_handle_intro_established(const hs_service_t *service, tor_assert(circ); tor_assert(payload); + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + goto done; + } + /* Try to parse the payload into a cell making sure we do actually have a * valid cell. For a legacy node, it's an empty payload so as long as we * have the cell, we are good. */ diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index a0453841f9..644611feab 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -607,5 +607,6 @@ hs_intropoint_clear(hs_intropoint_t *ip) SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, hs_desc_link_specifier_free(ls)); smartlist_free(ip->link_specifiers); + memset(ip, 0, sizeof(hs_intropoint_t)); } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 592b1f2552..f1592efa81 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -433,14 +433,18 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) STATIC void service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) { + hs_service_intro_point_t *old_ip_entry; + tor_assert(map); tor_assert(ip); - digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + /* Make sure we didn't just try to double-add an intro point */ + tor_assert_nonfatal(!old_ip_entry); } -/* For a given service, remove the intro point from that service which will - * look in both descriptors. */ +/* For a given service, remove the intro point from that service's descriptors + * (check both current and next descriptor) */ STATIC void service_intro_point_remove(const hs_service_t *service, const hs_service_intro_point_t *ip) @@ -1623,7 +1627,7 @@ cleanup_intro_points(hs_service_t *service, time_t now) * 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) { + if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) { /* After this, no new cells will be handled on the circuit. */ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); } diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c index 9c963bcb16..1686fccee7 100644 --- a/src/test/test_hs_cell.c +++ b/src/test/test_hs_cell.c @@ -106,7 +106,7 @@ test_gen_establish_intro_cell_bad(void *arg) expect_log_msg_containing("Unable to make signature for " "ESTABLISH_INTRO cell."); teardown_capture_of_logs(); - tt_u64_op(cell_len, OP_EQ, -1); + tt_i64_op(cell_len, OP_EQ, -1); done: trn_cell_establish_intro_free(cell); From 440eaa9b22573cdb0d38bf5c13200cc1077a453f Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 3 Aug 2017 16:08:17 +0300 Subject: [PATCH 71/91] Correctly assign HSDir flags based on protocol list In Nick's words: "We want to always return false if the platform is a Tor version, and it is not as new as 0.3.0.8 -- but if the platform is not a Tor version, or if the version is as new as 0.3.0.8, then we want to obey the protocol list. That way, other implementations of our protocol won't have to claim any particular Tor version, and future versions of Tor will have the freedom to drop this protocol in the distant future." --- src/or/nodelist.c | 9 +++++++-- src/or/routerparse.c | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index abbe15ecc2..e900b51455 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -800,9 +800,14 @@ node_supports_v3_hsdir(const node_t *node) if (node->ri->protocol_list == NULL) { return 0; } + /* Bug #22447 forces us to filter on tor version: + * If platform is a Tor version, and older than 0.3.0.8, return False. + * Else, obey the protocol list. */ if (node->ri->platform) { - /* Bug #22447 forces us to filter on this version. */ - return tor_version_as_new_as(node->ri->platform, "0.3.0.8"); + if (!strcmpstart(node->ri->platform, "Tor ") && + !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) { + return 0; + } } return protocol_list_supports_protocol(node->ri->protocol_list, PRT_HSDIR, PROTOVER_HSDIR_V3); diff --git a/src/or/routerparse.c b/src/or/routerparse.c index ec63aef4d4..db42a44ee4 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -2706,7 +2706,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->supports_ed25519_hs_intro = protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); rs->supports_v3_hsdir = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2); + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HSDIR_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -2720,8 +2721,9 @@ routerstatus_parse_entry_from_string(memarea_t *area, } if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { /* Bug #22447 forces us to filter on this version. */ - rs->supports_v3_hsdir = - tor_version_as_new_as(tok->args[0], "0.3.0.8"); + if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { + rs->supports_v3_hsdir = 0; + } } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); From b89d2fa1db2379bffd2e2b4c851c3facc57b6ed8 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 12:21:14 +0300 Subject: [PATCH 72/91] Don't set HSDir index if we don't have a live consensus. We also had to alter the SRV functions to take a consensus as optional input, since we might be setting our HSDir index using a consensus that is currently being processed and won't be returned by the networkstatus_get_live_consensus() function. This change has two results: a) It makes sure we are using a fresh consensus with the right SRV value when we are calculating the HSDir hash ring. b) It ensures that we will not use the sr_get_current/previous() functions when we don't have a consensus which would have falsely triggered the disaster SRV logic. --- src/or/hs_common.c | 11 ++++++----- src/or/hs_common.h | 6 ++++-- src/or/networkstatus.c | 15 +++++++++++---- src/or/networkstatus.h | 1 + src/or/nodelist.c | 13 +++++++++---- src/or/shared_random.c | 38 ++++++++++++++++++++++++++++++-------- src/or/shared_random.h | 4 ++-- 7 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 570c1132c3..2894d0a286 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1058,12 +1058,13 @@ hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, /* Return a newly allocated buffer containing the current shared random value * or if not present, a disaster value is computed using the given time period - * number. This function can't fail. */ + * number. If a consensus is provided in ns, use it to get the SRV + * value. This function can't fail. */ uint8_t * -hs_get_current_srv(uint64_t time_period_num) +hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) { uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); - const sr_srv_t *current_srv = sr_get_current(); + const sr_srv_t *current_srv = sr_get_current(ns); if (current_srv) { memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); @@ -1078,10 +1079,10 @@ hs_get_current_srv(uint64_t time_period_num) * value or if not present, a disaster value is computed using the given time * period number. This function can't fail. */ uint8_t * -hs_get_previous_srv(uint64_t time_period_num) +hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) { uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); - const sr_srv_t *previous_srv = sr_get_previous(); + const sr_srv_t *previous_srv = sr_get_previous(ns); if (previous_srv) { memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 268a69bb52..7e37f81e5c 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -200,8 +200,10 @@ link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); MOCK_DECL(int, hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)); -uint8_t *hs_get_current_srv(uint64_t time_period_num); -uint8_t *hs_get_previous_srv(uint64_t time_period_num); +uint8_t *hs_get_current_srv(uint64_t time_period_num, + const networkstatus_t *ns); +uint8_t *hs_get_previous_srv(uint64_t time_period_num, + const networkstatus_t *ns); void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, const uint8_t *srv, uint64_t period_num, diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index aff36b4c0b..82ceb8a9eb 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1393,14 +1393,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) MOCK_IMPL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)) { - if (networkstatus_get_latest_consensus() && - networkstatus_get_latest_consensus()->valid_after <= now && - now <= networkstatus_get_latest_consensus()->valid_until) - return networkstatus_get_latest_consensus(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && networkstatus_is_live(ns, now)) + return ns; else return NULL; } +/** Given a consensus in ns, validate that it's currently live and + * unexpired. */ +int +networkstatus_is_live(const networkstatus_t *ns, time_t now) +{ + return (ns->valid_after <= now && now <= ns->valid_until); +} + /** Determine if consensus is valid or expired recently enough that * we can still use it. * diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index e774c4d266..f9320747d2 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -81,6 +81,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); +int networkstatus_is_live(const networkstatus_t *ns, time_t now); int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); int networkstatus_valid_until_is_reasonably_live(time_t valid_until, diff --git a/src/or/nodelist.c b/src/or/nodelist.c index e900b51455..0fcaea626d 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -179,7 +179,7 @@ node_get_or_create(const char *identity_digest) static void node_set_hsdir_index(node_t *node, const networkstatus_t *ns) { - time_t now = time(NULL); + time_t now = approx_time(); const ed25519_public_key_t *node_identity_pk; uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL; uint64_t next_time_period_num, current_time_period_num; @@ -187,6 +187,11 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) tor_assert(node); tor_assert(ns); + if (!networkstatus_is_live(ns, now)) { + log_info(LD_GENERAL, "Not setting hsdir index with a non-live consensus."); + goto done; + } + node_identity_pk = node_get_ed25519_id(node); if (node_identity_pk == NULL) { log_debug(LD_GENERAL, "ed25519 identity public key not found when " @@ -205,15 +210,15 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) * period, we have to use the current SRV and use the previous SRV for the * current time period. If the current or previous SRV can't be found, the * disaster one is returned. */ - next_hsdir_index_srv = hs_get_current_srv(next_time_period_num); + next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns); /* The following can be confusing so again, in overlap mode, we use our * previous SRV for our _current_ hsdir index. */ - current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num); + current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns); } else { /* If NOT in overlap mode, we only need to compute the current hsdir index * for the ongoing time period and thus the current SRV. If it can't be * found, the disaster one is returned. */ - current_hsdir_index_srv = hs_get_current_srv(current_time_period_num); + current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns); } /* Build the current hsdir index. */ diff --git a/src/or/shared_random.c b/src/or/shared_random.c index ec2533dad2..e4ee64139a 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1393,11 +1393,22 @@ sr_get_previous_for_control(void) /* Return current shared random value from the latest consensus. Caller can * NOT keep a reference to the returned pointer. Return NULL if none. */ const sr_srv_t * -sr_get_current(void) +sr_get_current(const networkstatus_t *ns) { - const networkstatus_t *c = networkstatus_get_latest_consensus(); - if (c) { - return c->sr_info.current_srv; + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.current_srv; } return NULL; } @@ -1405,11 +1416,22 @@ sr_get_current(void) /* Return previous shared random value from the latest consensus. Caller can * NOT keep a reference to the returned pointer. Return NULL if none. */ const sr_srv_t * -sr_get_previous(void) +sr_get_previous(const networkstatus_t *ns) { - const networkstatus_t *c = networkstatus_get_latest_consensus(); - if (c) { - return c->sr_info.previous_srv; + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.previous_srv; } return NULL; } diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 58ea360df0..76d5b95422 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -130,8 +130,8 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp, char *sr_get_current_for_control(void); char *sr_get_previous_for_control(void); -const sr_srv_t *sr_get_current(void); -const sr_srv_t *sr_get_previous(void); +const sr_srv_t *sr_get_current(const networkstatus_t *ns); +const sr_srv_t *sr_get_previous(const networkstatus_t *ns); #ifdef SHARED_RANDOM_PRIVATE From 4ad4467fa13a0e6333fa0016a63060d5b9dd9715 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 12:37:48 +0300 Subject: [PATCH 73/91] Don't double hash the ed25519 blind key parameter. We used to do: h = H(BLIND_STRING | H(A | s | B | N ) when we should be doing: h = H(BLIND_STRING | A | s | B | N) Change the logic so that hs_common.c does the hashing, and our ed25519 libraries just receive the hashed parameter ready-made. That's easier than doing the hashing on the ed25519 libraries, since that means we would have to pass them a variable-length param (depending on whether 's' is set or not). Also fix the ed25519 test vectors since they were also double hashing. --- src/ext/ed25519/donna/ed25519_tor.c | 8 +------- src/ext/ed25519/ref10/blinding.c | 4 ++-- src/or/hs_common.c | 4 +++- src/test/ed25519_exts_ref.py | 6 ++---- src/test/ed25519_vectors.inc | 32 ++++++++++++++--------------- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/ext/ed25519/donna/ed25519_tor.c b/src/ext/ed25519/donna/ed25519_tor.c index 6bc22675ae..44ec562f02 100644 --- a/src/ext/ed25519/donna/ed25519_tor.c +++ b/src/ext/ed25519/donna/ed25519_tor.c @@ -245,13 +245,7 @@ ed25519_donna_sign(unsigned char *sig, const unsigned char *m, size_t mlen, static void ed25519_donna_gettweak(unsigned char *out, const unsigned char *param) { - static const char str[] = "Derive temporary signing key"; - ed25519_hash_context ctx; - - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, (const unsigned char*)str, strlen(str)); - ed25519_hash_update(&ctx, param, 32); - ed25519_hash_final(&ctx, out); + memcpy(out, param, 32); out[0] &= 248; /* Is this necessary ? */ out[31] &= 63; diff --git a/src/ext/ed25519/ref10/blinding.c b/src/ext/ed25519/ref10/blinding.c index 31332a2719..a3b32fa80c 100644 --- a/src/ext/ed25519/ref10/blinding.c +++ b/src/ext/ed25519/ref10/blinding.c @@ -12,8 +12,8 @@ static void ed25519_ref10_gettweak(unsigned char *out, const unsigned char *param) { - const char str[] = "Derive temporary signing key"; - crypto_hash_sha512_2(out, (const unsigned char*)str, strlen(str), param, 32); + memcpy(out, param, 32); + out[0] &= 248; /* Is this necessary necessary ? */ out[31] &= 63; out[31] |= 64; diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 2894d0a286..a29b377494 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -551,6 +551,7 @@ build_blinded_key_param(const ed25519_public_key_t *pubkey, uint8_t *param_out) { size_t offset = 0; + const char blind_str[] = "Derive temporary signing key"; uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; crypto_digest_t *digest; @@ -568,8 +569,9 @@ build_blinded_key_param(const ed25519_public_key_t *pubkey, tor_assert(offset == HS_KEYBLIND_NONCE_LEN); /* Generate the parameter h and the construction is as follow: - * h = H(pubkey | [secret] | ed25519-basepoint | nonce) */ + * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */ digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str)); crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); /* Optional secret. */ if (secret) { diff --git a/src/test/ed25519_exts_ref.py b/src/test/ed25519_exts_ref.py index 1898256540..f84d3002d3 100644 --- a/src/test/ed25519_exts_ref.py +++ b/src/test/ed25519_exts_ref.py @@ -32,8 +32,7 @@ def curve25519ToEd25519(c, sign): return encodepoint([x,y]) def blindESK(esk, param): - h = H("Derive temporary signing key" + param) - mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) + mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2)) s = decodeint(esk[:32]) s_prime = (s * mult) % ell k = esk[32:] @@ -42,8 +41,7 @@ def blindESK(esk, param): return encodeint(s_prime) + k_prime def blindPK(pk, param): - h = H("Derive temporary signing key" + param) - mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) + mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2)) P = decodepoint(pk) return encodepoint(scalarmult(P, mult)) diff --git a/src/test/ed25519_vectors.inc b/src/test/ed25519_vectors.inc index 760bafb971..60c863beba 100644 --- a/src/test/ed25519_vectors.inc +++ b/src/test/ed25519_vectors.inc @@ -91,21 +91,21 @@ static const char *ED25519_BLINDING_PARAMS[] = { * blinding parameter. */ static const char *ED25519_BLINDED_SECRET_KEYS[] = { - "014e83abadb2ca9a27e0ffe23920333d817729f48700e97656ec2823d694050e171d43" + "293c3acff4e902f6f63ddc5d5caa2a57e771db4f24de65d4c28df3232f47fa01171d43" "f24e3f53e70ec7ac280044ac77d4942dee5d6807118a59bdf3ee647e89", - "fad8cca0b4335847795288b1452508752b253e64e6c7c78d4a02dbbd7d46aa0eb8ceff" + "38b88f9f9440358da544504ee152fb475528f7c51c285bd1c68b14ade8e29a07b8ceff" "20dfcf53eb52b891fc078c934efbf0353af7242e7dc51bb32a093afa29", - "116eb0ae0a4a91763365bdf86db427b00862db448487808788cc339ac10e5e089217f5" + "4d03ce16a3f3249846aac9de0a0075061495c3b027248eeee47da4ddbaf9e0049217f5" "2e92797462bd890fc274672e05c98f2c82970d640084781334aae0f940", - "bd1fbb0ee5acddc4adbcf5f33e95d9445f40326ce579fdd764a24483a9ccb20f509ece" + "51d7db01aaa0d937a9fd7c8c7381445a14d8fa61f43347af5460d7cd8fda9904509ece" "e77082ce088f7c19d5a00e955eeef8df6fa41686abc1030c2d76807733", - "237f5345cefe8573ce9fa7e216381a1172796c9e3f70668ab503b1352952530fb57b95" + "1f76cab834e222bd2546efa7e073425680ab88df186ff41327d3e40770129b00b57b95" "a440570659a440a3e4771465022a8e67af86bdf2d0990c54e7bb87ff9a", - "ba8ff23bc4ad2b739e1ccffc9fbc7837053ea81cdfdb15073f56411cfbae1d0ec492fc" + "c23588c23ee76093419d07b27c6df5922a03ac58f96c53671456a7d1bdbf560ec492fc" "87d5ec2a1b185ca5a40541fdef0b1e128fd5c2380c888bfa924711bcab", - "0fa68f969de038c7a90a4a74ee6167c77582006f2dedecc1956501ba6b6fb10391b476" + "3ed249c6932d076e1a2f6916975914b14e8c739da00992358b8f37d3e790650691b476" "8f8e556d78f4bdcb9a13b6f6066fe81d3134ae965dc48cd0785b3af2b8", - "deaa3456d1c21944d5dcd361a646858c6cf9336b0a6851d925717eb1ae186902053d9c" + "288cbfd923cb286d48c084555b5bdd06c05e92fb81acdb45271367f57515380e053d9c" "00c81e1331c06ab50087be8cfc7dc11691b132614474f1aa9c2503cccd", }; @@ -115,14 +115,14 @@ static const char *ED25519_BLINDED_SECRET_KEYS[] = { * blinding parameter. */ static const char *ED25519_BLINDED_PUBLIC_KEYS[] = { - "722d6da6348e618967ef782e71061e27163a8b35f21856475d9d2023f65b6495", - "1dffa0586da6cbfcff2024eedf4fc6c818242d9a82dbbe635d6da1b975a1160d", - "5ed81f98fed5a6acda4ea6da2c34fab0ab359d950c510c256473f1f33ff438b4", - "6e6f92a54fb282120c46d9603df41135f025bc1f58f283809d04be96aeb04040", - "cda236f28edc4c7e02d18007b8dab49d669265b0f7aefb1824d7cc8e73a2cd63", - "367b03b17b67ca7329b89a520bdab91782402a41cd67264e34b5541a4b3f875b", - "8d486b03ac4e3b486b7a1d563706c7fdac75aee789a7cf6f22789eedeff61a31", - "9f297ff0aa2ceda91c5ab1b6446f12533d145940de6d850dc323417afde0cb78", + "1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41", + "1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e", + "c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028", + "3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f", + "59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d", + "2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9", + "c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e", + "0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac", }; /** From c62da5cf95de0958d28d2dfd8cdc66cf11498ebe Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 22:02:28 +0300 Subject: [PATCH 74/91] Improve code based on Nick review: - Fix some more crazy ternary ops. - Fix the order of disaster SRV computation. - Whitespace fixes. - Remove a redundant warn. - Better docs. --- src/or/hs_circuit.c | 7 +++++-- src/or/hs_common.c | 4 ++-- src/or/hs_descriptor.c | 1 - src/or/hs_service.c | 4 ++-- src/or/networkstatus.c | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 527439599a..faceb731c5 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -699,8 +699,11 @@ hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) circ->hs_service_side_rend_circ_has_been_relaunched = 1; /* Legacy service don't have an hidden service ident. */ - (circ->hs_ident) ? retry_service_rendezvous_point(circ) : - rend_service_relaunch_rendezvous(circ); + if (circ->hs_ident) { + retry_service_rendezvous_point(circ); + } else { + rend_service_relaunch_rendezvous(circ); + } done: return; diff --git a/src/or/hs_common.c b/src/or/hs_common.c index a29b377494..f5c63cb6a0 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -521,9 +521,9 @@ get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) uint64_t time_period_length = get_time_period_length(); char period_stuff[sizeof(uint64_t)*2]; size_t offset = 0; - set_uint64(period_stuff, tor_htonll(time_period_num)); + set_uint64(period_stuff, tor_htonll(time_period_length)); offset += sizeof(uint64_t); - set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + set_uint64(period_stuff+offset, tor_htonll(time_period_num)); offset += sizeof(uint64_t); tor_assert(offset == sizeof(period_stuff)); diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 430e2f6f99..0b10f205a5 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -1007,7 +1007,6 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(desc->plaintext_data.version == 3); if (BUG(desc->subcredential == NULL)) { - log_warn(LD_GENERAL, "Asked to encode desc with no subcred. No!"); goto err; } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index f1592efa81..bea760837b 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2881,14 +2881,14 @@ hs_service_circuit_has_opened(origin_circuit_t *circ) if (circ->hs_ident) { service_intro_circ_has_opened(circ); } else { - rend_service_intro_has_opened(circ); + rend_service_intro_has_opened(circ); } break; case CIRCUIT_PURPOSE_S_CONNECT_REND: if (circ->hs_ident) { service_rendezvous_circ_has_opened(circ); } else { - rend_service_rendezvous_has_opened(circ); + rend_service_rendezvous_has_opened(circ); } break; default: diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 82ceb8a9eb..69bff55cff 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -1400,7 +1400,7 @@ networkstatus_get_live_consensus,(time_t now)) return NULL; } -/** Given a consensus in ns, validate that it's currently live and +/** Given a consensus in ns, return true iff currently live and * unexpired. */ int networkstatus_is_live(const networkstatus_t *ns, time_t now) From a464d49aeb66ad2daa6b753c34ceadfe0b391490 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 22:53:53 +0300 Subject: [PATCH 75/91] prop224 tests: test_gen_establish_intro_cell() check cell contents. --- src/test/test_hs_cell.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c index 1686fccee7..1b3c788a67 100644 --- a/src/test/test_hs_cell.c +++ b/src/test/test_hs_cell.c @@ -30,7 +30,6 @@ test_gen_establish_intro_cell(void *arg) ssize_t ret; char circ_nonce[DIGEST_LEN] = {0}; uint8_t buf[RELAY_PAYLOAD_SIZE]; - trn_cell_establish_intro_t *cell_out = NULL; trn_cell_establish_intro_t *cell_in = NULL; crypto_rand(circ_nonce, sizeof(circ_nonce)); @@ -38,7 +37,6 @@ test_gen_establish_intro_cell(void *arg) /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ { - cell_out = trn_cell_establish_intro_new(); /* We only need the auth key pair here. */ hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0); /* Auth key pair is generated in the constructor so we are all set for @@ -46,9 +44,18 @@ test_gen_establish_intro_cell(void *arg) ret = hs_cell_build_establish_intro(circ_nonce, ip, buf); service_intro_point_free(ip); tt_u64_op(ret, OP_GT, 0); + } - ret = trn_cell_establish_intro_encode(buf, sizeof(buf), cell_out); - tt_u64_op(ret, OP_GT, 0); + /* Check the contents of the cell */ + { + /* First byte is the auth key type: make sure its correct */ + tt_int_op(buf[0], OP_EQ, HS_INTRO_AUTH_KEY_TYPE_ED25519); + /* Next two bytes is auth key len */ + tt_int_op(ntohs(get_uint16(buf+1)), OP_EQ, ED25519_PUBKEY_LEN); + /* Skip to the number of extensions: no extensions */ + tt_int_op(buf[35], OP_EQ, 0); + /* Skip to the sig len. Make sure it's the size of an ed25519 sig */ + tt_int_op(ntohs(get_uint16(buf+35+1+32)), OP_EQ, ED25519_SIG_LEN); } /* Parse it as the receiver */ @@ -64,7 +71,6 @@ test_gen_establish_intro_cell(void *arg) done: trn_cell_establish_intro_free(cell_in); - trn_cell_establish_intro_free(cell_out); } /* Mocked ed25519_sign_prefixed() function that always fails :) */ From 471489ca035a50733205f97c8d589d80c58e36e8 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 23:35:04 +0300 Subject: [PATCH 76/91] Extract intro point onion key even with multiple types. --- src/or/hs_descriptor.c | 58 ++++++++++++++++++++++++++++++++---------- src/or/parsecommon.c | 2 +- src/or/parsecommon.h | 2 +- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 0b10f205a5..3e8343691e 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -1657,6 +1657,50 @@ decode_intro_legacy_key(const directory_token_t *tok, return -1; } +/* Dig into the descriptor tokens to find the onion key we should use + * for this intro point, and set it into onion_key_out. Return 0 if it + * was found and well-formed, otherwise return -1 in case of errors. */ +static int +set_intro_point_onion_key(curve25519_public_key_t *onion_key_out, + const smartlist_t *tokens) +{ + int retval = -1; + smartlist_t *onion_keys = NULL; + + tor_assert(onion_key_out); + + onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY); + if (!onion_keys) { + log_warn(LD_REND, "Descriptor did not contain intro onion keys"); + goto err; + } + + SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) { + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); + + /* Try to find an ntor key, it's the only recognized type right now */ + if (!strcmp(tok->args[0], "ntor")) { + if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); + goto err; + } + /* Got the onion key! Set the appropriate retval */ + retval = 0; + } + } SMARTLIST_FOREACH_END(tok); + + /* Log an error if we didn't find it :( */ + if (retval < 0) { + log_warn(LD_REND, "Descriptor did not contain ntor onion keys"); + } + + err: + smartlist_free(onion_keys); + return retval; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1696,19 +1740,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) } /* "onion-key" SP ntor SP key NL */ - tok = find_by_keyword(tokens, R3_INTRO_ONION_KEY); - if (!strcmp(tok->args[0], "ntor")) { - /* This field is using GE(2) so for possible forward compatibility, we - * accept more fields but must be at least 2. */ - tor_assert(tok->n_args >= 2); - - if (curve25519_public_from_base64(&ip->onion_key, tok->args[1]) < 0) { - log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); - goto err; - } - } else { - /* Unknown key type so we can't use that introduction point. */ - log_warn(LD_REND, "Introduction point onion key is unrecognized."); + if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) { goto err; } diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c index 7959867875..6b5359303a 100644 --- a/src/or/parsecommon.c +++ b/src/or/parsecommon.c @@ -436,7 +436,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) * in the same order in which they occur in s. Otherwise return * NULL. */ smartlist_t * -find_all_by_keyword(smartlist_t *s, directory_keyword k) +find_all_by_keyword(const smartlist_t *s, directory_keyword k) { smartlist_t *out = NULL; SMARTLIST_FOREACH(s, directory_token_t *, t, diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index a76b104fde..5e5f9f4db6 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -316,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s, directory_token_t *find_opt_by_keyword(smartlist_t *s, directory_keyword keyword); -smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k); +smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k); #endif /* TOR_PARSECOMMON_H */ From fe0c40c9b344853963dc7af0f746d8c772bb3a97 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Fri, 4 Aug 2017 23:46:20 +0300 Subject: [PATCH 77/91] Fix broken intro point unittest. The structure was not zeroed out, and left some boolean fields uninitialized. --- src/test/test_hs_service.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 10eeaa4321..6a7b9b5272 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1092,6 +1092,8 @@ test_build_update_descriptors(void *arg) tor_addr_t ipv4_addr; curve25519_secret_key_t curve25519_secret_key; + memset(&ri, 0, sizeof(routerinfo_t)); + tor_addr_parse(&ipv4_addr, "127.0.0.1"); ri.addr = tor_addr_to_ipv4h(&ipv4_addr); ri.or_port = 1337; From 0bf8587858b927d1dcb39189442f55d8dabe50aa Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 5 Aug 2017 00:33:34 +0300 Subject: [PATCH 78/91] Do more type checking when setting HS idents. I repurposed the old directory_request_set_hs_ident() into a new directory_request_upload_set_hs_ident() which is only used for the upload purpose and so it can assert on the dir_purpose. When coding the client-side we can make a second function for fetch. --- src/or/directory.c | 7 +++---- src/or/directory.h | 4 ++-- src/or/hs_service.c | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index fc83c013cb..e079a5941f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1281,12 +1281,11 @@ directory_request_set_rend_query(directory_request_t *req, * ident object must outlive the request. */ void -directory_request_set_hs_ident(directory_request_t *req, - const hs_ident_dir_conn_t *ident) +directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) { if (ident) { - tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC || - req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); + tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); } req->hs_ident = ident; } diff --git a/src/or/directory.h b/src/or/directory.h index 6b3102bc33..d3f8a45a82 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -73,8 +73,8 @@ void directory_request_set_if_modified_since(directory_request_t *req, time_t if_modified_since); void directory_request_set_rend_query(directory_request_t *req, const rend_data_t *query); -void directory_request_set_hs_ident(directory_request_t *req, - const hs_ident_dir_conn_t *ident); +void directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); void directory_request_set_routerstatus(directory_request_t *req, const routerstatus_t *rs); diff --git a/src/or/hs_service.c b/src/or/hs_service.c index bea760837b..43528a4285 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1957,7 +1957,7 @@ upload_descriptor_to_hsdir(const hs_service_t *service, strlen(encoded_desc)); /* The ident object is copied over the directory connection object once * the directory request is initiated. */ - directory_request_set_hs_ident(dir_req, &ident); + directory_request_upload_set_hs_ident(dir_req, &ident); /* Initiate the directory request to the hsdir.*/ directory_initiate_request(dir_req); From 4a1b57e9b00c8474ada28dd052a2de73a1e9c641 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 5 Aug 2017 23:11:37 +0300 Subject: [PATCH 79/91] prop224 tests: Improve SRV protocol tests. --- src/or/shared_random_state.c | 2 +- src/or/shared_random_state.h | 1 + src/test/test_shared_random.c | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 7f8094dafd..cbf58e95f4 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -133,7 +133,7 @@ get_voting_interval(void) /* Given the time now, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. */ -static time_t +STATIC time_t get_start_time_of_current_round(time_t now) { const or_options_t *options = get_options(); diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 03dd5eb37e..837fa75392 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -130,6 +130,7 @@ unsigned int sr_state_get_protocol_run_duration(void); STATIC int disk_state_load_from_disk_impl(const char *fname); STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_start_time_of_current_round(time_t now); STATIC time_t get_state_valid_until_time(time_t now); STATIC const char *get_phase_str(sr_phase_t phase); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index ea037d417b..bee0ea0a32 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -260,6 +260,30 @@ test_get_start_time_of_current_run(void *arg) ; } +/** Do some rudimentary consistency checks between the functions that + * understand the shared random protocol schedule */ +static void +test_get_start_time_functions(void *arg) +{ + (void) arg; + time_t now = approx_time(); + + time_t start_time_of_protocol_run = + sr_state_get_start_time_of_current_protocol_run(now); + tt_assert(start_time_of_protocol_run); + + /* Check that the round start time of the beginning of the run, is itself */ + tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run), OP_EQ, + start_time_of_protocol_run); + + /* Check that even if we increment the start time, we still get the start + time of the run as the beginning of the round. */ + tt_int_op(get_start_time_of_current_round(start_time_of_protocol_run+1), + OP_EQ, start_time_of_protocol_run); + + done: ; +} + static void test_get_sr_protocol_duration(void *arg) { @@ -1364,6 +1388,8 @@ struct testcase_t sr_tests[] = { NULL, NULL }, { "get_start_time_of_current_run", test_get_start_time_of_current_run, TT_FORK, NULL, NULL }, + { "get_start_time_functions", test_get_start_time_functions, + TT_FORK, NULL, NULL }, { "get_sr_protocol_duration", test_get_sr_protocol_duration, TT_FORK, NULL, NULL }, { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK, From e70341deb7dd8a8f50b872e9f7f999a113bb62b0 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 5 Aug 2017 23:25:44 +0300 Subject: [PATCH 80/91] prop224 tests: Better HS address tests. --- src/or/hs_service.c | 2 +- src/or/hs_service.h | 3 +++ src/test/test_hs_common.c | 25 ++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 43528a4285..6cb24a19af 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -848,7 +848,7 @@ register_all_services(void) /* Write the onion address of a given service to the given filename fname_ in * the service directory. Return 0 on success else -1 on error. */ -static int +STATIC int write_address_to_file(const hs_service_t *service, const char *fname_) { int ret = -1; diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 93d2710cdc..8d613d23ed 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -335,6 +335,9 @@ check_state_line_for_service_rev_counter(const char *state_line, const ed25519_public_key_t *blinded_pubkey, int *service_found_out); +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_); + #endif /* TOR_UNIT_TESTS */ #endif /* HS_SERVICE_PRIVATE */ diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 60577a2a50..65c6948382 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -7,6 +7,7 @@ */ #define HS_COMMON_PRIVATE +#define HS_SERVICE_PRIVATE #include "test.h" #include "test_helpers.h" @@ -14,6 +15,7 @@ #include "hs_test_helpers.h" #include "hs_common.h" +#include "hs_service.h" #include "config.h" /** Test the validation of HS v3 addresses */ @@ -70,6 +72,18 @@ test_validate_address(void *arg) ; } +static int +mock_write_str_to_file(const char *path, const char *str, int bin) +{ + (void)bin; + tt_str_op(path, OP_EQ, "/double/five/squared"); + tt_str_op(str, OP_EQ, + "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid.onion\n"); + + done: + return 0; +} + /** Test building HS v3 onion addresses */ static void test_build_address(void *arg) @@ -77,9 +91,12 @@ test_build_address(void *arg) int ret; char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; ed25519_public_key_t pubkey; + hs_service_t *service = NULL; (void) arg; + MOCK(write_str_to_file, mock_write_str_to_file); + /* The following has been created with hs_build_address.py script that * follows proposal 224 specification to build an onion address. */ static const char *test_addr = @@ -94,8 +111,14 @@ test_build_address(void *arg) ret = hs_address_is_valid(onion_addr); tt_int_op(ret, OP_EQ, 1); + service = tor_malloc_zero(sizeof(hs_service_t)); + memcpy(service->onion_address, onion_addr, sizeof(service->onion_address)); + tor_asprintf(&service->config.directory_path, "/double/five"); + ret = write_address_to_file(service, "squared"); + tt_int_op(ret, OP_EQ, 0); + done: - ; + hs_service_free(service); } /** Test that our HS time period calculation functions work properly */ From bd3213b17e6ff94bfd1c5deb3ecf2d906dd8d0b6 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 5 Aug 2017 23:43:05 +0300 Subject: [PATCH 81/91] prop224 tests: Better HS time period tests. --- src/test/test_hs_common.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 65c6948382..cc62870cb6 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -128,7 +128,7 @@ test_time_period(void *arg) (void) arg; uint64_t tn; int retval; - time_t fake_time; + time_t fake_time, correct_time, start_time; /* Let's do the example in prop224 section [TIME-PERIODS] */ retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", @@ -145,6 +145,15 @@ test_time_period(void *arg) tn = hs_get_time_period_num(fake_time); tt_u64_op(tn, ==, 16903); + { /* Check start time of next time period */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC", + &correct_time); + tt_int_op(retval, ==, 0); + + start_time = hs_get_start_time_of_next_time_period(fake_time); + tt_int_op(start_time, OP_EQ, correct_time); + } + /* Now take time to 12:00:00 UTC and check that the time period rotated */ fake_time += 1; tn = hs_get_time_period_num(fake_time); @@ -154,6 +163,24 @@ test_time_period(void *arg) tn = hs_get_next_time_period_num(fake_time); tt_u64_op(tn, ==, 16905); + { /* Check start time of next time period again */ + retval = parse_rfc1123_time("Wed, 14 Apr 2016 12:00:00 UTC", + &correct_time); + tt_int_op(retval, ==, 0); + + start_time = hs_get_start_time_of_next_time_period(fake_time); + tt_int_op(start_time, OP_EQ, correct_time); + } + + /* Now do another sanity check: The time period number at the start of the + * next time period, must be the same time period number as the one returned + * from hs_get_next_time_period_num() */ + { + time_t next_tp_start = hs_get_start_time_of_next_time_period(fake_time); + tt_int_op(hs_get_time_period_num(next_tp_start), OP_EQ, + hs_get_next_time_period_num(fake_time)); + } + done: ; } From 827bd0e8827e10d1fe14c04b3d605b2278e3001e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sun, 6 Aug 2017 22:24:07 +0300 Subject: [PATCH 82/91] Increase HS desc cert lifetime. We used to have a small HS desc cert lifetime but those certs can stick around for 36 hours if they get initialized in the beginning of overlap period. [warn] Bug: Non-fatal assertion !(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, &encoded_desc) < 0) failed in upload_descriptor_to_hsdir at src/or/hs_service.c:1886. Stack trace: (on Tor 0.3.2.0-alpha-dev b4a14555597fb9b3) --- src/or/hs_descriptor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index d9c632b589..fa211d3917 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -31,7 +31,7 @@ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) /* Lifetime of certificate in the descriptor. This defines the lifetime of the * descriptor signing key and the cross certification cert of that key. */ -#define HS_DESC_CERT_LIFETIME (24 * 60 * 60) +#define HS_DESC_CERT_LIFETIME (36 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 /* Length of the secret input needed for the KDF construction which derives From 273638288d4e6516011c4d538e5c1447f4753958 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 7 Aug 2017 12:02:45 +0300 Subject: [PATCH 83/91] Improve docs on rendezvous circ relaunch. --- src/or/hs_circuit.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index faceb731c5..43906619d7 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -682,7 +682,18 @@ hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) /* Called when we fail building a rendezvous circuit at some point other than * the last hop: launches a new circuit to the same rendezvous point. This - * supports legacy service. */ + * supports legacy service. + * + * We currently relaunch connections to rendezvous points if: + * - A rendezvous circuit timed out before connecting to RP. + * - The redenzvous circuit failed to connect to the RP. + * + * We avoid relaunching a connection to this rendezvous point if: + * - We have already tried MAX_REND_FAILURES times to connect to this RP. + * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT + * seconds + * - We've already retried this specific rendezvous circuit. + */ void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) { From 8bac50d7559adba16e282d5c83b891a387a8a3d5 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 7 Aug 2017 16:17:33 +0300 Subject: [PATCH 84/91] prop224: Improve comments and tests for ed25519 keys in IPs/RPs. Also make sure we are not gonna advertise the ed25519 key of an intro point that doesn't support it. --- src/or/hs_circuit.c | 3 ++- src/or/hs_descriptor.c | 1 + src/or/hs_service.c | 10 +++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 43906619d7..f6594739bc 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -405,7 +405,8 @@ get_rp_extend_info(const smartlist_t *link_specifiers, } } SMARTLIST_FOREACH_END(ls); - /* IPv4, legacy ID and ed25519 are mandatory. */ + /* IPv4, legacy ID are mandatory for rend points. + * ed25519 keys and ipv6 are optional for rend points */ if (!have_v4 || !have_legacy_id) { goto done; } diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index 3e8343691e..9a1e377155 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -2510,6 +2510,7 @@ hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); break; case LS_ED25519_ID: + /* ed25519 keys are optional for intro points */ if (ed25519_public_key_is_zero(&info->ed_identity)) { goto err; } diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 6cb24a19af..4c0ec628cc 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -405,7 +405,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) } smartlist_add(ip->base.link_specifiers, ls); - /* ed25519 identity key is optional */ + /* ed25519 identity key is optional for intro points */ ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); if (ls) { smartlist_add(ip->base.link_specifiers, ls); @@ -1411,6 +1411,14 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) if (BUG(info == NULL)) { goto err; } + + /* Let's do a basic sanity check here so that we don't end up advertising the + * ed25519 identity key of relays that don't actually support the link + * protocol */ + if (!node_supports_ed25519_link_authentication(node)) { + tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); + } + /* 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) { From 101ce6da01770ba0d05291ccafb98c4274cb616e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 7 Aug 2017 18:58:13 +0300 Subject: [PATCH 85/91] Fix the build_hs_index() function. Also add a unittest for hs_get_responsible_hsdirs() which was used to find and fix the bug. --- src/or/hs_common.c | 4 +- src/test/test_hs_common.c | 82 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index f5c63cb6a0..62cda34bd6 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1004,9 +1004,9 @@ hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, size_t offset = 0; set_uint64(buf, tor_htonll(replica)); offset += sizeof(uint64_t); - set_uint64(buf, tor_htonll(period_length)); + set_uint64(buf+offset, tor_htonll(period_length)); offset += sizeof(uint64_t); - set_uint64(buf, tor_htonll(period_num)); + set_uint64(buf+offset, tor_htonll(period_num)); offset += sizeof(uint64_t); tor_assert(offset == sizeof(buf)); diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index cc62870cb6..3041d24a62 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -17,6 +17,8 @@ #include "hs_common.h" #include "hs_service.h" #include "config.h" +#include "networkstatus.h" +#include "nodelist.h" /** Test the validation of HS v3 addresses */ static void @@ -355,6 +357,83 @@ test_desc_overlap_period_testnet(void *arg) tor_free(dummy_consensus); } +static networkstatus_t *mock_ns = NULL; + +static networkstatus_t * +mock_networkstatus_get_latest_consensus(void) +{ + time_t now = approx_time(); + + /* If initialized, return it */ + if (mock_ns) { + return mock_ns; + } + + /* Initialize fake consensus */ + mock_ns = tor_malloc_zero(sizeof(networkstatus_t)); + + /* This consensus is live */ + mock_ns->valid_after = now-1; + mock_ns->fresh_until = now+1; + mock_ns->valid_until = now+2; + /* Create routerstatus list */ + mock_ns->routerstatus_list = smartlist_new(); + + return mock_ns; +} + +/** Test the responsible HSDirs calculation function */ +static void +test_responsible_hsdirs(void *arg) +{ + time_t now = approx_time(); + smartlist_t *responsible_dirs = smartlist_new(); + networkstatus_t *ns = NULL; + routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t)); + + (void) arg; + + hs_init(); + + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + + ns = networkstatus_get_latest_consensus(); + + { /* First router: HSdir */ + tor_addr_t ipv4_addr; + memset(rs->identity_digest, 'A', DIGEST_LEN); + rs->is_hs_dir = 1; + rs->supports_v3_hsdir = 1; + routerinfo_t ri; + memset(&ri, 0 ,sizeof(routerinfo_t)); + tor_addr_parse(&ipv4_addr, "127.0.0.1"); + ri.addr = tor_addr_to_ipv4h(&ipv4_addr); + ri.nickname = tor_strdup("fatal"); + ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3"; + memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + tt_assert(nodelist_set_routerinfo(&ri, NULL)); + node_t *node = node_get_mutable_by_id(ri.cache_info.identity_digest); + memset(node->hsdir_index->current, 'Z', + sizeof(node->hsdir_index->current)); + smartlist_add(ns->routerstatus_list, rs); + } + + ed25519_public_key_t blinded_pk; + uint64_t time_period_num = hs_get_time_period_num(now); + hs_get_responsible_hsdirs(&blinded_pk, time_period_num, + 0, 0, responsible_dirs); + tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 1); + + /** TODO: Build a bigger network and do more tests here */ + + done: + routerstatus_free(rs); + smartlist_free(responsible_dirs); + smartlist_clear(ns->routerstatus_list); + networkstatus_vote_free(mock_ns); +} + struct testcase_t hs_common_tests[] = { { "build_address", test_build_address, TT_FORK, NULL, NULL }, @@ -368,6 +447,9 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK, NULL, NULL }, + { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK, + NULL, NULL }, + END_OF_TESTCASES }; From ff249ee4a6ea665bb7ed1c7ab53d4d6b0eb9db78 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 8 Aug 2017 11:45:45 +0300 Subject: [PATCH 86/91] Start caching disaster SRV values. Also add some unittests. --- src/or/hs_common.c | 56 ++++++++++++++++++++++++++++++++++++++- src/or/hs_common.h | 5 ++++ src/test/test_hs_common.c | 53 +++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 62cda34bd6..2b637eb780 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -503,7 +503,7 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) /* Using the given time period number, compute the disaster shared random * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ static void -get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) { crypto_digest_t *digest; @@ -534,6 +534,60 @@ get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) crypto_digest_free(digest); } +/** Due to the high cost of computing the disaster SRV and that potentially we + * would have to do it thousands of times in a row, we always cache the + * computer disaster SRV (and its corresponding time period num) in case we + * want to reuse it soon after. We need to cache two SRVs, one for each active + * time period (in case of overlap mode). + */ +static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; +static uint64_t cached_time_period_nums[2] = {0}; + +/** Compute the disaster SRV value for this time_period_num and put it + * in srv_out (of size at least DIGEST256_LEN). First check our caches + * to see if we have already computed it. */ +STATIC void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + if (time_period_num == cached_time_period_nums[0]) { + memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN); + return; + } else if (time_period_num == cached_time_period_nums[1]) { + memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN); + return; + } else { + int replace_idx; + // Replace the lower period number. + if (cached_time_period_nums[0] <= cached_time_period_nums[1]) { + replace_idx = 0; + } else { + replace_idx = 1; + } + cached_time_period_nums[replace_idx] = time_period_num; + compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]); + memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN); + return; + } +} + +#ifdef TOR_UNIT_TESTS + +/** Get the first cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_first_cached_disaster_srv(void) +{ + return cached_disaster_srv[0]; +} + +/** Get the second cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_second_cached_disaster_srv(void) +{ + return cached_disaster_srv[1]; +} + +#endif + /* When creating a blinded key, we need a parameter which construction is as * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). * diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 7e37f81e5c..5004e02088 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -224,10 +224,15 @@ int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); #ifdef HS_COMMON_PRIVATE +STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); + #ifdef TOR_UNIT_TESTS STATIC uint64_t get_time_period_length(void); +STATIC uint8_t *get_first_cached_disaster_srv(void); +STATIC uint8_t *get_second_cached_disaster_srv(void); + #endif /* TOR_UNIT_TESTS */ #endif /* HS_COMMON_PRIVATE */ diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 3041d24a62..d79d80bfab 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -434,6 +434,57 @@ test_responsible_hsdirs(void *arg) networkstatus_vote_free(mock_ns); } +/** Test disaster SRV computation and caching */ +static void +test_disaster_srv(void *arg) +{ + uint8_t *cached_disaster_srv_one = NULL; + uint8_t *cached_disaster_srv_two = NULL; + uint8_t srv_one[DIGEST256_LEN] = {0}; + uint8_t srv_two[DIGEST256_LEN] = {0}; + uint8_t srv_three[DIGEST256_LEN] = {0}; + uint8_t srv_four[DIGEST256_LEN] = {0}; + uint8_t srv_five[DIGEST256_LEN] = {0}; + + (void) arg; + + /* Get the cached SRVs: we gonna use them later for verification */ + cached_disaster_srv_one = get_first_cached_disaster_srv(); + cached_disaster_srv_two = get_second_cached_disaster_srv(); + + /* Compute some srvs */ + get_disaster_srv(1, srv_one); + get_disaster_srv(2, srv_two); + + /* Check that the cached ones where updated */ + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for an SRV that has already been computed */ + get_disaster_srv(2, srv_two); + /* and check that the cache entries have not changed */ + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for a new SRV */ + get_disaster_srv(3, srv_three); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for another SRV: none of the original SRVs should now be cached */ + get_disaster_srv(4, srv_four); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN); + + /* Ask for yet another SRV */ + get_disaster_srv(5, srv_five); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_five, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN); + + done: + ; +} + struct testcase_t hs_common_tests[] = { { "build_address", test_build_address, TT_FORK, NULL, NULL }, @@ -449,7 +500,7 @@ struct testcase_t hs_common_tests[] = { NULL, NULL }, { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK, NULL, NULL }, - + { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; From 0a0bbfe96fe425f27641f86fabd19f65a551ac6c Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 8 Aug 2017 11:51:16 +0300 Subject: [PATCH 87/91] Add note about handling INTRODUCE2 cells. Also fix a check-spaces instance. --- src/or/hs_cell.c | 5 +++++ src/test/test_hs_service.c | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c index 064e26334d..a0e9074601 100644 --- a/src/or/hs_cell.c +++ b/src/or/hs_cell.c @@ -210,6 +210,11 @@ build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, * service and circuit which are used only for logging purposes. The resulting * parsed cell is put in cell_ptr_out. * + * This function only parses prop224 INTRODUCE2 cells even when the intro point + * is a legacy intro point. That's because intro points don't actually care + * about the contents of the introduce cell. Legacy INTRODUCE cells are only + * used by the legacy system now. + * * Return 0 on success else a negative value and cell_ptr_out is untouched. */ static int parse_introduce2_cell(const hs_service_t *service, diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 6a7b9b5272..aea2c8fbfb 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1114,7 +1114,8 @@ test_build_update_descriptors(void *arg) ed25519_keypair_generate(&kp1, 0); ri.cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t)); tt_assert(ri.cache_info.signing_key_cert); - ed25519_pubkey_copy(&ri.cache_info.signing_key_cert->signing_key, &kp1.pubkey); + ed25519_pubkey_copy(&ri.cache_info.signing_key_cert->signing_key, + &kp1.pubkey); nodelist_set_routerinfo(&ri, NULL); node = node_get_mutable_by_id(ri.cache_info.identity_digest); tt_assert(node); From 400ba2f636edf5afb14fe3b57f23d80e433d893d Mon Sep 17 00:00:00 2001 From: David Goulet Date: Fri, 4 Aug 2017 12:06:34 -0400 Subject: [PATCH 88/91] prop224: Always note down the use of internal circuit Also, this removes all the callsite of this rephist in the hs subsystem Fixes #23097 Signed-off-by: David Goulet --- src/or/circuituse.c | 31 ++++++++++++++++++++++++++----- src/or/circuituse.h | 3 ++- src/or/hs_circuit.c | 12 +----------- src/or/hs_circuit.h | 2 +- src/or/hs_service.c | 6 +++--- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 5292dc01db..66006542d0 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1114,11 +1114,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) /* Return true if we need any more hidden service server circuits. * HS servers only need an internal circuit. */ STATIC int -needs_hs_server_circuits(int num_uptime_internal) +needs_hs_server_circuits(time_t now, int num_uptime_internal) { - return ((rend_num_services() || hs_service_get_num_services()) && - num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && - router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); + if (!rend_num_services() && !hs_service_get_num_services()) { + /* No services, we don't need anything. */ + goto no_need; + } + + if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) { + /* We have sufficient amount of internal circuit. */ + goto no_need; + } + + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) { + /* Consensus hasn't been checked or might be invalid so requesting + * internal circuits is not wise. */ + goto no_need; + } + + /* At this point, we need a certain amount of circuits and we will most + * likely use them for rendezvous so we note down the use of internal + * circuit for our prediction for circuit needing uptime and capacity. */ + rep_hist_note_used_internal(now, 1, 1); + + return 1; + no_need: + return 0; } /* We need at least this many internal circuits for hidden service clients */ @@ -1217,7 +1238,7 @@ circuit_predict_and_launch_new(void) return; } - if (needs_hs_server_circuits(num_uptime_internal)) { + if (needs_hs_server_circuits(now, num_uptime_internal)) { flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL); diff --git a/src/or/circuituse.h b/src/or/circuituse.h index ad4c214a3b..e66679586d 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -68,7 +68,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ); STATIC int needs_exit_circuits(time_t now, int *port_needs_uptime, int *port_needs_capacity); -STATIC int needs_hs_server_circuits(int num_uptime_internal); +STATIC int needs_hs_server_circuits(time_t now, + int num_uptime_internal); STATIC int needs_hs_client_circuits(time_t now, int *needs_uptime, diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index f6594739bc..d0265dc548 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -480,8 +480,6 @@ launch_rendezvous_point_circuit(const hs_service_t *service, tor_assert(data); circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); - /* Help predict this next time */ - rep_hist_note_used_internal(now, circ_needs_uptime, 1); /* Get the extend info data structure for the chosen rendezvous point * specified by the given link specifiers. */ @@ -632,10 +630,6 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) * has no anonymity (single onion), this change of behavior won't affect * security directly. */ - /* Help predict this next time */ - rep_hist_note_used_internal(time(NULL), bstate->need_uptime, - bstate->need_capacity); - new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, bstate->chosen_exit, flags); if (new_circ == NULL) { @@ -728,7 +722,7 @@ hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) int hs_circ_launch_intro_point(hs_service_t *service, const hs_service_intro_point_t *ip, - extend_info_t *ei, time_t now) + extend_info_t *ei) { /* Standard flags for introduction circuit. */ int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; @@ -748,10 +742,6 @@ hs_circ_launch_intro_point(hs_service_t *service, safe_str_client(extend_info_describe(ei)), safe_str_client(service->onion_address)); - /* Note down that we are about to use an internal circuit. */ - rep_hist_note_used_internal(now, circ_flags & CIRCLAUNCH_NEED_UPTIME, - circ_flags & CIRCLAUNCH_NEED_CAPACITY); - /* Note down the launch for the retry period. Even if the circuit fails to * be launched, we still want to respect the retry period to avoid stress on * the circuit subsystem. */ diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 8706e6b0ed..9e359394e8 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -24,7 +24,7 @@ void hs_circ_service_rp_has_opened(const hs_service_t *service, origin_circuit_t *circ); int hs_circ_launch_intro_point(hs_service_t *service, const hs_service_intro_point_t *ip, - extend_info_t *ei, time_t now); + extend_info_t *ei); int hs_circ_launch_rendezvous_point(const hs_service_t *service, const curve25519_public_key_t *onion_key, const uint8_t *rendezvous_cookie); diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 4c0ec628cc..5f36964547 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1749,7 +1749,7 @@ run_build_descriptor_event(time_t now) /* For the given service, launch any intro point circuits that could be * needed. This considers every descriptor of the service. */ static void -launch_intro_point_circuits(hs_service_t *service, time_t now) +launch_intro_point_circuits(hs_service_t *service) { tor_assert(service); @@ -1785,7 +1785,7 @@ launch_intro_point_circuits(hs_service_t *service, time_t now) /* Launch a circuit to the intro point. */ ip->circuit_retries++; - if (hs_circ_launch_intro_point(service, ip, ei, now) < 0) { + if (hs_circ_launch_intro_point(service, ip, ei) < 0) { log_warn(LD_REND, "Unable to launch intro circuit to node %s " "for service %s.", safe_str_client(extend_info_describe(ei)), @@ -1910,7 +1910,7 @@ run_build_circuit_event(time_t now) * circuit creation so make sure this service is respecting that limit. */ if (can_service_launch_intro_circuit(service, now)) { /* Launch intro point circuits if needed. */ - launch_intro_point_circuits(service, now); + launch_intro_point_circuits(service); /* Once the circuits have opened, we'll make sure to update the * descriptor intro point list and cleanup any extraneous. */ } From 5c4f4acedb8600769889cdff0940806c27b679bd Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 31 Jul 2017 17:59:12 +0300 Subject: [PATCH 89/91] prop224: Function to inc/decrement num rendezvous stream Add a common function for both legacy and prop224 hidden service to increment and decrement the rendezvous stream counter on an origin circuit. Signed-off-by: David Goulet --- src/or/circuituse.c | 3 +-- src/or/connection_edge.c | 10 +--------- src/or/hs_common.c | 34 ++++++++++++++++++++++++++++++++++ src/or/hs_common.h | 3 +++ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 66006542d0..21cc9c540f 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1383,8 +1383,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) * number of streams on the circuit associated with the rend service. */ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ->rend_data); - origin_circ->rend_data->nr_streams--; + hs_dec_rdv_stream_counter(origin_circ); } return; } diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 9f0cc061e1..12ddc7e829 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3139,15 +3139,7 @@ handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) conn->on_circuit = circ; assert_circuit_ok(circ); - if (origin_circ->rend_data) { - origin_circ->rend_data->nr_streams++; - } else if (origin_circ->hs_ident) { - origin_circ->hs_ident->num_rdv_streams++; - } else { - /* The previous if/else at the start of the function guarantee that we'll - * never end up in a else situation unless it's freed in between. */ - tor_assert(0); - } + hs_inc_rdv_stream_counter(origin_circ); /* Connect tor to the hidden service destination. */ connection_exit_connect(conn); diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 2b637eb780..0d3d41b7cd 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1333,3 +1333,37 @@ hs_free_all(void) hs_cache_free_all(); } +/* For the given origin circuit circ, decrement the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_dec_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams--; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams--; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + +/* For the given origin circuit circ, increment the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_inc_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams++; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams++; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 5004e02088..fd2a1f4e32 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -222,6 +222,9 @@ void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); +void hs_inc_rdv_stream_counter(origin_circuit_t *circ); +void hs_dec_rdv_stream_counter(origin_circuit_t *circ); + #ifdef HS_COMMON_PRIVATE STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); From 686891d67ebf69808ac3d2b2c98263d21a3fe143 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 8 Aug 2017 21:25:04 +0300 Subject: [PATCH 90/91] prop224: Add XXX about opaqueness of link_specifier_t. --- src/trunnel/ed25519_cert.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/trunnel/ed25519_cert.h b/src/trunnel/ed25519_cert.h index 782bd59585..0aa953d174 100644 --- a/src/trunnel/ed25519_cert.h +++ b/src/trunnel/ed25519_cert.h @@ -59,6 +59,10 @@ struct link_specifier_st { }; #endif typedef struct link_specifier_st link_specifier_t; +/** XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by + * taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE we would + * need to refactor that function to do the coyp by encoding and decoding the + * object. */ #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT) struct ed25519_cert_st { uint8_t version; From 2f17743d6f7222cf96250890dd91d6689b2d55c6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 8 Aug 2017 20:31:47 -0400 Subject: [PATCH 91/91] Put comment in the trunnel file, so it wont go away. --- src/trunnel/ed25519_cert.trunnel | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/trunnel/ed25519_cert.trunnel b/src/trunnel/ed25519_cert.trunnel index e424ce5464..8d6483d558 100644 --- a/src/trunnel/ed25519_cert.trunnel +++ b/src/trunnel/ed25519_cert.trunnel @@ -28,6 +28,12 @@ const LS_IPV6 = 0x01; const LS_LEGACY_ID = 0x02; const LS_ED25519_ID = 0x03; +// XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by +// taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE, or +// if we ever make link_specifier contain other types, we will +// need to refactor that function to do the copy by encoding and decoding the +// object. + // amended from tor.trunnel struct link_specifier { u8 ls_type;