From 3a45f6ffe95d4c51e4ad4e14f468feb3f4bd6b1e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 10 May 2018 08:46:36 -0400 Subject: [PATCH 01/13] Implementation for a simple order-preserving encryption scheme. This is meant for use when encrypting the current time within the period in order to get a monotonically increasing revision counter without actually revealing our view of the time. This scheme is far from the most state-of-the-art: don't use it for anything else without careful analysis by somebody much smarter than I am. See ticket #25552 for some rationale for this logic. --- src/lib/crypt_ops/crypto_ope.c | 179 +++++++++++++++++++++++++++++++++ src/lib/crypt_ops/crypto_ope.h | 35 +++++++ src/lib/crypt_ops/include.am | 2 + src/test/include.am | 1 + src/test/ope_ref.py | 40 ++++++++ src/test/test.c | 1 + src/test/test.h | 1 + src/test/test_crypto_ope.c | 148 +++++++++++++++++++++++++++ 8 files changed, 407 insertions(+) create mode 100644 src/lib/crypt_ops/crypto_ope.c create mode 100644 src/lib/crypt_ops/crypto_ope.h create mode 100644 src/test/ope_ref.py create mode 100644 src/test/test_crypto_ope.c diff --git a/src/lib/crypt_ops/crypto_ope.c b/src/lib/crypt_ops/crypto_ope.c new file mode 100644 index 0000000000..dd04ffbaaa --- /dev/null +++ b/src/lib/crypt_ops/crypto_ope.c @@ -0,0 +1,179 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * A rudimentary order-preserving encryption scheme. + * + * To compute the encryption of N, this scheme uses an AES-CTR stream to + * generate M-byte values, and adds the first N of them together. (+1 each to + * insure that the ciphertexts are strictly decreasing.) + * + * We use this for generating onion service revision counters based on the + * current time, without leaking the amount of skew in our view of the current + * time. MUCH more analysis would be needed before using it for anything + * else! + */ + +#include "orconfig.h" +#include "crypto.h" + +#define CRYPTO_OPE_PRIVATE + +#include "crypto_ope.h" +/** + * How infrequent should the precomputed values be for this encryption? + * The choice of this value creates a space/time tradeoff. + * + * Note that this value must be a multiple of 16; see + * ope_get_cipher() + */ +#define SAMPLE_INTERVAL 1024 +/** Number of precomputed samples to make for each OPE key. */ +#define N_SAMPLES (OPE_INPUT_MAX / SAMPLE_INTERVAL) + +struct crypto_ope_c { + /** An AES key for use with this object. */ + uint8_t key[OPE_KEY_LEN]; + /** Cached intermediate encryption values at SAMPLE_INTERVAL, + * SAMPLE_INTERVAL*2,...SAMPLE_INTERVAL*N_SAMPLES */ + uint64_t samples[N_SAMPLES]; +}; + +/** The type to add up in order to produce our OPE ciphertexts */ +typedef uint16_t ope_val_t; + +#ifdef WORDS_BIG_ENDIAN +/** Convert an OPE value to little-endian */ +static inline ope_val_t +ope_val_to_le(ope_val_t x) +{ + return + ((x) >> 8) | + (((x)&0xff) << 8); +} +#else +#define ope_val_to_le(x) (x) +#endif + +/** + * Return a new AES256-CTR stream cipher object for ope, ready to yield + * bytes from the stream at position initial_idx. + * + * Note that because the index is converted directly to an IV, it must be a + * multiple of the AES block size (16). + */ +STATIC crypto_cipher_t * +ope_get_cipher(const crypto_ope_t *ope, uint32_t initial_idx) +{ + uint8_t iv[CIPHER_IV_LEN]; + tor_assert((initial_idx & 0xf) == 0); + uint32_t n = htonl(initial_idx >> 4); + memset(iv, 0, sizeof(iv)); + memcpy(iv + CIPHER_IV_LEN - sizeof(n), &n, sizeof(n)); + + return crypto_cipher_new_with_iv_and_bits(ope->key, + iv, + OPE_KEY_LEN * 8); +} + +/** + * Retrieve and add the next n values from the stream cipher c, + * and return their sum. + * + * Note that values are taken in little-endian order (for performance on + * prevalent hardware), and are mapped from range 0..2^n-1 to range 1..2^n (so + * that each input encrypts to a different output). + * + * NOTE: this function is not constant-time. + */ +STATIC uint64_t +sum_values_from_cipher(crypto_cipher_t *c, size_t n) +{ +#define BUFSZ 256 + ope_val_t buf[BUFSZ]; + uint64_t total = 0; + unsigned i; + while (n >= BUFSZ) { + memset(buf, 0, sizeof(buf)); + crypto_cipher_crypt_inplace(c, (char*)buf, BUFSZ*sizeof(ope_val_t)); + + for (i = 0; i < BUFSZ; ++i) { + total += ope_val_to_le(buf[i]); + total += 1; + } + n -= BUFSZ; + } + + memset(buf, 0, n*sizeof(ope_val_t)); + crypto_cipher_crypt_inplace(c, (char*)buf, n*sizeof(ope_val_t)); + for (i = 0; i < n; ++i) { + total += ope_val_to_le(buf[i]); + total += 1; + } + + memset(buf, 0, sizeof(buf)); + return total; +} + +/** + * Return a new crypto_ope_t object, using the provided 256-bit key. + */ +crypto_ope_t * +crypto_ope_new(const uint8_t *key) +{ + crypto_ope_t *ope = tor_malloc_zero(sizeof(crypto_ope_t)); + memcpy(ope->key, key, OPE_KEY_LEN); + + crypto_cipher_t *cipher = ope_get_cipher(ope, 0); + + uint64_t v = 0; + int i; + for (i = 0; i < N_SAMPLES; ++i) { + v += sum_values_from_cipher(cipher, SAMPLE_INTERVAL); + ope->samples[i] = v; + } + + crypto_cipher_free(cipher); + return ope; +} + +/** Free all storage held in <>ope. */ +void +crypto_ope_free_(crypto_ope_t *ope) +{ + if (!ope) + return; + memwipe(ope, 0, sizeof(*ope)); + tor_free(ope); +} + +/** + * Return the encrypted value corresponding to input. The input value + * must be in range 1..OPE_INPUT_MAX. Returns UINT64_MAX on an invalid input. + * + * NOTE: this function is not constant-time. + */ +uint64_t +crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext) +{ + if (plaintext <= 0 || plaintext > OPE_INPUT_MAX) + return UINT64_MAX; + + const int sample_idx = (plaintext / SAMPLE_INTERVAL); + const int starting_iv = sample_idx * SAMPLE_INTERVAL; + const int remaining_values = plaintext - starting_iv; + uint64_t v; + if (sample_idx == 0) { + v = 0; + } else { + v = ope->samples[sample_idx - 1]; + } + crypto_cipher_t *cipher = ope_get_cipher(ope, starting_iv*sizeof(ope_val_t)); + + v += sum_values_from_cipher(cipher, remaining_values); + + crypto_cipher_free(cipher); + + return v; +} + diff --git a/src/lib/crypt_ops/crypto_ope.h b/src/lib/crypt_ops/crypto_ope.h new file mode 100644 index 0000000000..885ce84b2a --- /dev/null +++ b/src/lib/crypt_ops/crypto_ope.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef CRYPTO_OPE_H +#define CRYPTO_OPE_H + +#include "orconfig.h" +#include "crypto.h" +#include "crypto_util.h" + +#include "crypto_ope.h" + +/** Length of OPE key, in bytes. */ +#define OPE_KEY_LEN 32 + +/** Largest value that can be passed to crypto_ope_encrypt() */ +#define OPE_INPUT_MAX 131072 + +typedef struct crypto_ope_c crypto_ope_t; + +crypto_ope_t *crypto_ope_new(const uint8_t *key); +void crypto_ope_free_(crypto_ope_t *ope); +#define crypto_ope_free(ope) \ + FREE_AND_NULL(crypto_ope_t, crypto_ope_free_, (ope)) + +uint64_t crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext); + +#ifdef CRYPTO_OPE_PRIVATE +STATIC crypto_cipher_t *ope_get_cipher(const crypto_ope_t *ope, + uint32_t initial_idx); +STATIC uint64_t sum_values_from_cipher(crypto_cipher_t *c, size_t n); +#endif + +#endif + diff --git a/src/lib/crypt_ops/include.am b/src/lib/crypt_ops/include.am index b881c689d8..6b0b0d2001 100644 --- a/src/lib/crypt_ops/include.am +++ b/src/lib/crypt_ops/include.am @@ -14,6 +14,7 @@ src_lib_libtor_crypt_ops_a_SOURCES = \ src/lib/crypt_ops/crypto_ed25519.c \ src/lib/crypt_ops/crypto_format.c \ src/lib/crypt_ops/crypto_hkdf.c \ + src/lib/crypt_ops/crypto_ope.c \ src/lib/crypt_ops/crypto_openssl_mgt.c \ src/lib/crypt_ops/crypto_pwbox.c \ src/lib/crypt_ops/crypto_rand.c \ @@ -37,6 +38,7 @@ noinst_HEADERS += \ src/lib/crypt_ops/crypto.h \ src/lib/crypt_ops/crypto_hkdf.h \ src/lib/crypt_ops/crypto_openssl_mgt.h \ + src/lib/crypt_ops/crypto_ope.h \ src/lib/crypt_ops/crypto_pwbox.h \ src/lib/crypt_ops/crypto_rand.h \ src/lib/crypt_ops/crypto_rsa.h \ diff --git a/src/test/include.am b/src/test/include.am index 7e5ad46117..390c84ebe4 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -117,6 +117,7 @@ src_test_test_SOURCES += \ src/test/test_controller.c \ src/test/test_controller_events.c \ src/test/test_crypto.c \ + src/test/test_crypto_ope.c \ src/test/test_crypto_openssl.c \ src/test/test_data.c \ src/test/test_dir.c \ diff --git a/src/test/ope_ref.py b/src/test/ope_ref.py new file mode 100644 index 0000000000..3677e57a61 --- /dev/null +++ b/src/test/ope_ref.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +# Copyright 2018, The Tor Project, Inc. See LICENSE for licensing info. + +# Reference implementation for our rudimentary OPE code, used to +# generate test vectors. See crypto_ope.c for more details. + +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.backends import default_backend + +from binascii import a2b_hex + +#randomly generated and values. +KEY = a2b_hex( + "19e05891d55232c08c2cad91d612fdb9cbd6691949a0742434a76c80bc6992fe") +PTS = [ 121132, 82283, 72661, 72941, 123122, 12154, 121574, 11391, 65845, + 86301, 61284, 70505, 30438, 60150, 114800, 109403, 21893, 123569, + 95617, 48561, 53334, 92746, 7110, 9612, 106958, 46889, 87790, 68878, + 47917, 121128, 108602, 28217, 69498, 63870, 57542, 122148, 46254, + 42850, 92661, 57720] + +IV = b'\x00' * 16 + +backend = default_backend() + +def words(): + cipher = Cipher(algorithms.AES(KEY), modes.CTR(IV), backend=backend) + e = cipher.encryptor() + while True: + v = e.update(b'\x00\x00') + yield v[0] + 256 * v[1] + 1 + +def encrypt(n): + return sum(w for w, _ in zip(words(), range(n))) + +def example(n): + return ' {{ {}, UINT64_C({}) }},'.format(n, encrypt(n)) + +for v in PTS: + print(example(v)) diff --git a/src/test/test.c b/src/test/test.c index 1a3f4f8eb2..baf9e05bee 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -861,6 +861,7 @@ struct testgroup_t testgroups[] = { { "control/", controller_tests }, { "control/event/", controller_event_tests }, { "crypto/", crypto_tests }, + { "crypto/ope/", crypto_ope_tests }, { "crypto/openssl/", crypto_openssl_tests }, { "dir/", dir_tests }, { "dir_handle_get/", dir_handle_get_tests }, diff --git a/src/test/test.h b/src/test/test.h index 9abd46008d..b24163ed3d 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -212,6 +212,7 @@ extern struct testcase_t container_tests[]; extern struct testcase_t controller_tests[]; extern struct testcase_t controller_event_tests[]; extern struct testcase_t crypto_tests[]; +extern struct testcase_t crypto_ope_tests[]; extern struct testcase_t crypto_openssl_tests[]; extern struct testcase_t dir_tests[]; extern struct testcase_t dir_handle_get_tests[]; diff --git a/src/test/test_crypto_ope.c b/src/test/test_crypto_ope.c new file mode 100644 index 0000000000..1b93e6981e --- /dev/null +++ b/src/test/test_crypto_ope.c @@ -0,0 +1,148 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define CRYPTO_OPE_PRIVATE + +#include "lib/crypt_ops/crypto_ope.h" +#include "common/util_format.h" +#include "test/test.h" + +static void +test_crypto_ope_consistency(void *arg) +{ + (void)arg; + + crypto_ope_t *ope = NULL; + crypto_cipher_t *aes = NULL; + const int TEST_VALS[] = { 5, 500, 1023, 1024, 1025, 2046, 2047, 2048, 2049, + 10000, OPE_INPUT_MAX }; + unsigned i; + const uint8_t key[32] = "A fixed key, chosen arbitrarily."; + + ope = crypto_ope_new(key); + tt_assert(ope); + + uint64_t last_val = 0; + for (i = 0; i < ARRAY_LENGTH(TEST_VALS); ++i) { + aes = ope_get_cipher(ope, 0); + int val = TEST_VALS[i]; + uint64_t v1 = crypto_ope_encrypt(ope, val); + uint64_t v2 = sum_values_from_cipher(aes, val); + tt_u64_op(v1, OP_EQ, v2); + tt_u64_op(v2, OP_GT, last_val); + last_val = v2; + crypto_cipher_free(aes); + } + + done: + crypto_cipher_free(aes); + crypto_ope_free(ope); +} + +static void +test_crypto_ope_oob(void *arg) +{ + (void)arg; + + crypto_ope_t *ope = NULL; + const uint8_t key[32] = "A fixed key, chosen arbitrarily."; + ope = crypto_ope_new(key); + + tt_u64_op(UINT64_MAX, OP_EQ, crypto_ope_encrypt(ope,INT_MIN)); + tt_u64_op(UINT64_MAX, OP_EQ, crypto_ope_encrypt(ope,-100)); + tt_u64_op(UINT64_MAX, OP_EQ, crypto_ope_encrypt(ope,0)); + tt_u64_op(UINT64_MAX, OP_NE, crypto_ope_encrypt(ope,1)); + tt_u64_op(UINT64_MAX, OP_NE, crypto_ope_encrypt(ope,7000)); + tt_u64_op(UINT64_MAX, OP_NE, crypto_ope_encrypt(ope,OPE_INPUT_MAX)); + tt_u64_op(UINT64_MAX, OP_EQ, crypto_ope_encrypt(ope,OPE_INPUT_MAX+1)); + tt_u64_op(UINT64_MAX, OP_EQ, crypto_ope_encrypt(ope,INT_MAX)); + done: + crypto_ope_free(ope); +} + +static const char OPE_TEST_KEY[] = + "19e05891d55232c08c2cad91d612fdb9cbd6691949a0742434a76c80bc6992fe"; + +/* generated by a separate python implementation. */ +static const struct { + int v; + uint64_t r; +} OPE_TEST_VECTORS[] = { + { 121132, UINT64_C(3971694514) }, + { 82283, UINT64_C(2695743564) }, + { 72661, UINT64_C(2381548866) }, + { 72941, UINT64_C(2390408421) }, + { 123122, UINT64_C(4036781069) }, + { 12154, UINT64_C(402067100) }, + { 121574, UINT64_C(3986197593) }, + { 11391, UINT64_C(376696838) }, + { 65845, UINT64_C(2161801517) }, + { 86301, UINT64_C(2828270975) }, + { 61284, UINT64_C(2013616892) }, + { 70505, UINT64_C(2313368870) }, + { 30438, UINT64_C(1001394664) }, + { 60150, UINT64_C(1977329668) }, + { 114800, UINT64_C(3764946628) }, + { 109403, UINT64_C(3585352477) }, + { 21893, UINT64_C(721388468) }, + { 123569, UINT64_C(4051780471) }, + { 95617, UINT64_C(3134921876) }, + { 48561, UINT64_C(1597596985) }, + { 53334, UINT64_C(1753691710) }, + { 92746, UINT64_C(3040874493) }, + { 7110, UINT64_C(234966492) }, + { 9612, UINT64_C(318326551) }, + { 106958, UINT64_C(3506124249) }, + { 46889, UINT64_C(1542219146) }, + { 87790, UINT64_C(2877361609) }, + { 68878, UINT64_C(2260369112) }, + { 47917, UINT64_C(1576681737) }, + { 121128, UINT64_C(3971553290) }, + { 108602, UINT64_C(3559176081) }, + { 28217, UINT64_C(929692460) }, + { 69498, UINT64_C(2280554161) }, + { 63870, UINT64_C(2098322675) }, + { 57542, UINT64_C(1891698992) }, + { 122148, UINT64_C(4004515805) }, + { 46254, UINT64_C(1521227949) }, + { 42850, UINT64_C(1408996941) }, + { 92661, UINT64_C(3037901517) }, + { 57720, UINT64_C(1897369989) }, +}; + +static void +test_crypto_ope_vectors(void *arg) +{ + (void)arg; + uint8_t key[32]; + crypto_ope_t *ope = NULL, *ope2 = NULL; + + base16_decode((char*)key, 32, OPE_TEST_KEY, strlen(OPE_TEST_KEY)); + + ope = crypto_ope_new(key); + key[8] += 1; + ope2 = crypto_ope_new(key); + unsigned i; + for (i = 0; i < ARRAY_LENGTH(OPE_TEST_VECTORS); ++i) { + int val = OPE_TEST_VECTORS[i].v; + uint64_t res = OPE_TEST_VECTORS[i].r; + + tt_u64_op(crypto_ope_encrypt(ope, val), OP_EQ, res); + tt_u64_op(crypto_ope_encrypt(ope2, val), OP_NE, res); + } + done: + crypto_ope_free(ope); + crypto_ope_free(ope2); +} + +struct testcase_t crypto_ope_tests[] = { + { "consistency", test_crypto_ope_consistency, 0, NULL, NULL }, + { "oob", test_crypto_ope_oob, 0, NULL, NULL }, + { "vectors", test_crypto_ope_vectors, 0, NULL, NULL }, + END_OF_TESTCASES +}; + From 34a5eb5904896cfbfb8c2729fe3b029fab6deb39 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:23:09 +0300 Subject: [PATCH 02/13] Increase OPE_INPUT_MAX. --- src/lib/crypt_ops/crypto_ope.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib/crypt_ops/crypto_ope.h b/src/lib/crypt_ops/crypto_ope.h index 885ce84b2a..823524f84e 100644 --- a/src/lib/crypt_ops/crypto_ope.h +++ b/src/lib/crypt_ops/crypto_ope.h @@ -13,8 +13,18 @@ /** Length of OPE key, in bytes. */ #define OPE_KEY_LEN 32 -/** Largest value that can be passed to crypto_ope_encrypt() */ -#define OPE_INPUT_MAX 131072 +/** Largest value that can be passed to crypto_ope_encrypt(). + * + * Expressed as 2^18 because the OPE system prefers powers of two. + * + * The current max value stands for about 70 hours. The rationale here is as + * follows: The rev counter is the time of seconds since the start of an SRV + * period. SRVs are useful for about 48 hours (that's how long they stick + * around on the consensus). Let's also add 12 hours of drift for clock skewed + * services that might be using an old consensus and we arrive to 60 + * hours. The max value should be beyond that. + */ +#define OPE_INPUT_MAX (1<<18) typedef struct crypto_ope_c crypto_ope_t; From deec6913c5a30a568fd761c707867e2001646237 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:25:43 +0300 Subject: [PATCH 03/13] Introduce useful SRV funcs (start time of prev protocol run) --- src/or/shared_random_client.c | 13 +++++++++++++ src/or/shared_random_client.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/or/shared_random_client.c b/src/or/shared_random_client.c index f0b13a171e..d787b6b2ec 100644 --- a/src/or/shared_random_client.c +++ b/src/or/shared_random_client.c @@ -243,6 +243,19 @@ 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 start time of the previous SR protocol run. See + * sr_state_get_start_time_of_current_protocol_run() for more details. */ +time_t +sr_state_get_start_time_of_previous_protocol_run(time_t now) +{ + time_t start_time_of_current_run = + sr_state_get_start_time_of_current_protocol_run(now); + + /* We get the start time of previous protocol run, by getting the start time + * of current run and the subtracting a full protocol run from that. */ + return start_time_of_current_run - sr_state_get_protocol_run_duration(); +} + /** Return the time (in seconds) it takes to complete a full SR protocol phase * (e.g. the commit phase). */ unsigned int diff --git a/src/or/shared_random_client.h b/src/or/shared_random_client.h index 079829496c..35ebb1bd57 100644 --- a/src/or/shared_random_client.h +++ b/src/or/shared_random_client.h @@ -35,6 +35,7 @@ sr_srv_t *sr_parse_srv(const smartlist_t *args); #define SHARED_RANDOM_N_PHASES 2 time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +time_t sr_state_get_start_time_of_previous_protocol_run(time_t now); unsigned int sr_state_get_phase_duration(void); unsigned int sr_state_get_protocol_run_duration(void); time_t get_start_time_of_current_round(void); From 05c362274b8d643a43f367d047699a88d30d667a Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:35:59 +0300 Subject: [PATCH 04/13] Compute the description revision counter using the OPE scheme. To do so for a given descriptor, we use the "seconds since the SR protocol run" started, for the SRV that is relevant to this descriptor. This is guaranteed to be a positive value (since we need an SRV to be able to build a descriptor), and it's also guaranteed to be a small value (since SRVs stop being listed on a consensus after 48 hours). We cannot use the "seconds since the time period started", because for the next descriptor we use the next time period, so the timestamp would end up negative. See [SERVICEUPLOAD] from rend-spec-v3.txt for more details. To do so, we have to introduce a new `is_current` argument to a bunch of functions, because to use "seconds since the SR protocol run" we need to know if we are building the current or the next descriptor, since we use a different SRV for each descriptor. --- src/or/hs_service.c | 109 ++++++++++++++++++++++++++++++++++++++------ src/or/or.h | 1 + 2 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index b5649e2636..76b1634561 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -91,7 +91,8 @@ static smartlist_t *hs_service_staging_list; static int consider_republishing_hs_descriptors = 0; /* Static declaration. */ -static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); +static void set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, + time_t now, bool is_current); static void move_descriptors(hs_service_t *src, hs_service_t *dst); /* Helper: Function to compare two objects in the service map. Return 1 if the @@ -1420,11 +1421,15 @@ build_service_desc_keys(const hs_service_t *service, * the update function. On success, desc_out will point to the newly allocated * descriptor object. * + * If is_current is true, this is the current service descriptor, + * otherwise it's the next one. + * * 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) + hs_service_descriptor_t **desc_out, + bool is_current) { char *encoded_desc; hs_service_descriptor_t *desc; @@ -1449,7 +1454,7 @@ build_service_descriptor(hs_service_t *service, time_t now, } /* Set the revision counter for this descriptor */ - set_descriptor_revision_counter(desc->desc); + set_descriptor_revision_counter(desc, now, is_current); /* 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 @@ -1515,9 +1520,9 @@ build_descriptors_for_new_service(hs_service_t *service, time_t now) /* Build descriptors. */ build_service_descriptor(service, now, current_desc_tp, - &service->desc_current); + &service->desc_current, 1); build_service_descriptor(service, now, next_desc_tp, - &service->desc_next); + &service->desc_next, 0); log_info(LD_REND, "Hidden service %s has just started. Both descriptors " "built. Now scheduled for upload.", safe_str_client(service->onion_address)); @@ -1548,7 +1553,7 @@ build_all_descriptors(time_t now) if (service->desc_next == NULL) { build_service_descriptor(service, now, hs_get_next_time_period_num(0), - &service->desc_next); + &service->desc_next, 0); log_info(LD_REND, "Hidden service %s next descriptor successfully " "built. Now scheduled for upload.", safe_str_client(service->onion_address)); @@ -2514,16 +2519,94 @@ increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) 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. */ +/** Set the revision counter in hs_desc. We do this by encrypting a + * timestamp using an OPE scheme and using the ciphertext as our revision + * counter. + * + * If is_current is true, then this is the current HS descriptor, + * otherwise it's the next one. */ static void -set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, + bool is_current) { - /* Find stored rev counter if it exists */ - uint64_t rev_counter = - get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + uint64_t rev_counter = 0; - hs_desc->plaintext_data.revision_counter = rev_counter; + /* Get current time */ + time_t srv_start = 0; + + /* As our revision counter plaintext value, we use the seconds since the + * start of the SR protocol run that is relevant to this descriptor. This is + * guaranteed to be a positive value since we need the SRV to start making a + * descriptor (so that we know where to upload it). + * + * Depending on whether we are building the current or the next descriptor, + * services use a different SRV value. See [SERVICEUPLOAD] in + * rend-spec-v3.txt: + * + * In particular, for the current descriptor (aka first descriptor), Tor + * always uses the previous SRV for uploading the descriptor, and hence we + * should use the start time of the previous protocol run here. + * + * Whereas for the next descriptor (aka second descriptor), Tor always uses + * the current SRV for uploading the descriptor. and hence we use the start + * time of the current protocol run. + */ + if (is_current) { + srv_start = sr_state_get_start_time_of_previous_protocol_run(now); + } else { + srv_start = sr_state_get_start_time_of_current_protocol_run(now); + } + + log_info(LD_REND, "Setting rev counter for TP #%u: " + "SRV started at %d, now %d (%s)", + (unsigned) hs_desc->time_period_num, (int)srv_start, + (int)now, is_current ? "current" : "next"); + + tor_assert_nonfatal(now >= srv_start); + + /* Compute seconds elapsed since the start of the time period. That's the + * number of seconds of how long this blinded key has been active. */ + time_t seconds_since_start_of_srv = now - srv_start; + + /* Increment by one so that we are definitely sure this is strictly + * positive and not zero. */ + seconds_since_start_of_srv++; + + /* Check for too big inputs. */ + if (BUG(seconds_since_start_of_srv > OPE_INPUT_MAX)) { + seconds_since_start_of_srv = OPE_INPUT_MAX; + } + + /* Now we compute the actual revision counter value by encrypting the + plaintext using an OPE construction: */ + + /* First, compute OPE key as: K = H("rev-counter-generation" | S) */ + uint8_t key[DIGEST256_LEN]; + { + crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA3_256); + const char ope_key_prefix[] = "rev-counter-generation"; + ed25519_secret_key_t *eph_privkey = &hs_desc->blinded_kp.seckey; + crypto_digest_add_bytes(digest, ope_key_prefix, sizeof(ope_key_prefix)); + crypto_digest_add_bytes(digest, (char*)eph_privkey->seckey, + sizeof(eph_privkey->seckey)); + crypto_digest_get_digest(digest, (char *)key, sizeof(key)); + crypto_digest_free(digest); + } + + { /* Now encrypt the revision counter! */ + crypto_ope_t *ope = NULL; + ope = crypto_ope_new(key); + rev_counter = crypto_ope_encrypt(ope, (int) seconds_since_start_of_srv); + crypto_ope_free(ope); + } + + /* The OPE module returns UINT64_MAX in case of errors. */ + tor_assert_nonfatal(rev_counter < UINT64_MAX); + + log_info(LD_REND, "Encrypted revision counter %d to %ld", + (int) seconds_since_start_of_srv, (long int) rev_counter); + + hs_desc->desc->plaintext_data.revision_counter = rev_counter; } /* Encode and sign the service descriptor desc and upload it to the diff --git a/src/or/or.h b/src/or/or.h index 528159b4c6..4f071889a2 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -79,6 +79,7 @@ #include "or/replaycache.h" #include "lib/crypt_ops/crypto_curve25519.h" #include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_ope.h" #include "tor_queue.h" #include "common/token_bucket.h" #include "common/util_format.h" From 5fb6f656dfad3ddb178b76448742bfc3e2e834da Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:38:14 +0300 Subject: [PATCH 05/13] Use approx_time() instead of time(NULL) in some HS functions. These were breaking our unittests. --- src/or/hs_service.c | 2 +- src/or/voting_schedule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 76b1634561..70d9c1c6f0 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -433,7 +433,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { goto err; } - ip->time_to_expire = time(NULL) + + ip->time_to_expire = approx_time() + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); } diff --git a/src/or/voting_schedule.c b/src/or/voting_schedule.c index 6edde3f229..8c56a10526 100644 --- a/src/or/voting_schedule.c +++ b/src/or/voting_schedule.c @@ -168,7 +168,7 @@ voting_schedule_get_next_valid_after_time(void) done: if (need_to_recalculate_voting_schedule) { - voting_schedule_recalculate_timing(get_options(), now); + voting_schedule_recalculate_timing(get_options(), approx_time()); voting_schedule.created_on_demand = 1; } From 2e8d4139a77111d5b24726e970744378dcb92baa Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:38:46 +0300 Subject: [PATCH 06/13] Fix up some unittests by being more careful with the local time. Now that the rev counter depends on the local time, we need to be more careful in the unittests. Some unittests were breaking because they were using consensus values from 1985, but they were not updating the local time appropriately. That was causing the OPE module to complain that it was trying to encrypt insanely large values. --- src/test/test_hs_common.c | 3 ++- src/test/test_hs_service.c | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 6296a709dc..47a021312a 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -1361,7 +1361,8 @@ run_reachability_scenario(const reachability_cfg_t *cfg, int num_scenario) mock_service_ns->sr_info.previous_srv = cfg->service_previous_srv; /* Initialize a service to get keys. */ - service = helper_init_service(time(NULL)); + update_approx_time(mock_service_ns->valid_after); + service = helper_init_service(mock_service_ns->valid_after+1); /* * === Client setup === diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 684ac98f42..e328ca5bdc 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1042,7 +1042,7 @@ static void test_rotate_descriptors(void *arg) { int ret; - time_t next_rotation_time, now = time(NULL); + time_t next_rotation_time, now; hs_service_t *service; hs_service_descriptor_t *desc_next; @@ -1066,6 +1066,9 @@ test_rotate_descriptors(void *arg) tt_int_op(ret, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after); + update_approx_time(mock_ns.valid_after+1); + now = mock_ns.valid_after+1; + /* Create a service with a default descriptor and state. It's added to the * global map. */ service = helper_create_service(); @@ -1104,6 +1107,9 @@ test_rotate_descriptors(void *arg) tt_int_op(ret, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after); + update_approx_time(mock_ns.valid_after+1); + now = mock_ns.valid_after+1; + /* Note down what to expect for the next rotation time which is 01:00 + 23h * meaning 00:00:00. */ next_rotation_time = mock_ns.valid_after + (23 * 60 * 60); @@ -1166,6 +1172,9 @@ test_build_update_descriptors(void *arg) tt_int_op(ret, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after); + update_approx_time(mock_ns.valid_after+1); + now = mock_ns.valid_after+1; + /* Create a service without a current descriptor to trigger a build. */ service = helper_create_service(); tt_assert(service); @@ -1307,6 +1316,9 @@ test_build_update_descriptors(void *arg) &mock_ns.fresh_until); tt_int_op(ret, OP_EQ, 0); + update_approx_time(mock_ns.valid_after+1); + now = mock_ns.valid_after+1; + /* Create a service without a current descriptor to trigger a build. */ service = helper_create_service(); tt_assert(service); @@ -1361,7 +1373,7 @@ static void test_upload_descriptors(void *arg) { int ret; - time_t now = time(NULL); + time_t now; hs_service_t *service; (void) arg; @@ -1380,6 +1392,10 @@ test_upload_descriptors(void *arg) ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", &mock_ns.fresh_until); tt_int_op(ret, OP_EQ, 0); + voting_schedule_recalculate_timing(get_options(), mock_ns.valid_after); + + update_approx_time(mock_ns.valid_after+1); + now = mock_ns.valid_after+1; /* Create a service with no descriptor. It's added to the global map. */ service = hs_service_new(get_options()); From 1d2333405eed9ec616eaa3980db5c4b272bb5aa8 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:51:00 +0300 Subject: [PATCH 07/13] Remove now useless rev counter state file code. We are not using the state file for rev counters anymore, we just generate them on the fly! --- src/or/hs_service.c | 185 ------------------------------------- src/or/hs_service.h | 9 -- src/test/test_hs_service.c | 62 ------------- 3 files changed, 256 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 70d9c1c6f0..dd91e97bbe 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1763,7 +1763,6 @@ service_desc_schedule_upload(hs_service_descriptor_t *desc, /* 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, @@ -2338,187 +2337,6 @@ 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->HidServRevCounter); - state->HidServRevCounter = 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, - const 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: - tor_assert(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(const 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->HidServRevCounter ; line ; line = line->next) { - int service_found = 0; - uint64_t rev_counter = 0; - - tor_assert(!strcmp(line->key, "HidServRevCounter")); - - /* 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, - &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 -increment_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. We do this by encrypting a * timestamp using an OPE scheme and using the ciphertext as our revision * counter. @@ -2664,9 +2482,6 @@ upload_descriptor_to_all(const hs_service_t *service, safe_str_client(service->onion_address), fmt_next_time); } - /* Update the revision counter of this descriptor */ - increment_descriptor_revision_counter(desc->desc); - smartlist_free(responsible_dirs); return; } diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 4676042b54..a5f49ffef9 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -346,19 +346,10 @@ 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); #define service_descriptor_free(d) \ FREE_AND_NULL(hs_service_descriptor_t, \ service_descriptor_free_, (d)) - -STATIC uint64_t -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_); diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index e328ca5bdc..0350face46 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -1430,66 +1430,6 @@ test_upload_descriptors(void *arg) UNMOCK(get_or_state); } -/** 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, 66, - sizeof(desc_one->blinded_kp.pubkey.pubkey)); - memset(&desc_two->blinded_kp.pubkey.pubkey, 240, - 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); - tt_u64_op(cached_rev_counter, 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_u64_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_u64_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); -} - /** Global vars used by test_rendezvous1_parsing() */ static char rend1_payload[RELAY_PAYLOAD_SIZE]; static size_t rend1_payload_len = 0; @@ -1643,8 +1583,6 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "upload_descriptors", test_upload_descriptors, TT_FORK, NULL, NULL }, - { "revision_counter_state", test_revision_counter_state, TT_FORK, - NULL, NULL }, { "rendezvous1_parsing", test_rendezvous1_parsing, TT_FORK, NULL, NULL }, From f00b7a7faa443d224dcd46dd7511f42763e64584 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 14 Jun 2018 15:53:31 +0300 Subject: [PATCH 08/13] Add changes file. --- changes/bug25552 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changes/bug25552 diff --git a/changes/bug25552 b/changes/bug25552 new file mode 100644 index 0000000000..8d0488a462 --- /dev/null +++ b/changes/bug25552 @@ -0,0 +1,5 @@ + o Major feature (onion services): + - Improve revision counter generation in next-gen onion services. Onion + services can now scale by hosting multiple instances on different hosts + without synchronization between them, which was previously impossible + because descriptors would get rejected by HSDirs. Addresses ticket 25552. From d8b71609cb9c2ce15b6a26d11b2bcdedf15b915e Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 17 Jul 2018 12:03:18 -0400 Subject: [PATCH 09/13] Compute OPE cipher structure only when needed. The OPE cipher is tied to the current blinded key which is tied to the current time period. Hence create the OPE cipher structure when we create a new descriptor (and build its blinded key). --- src/or/hs_service.c | 62 ++++++++++++++++++++++++++------------------- src/or/hs_service.h | 4 +++ 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index dd91e97bbe..d6416ebcd6 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1074,6 +1074,7 @@ service_descriptor_free_(hs_service_descriptor_t *desc) SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s)); smartlist_free(desc->previous_hsdirs); } + crypto_ope_free(desc->ope_cipher); tor_free(desc); } @@ -1378,13 +1379,30 @@ build_service_desc_plaintext(const hs_service_t *service, return ret; } +/** Compute the descriptor's OPE cipher for encrypting revision counters. */ +static crypto_ope_t * +generate_ope_cipher_for_desc(const hs_service_descriptor_t *hs_desc) +{ + /* Compute OPE key as H("rev-counter-generation" | blinded privkey) */ + uint8_t key[DIGEST256_LEN]; + crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA3_256); + const char ope_key_prefix[] = "rev-counter-generation"; + const ed25519_secret_key_t *eph_privkey = &hs_desc->blinded_kp.seckey; + crypto_digest_add_bytes(digest, ope_key_prefix, sizeof(ope_key_prefix)); + crypto_digest_add_bytes(digest, (char*)eph_privkey->seckey, + sizeof(eph_privkey->seckey)); + crypto_digest_get_digest(digest, (char *)key, sizeof(key)); + crypto_digest_free(digest); + + return crypto_ope_new(key); +} + /* 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) + hs_service_descriptor_t *desc) { int ret = 0; ed25519_keypair_t kp; @@ -1400,10 +1418,17 @@ build_service_desc_keys(const hs_service_t *service, 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); + hs_build_blinded_keypair(&kp, NULL, 0, desc->time_period_num, + &desc->blinded_kp); /* Let's not keep too much traces of our keys in memory. */ memwipe(&kp, 0, sizeof(kp)); + /* Compute the OPE cipher struct (it's tied to the current blinded key) */ + log_info(LD_GENERAL, + "Getting OPE for TP#%u", (unsigned) desc->time_period_num); + tor_assert_nonfatal(!desc->ope_cipher); + desc->ope_cipher = generate_ope_cipher_for_desc(desc); + /* 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) { @@ -1438,10 +1463,12 @@ build_service_descriptor(hs_service_t *service, time_t now, tor_assert(desc_out); desc = service_descriptor_new(); + + /* Set current time period */ 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) { + if (build_service_desc_keys(service, desc) < 0) { goto err; } /* Setup plaintext descriptor content. */ @@ -2395,28 +2422,11 @@ set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, seconds_since_start_of_srv = OPE_INPUT_MAX; } - /* Now we compute the actual revision counter value by encrypting the - plaintext using an OPE construction: */ - - /* First, compute OPE key as: K = H("rev-counter-generation" | S) */ - uint8_t key[DIGEST256_LEN]; - { - crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA3_256); - const char ope_key_prefix[] = "rev-counter-generation"; - ed25519_secret_key_t *eph_privkey = &hs_desc->blinded_kp.seckey; - crypto_digest_add_bytes(digest, ope_key_prefix, sizeof(ope_key_prefix)); - crypto_digest_add_bytes(digest, (char*)eph_privkey->seckey, - sizeof(eph_privkey->seckey)); - crypto_digest_get_digest(digest, (char *)key, sizeof(key)); - crypto_digest_free(digest); - } - - { /* Now encrypt the revision counter! */ - crypto_ope_t *ope = NULL; - ope = crypto_ope_new(key); - rev_counter = crypto_ope_encrypt(ope, (int) seconds_since_start_of_srv); - crypto_ope_free(ope); - } + /* Now we compute the final revision counter value by encrypting the + plaintext using our OPE cipher: */ + tor_assert(hs_desc->ope_cipher); + rev_counter = crypto_ope_encrypt(hs_desc->ope_cipher, + (int) seconds_since_start_of_srv); /* The OPE module returns UINT64_MAX in case of errors. */ tor_assert_nonfatal(rev_counter < UINT64_MAX); diff --git a/src/or/hs_service.h b/src/or/hs_service.h index a5f49ffef9..e1f125d76e 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -131,6 +131,10 @@ typedef struct hs_service_descriptor_t { * from this list, this means we received new dirinfo and we need to * reupload our descriptor. */ smartlist_t *previous_hsdirs; + + /** The OPE cipher for encrypting revision counters for this descriptor. + * Tied to the descriptor blinded key. */ + crypto_ope_t *ope_cipher; } hs_service_descriptor_t; /* Service key material. */ From 0140052a356cdcfe0e2da25aee6b8c376815528c Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 10 Jul 2018 20:10:22 +0300 Subject: [PATCH 10/13] Make the OPE scheme return CRYPTO_OPE_ERROR on error. Instead of UINT64_MAX. --- src/lib/crypt_ops/crypto_ope.c | 5 +++-- src/lib/crypt_ops/crypto_ope.h | 2 ++ src/or/hs_service.c | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/crypt_ops/crypto_ope.c b/src/lib/crypt_ops/crypto_ope.c index dd04ffbaaa..644f3bae4a 100644 --- a/src/lib/crypt_ops/crypto_ope.c +++ b/src/lib/crypt_ops/crypto_ope.c @@ -149,7 +149,8 @@ crypto_ope_free_(crypto_ope_t *ope) /** * Return the encrypted value corresponding to input. The input value - * must be in range 1..OPE_INPUT_MAX. Returns UINT64_MAX on an invalid input. + * must be in range 1..OPE_INPUT_MAX. Returns CRYPTO_OPE_ERROR on an invalid + * input. * * NOTE: this function is not constant-time. */ @@ -157,7 +158,7 @@ uint64_t crypto_ope_encrypt(const crypto_ope_t *ope, int plaintext) { if (plaintext <= 0 || plaintext > OPE_INPUT_MAX) - return UINT64_MAX; + return CRYPTO_OPE_ERROR; const int sample_idx = (plaintext / SAMPLE_INTERVAL); const int starting_iv = sample_idx * SAMPLE_INTERVAL; diff --git a/src/lib/crypt_ops/crypto_ope.h b/src/lib/crypt_ops/crypto_ope.h index 823524f84e..19ec3e495e 100644 --- a/src/lib/crypt_ops/crypto_ope.h +++ b/src/lib/crypt_ops/crypto_ope.h @@ -26,6 +26,8 @@ */ #define OPE_INPUT_MAX (1<<18) +#define CRYPTO_OPE_ERROR UINT64_MAX + typedef struct crypto_ope_c crypto_ope_t; crypto_ope_t *crypto_ope_new(const uint8_t *key); diff --git a/src/or/hs_service.c b/src/or/hs_service.c index d6416ebcd6..3500e497bd 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2428,8 +2428,8 @@ set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, rev_counter = crypto_ope_encrypt(hs_desc->ope_cipher, (int) seconds_since_start_of_srv); - /* The OPE module returns UINT64_MAX in case of errors. */ - tor_assert_nonfatal(rev_counter < UINT64_MAX); + /* The OPE module returns CRYPTO_OPE_ERROR in case of errors. */ + tor_assert_nonfatal(rev_counter < CRYPTO_OPE_ERROR); log_info(LD_REND, "Encrypted revision counter %d to %ld", (int) seconds_since_start_of_srv, (long int) rev_counter); From 4cfade2f4682fa2554f79b2edf51f9dde8f924dc Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Thu, 12 Jul 2018 16:51:31 +0300 Subject: [PATCH 11/13] Set revision counter before uploading, not during building. We only build a descriptor once, and we just re-encode it (and change its intro points if needed) before uploading. Hence we should set the revision counter before uploading, not during building. --- src/or/hs_service.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 3500e497bd..5430ef4d87 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1446,15 +1446,11 @@ build_service_desc_keys(const hs_service_t *service, * the update function. On success, desc_out will point to the newly allocated * descriptor object. * - * If is_current is true, this is the current service descriptor, - * otherwise it's the next one. - * * 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, - bool is_current) + hs_service_descriptor_t **desc_out) { char *encoded_desc; hs_service_descriptor_t *desc; @@ -1480,9 +1476,6 @@ build_service_descriptor(hs_service_t *service, time_t now, goto err; } - /* Set the revision counter for this descriptor */ - set_descriptor_revision_counter(desc, now, is_current); - /* 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. */ @@ -1547,9 +1540,9 @@ build_descriptors_for_new_service(hs_service_t *service, time_t now) /* Build descriptors. */ build_service_descriptor(service, now, current_desc_tp, - &service->desc_current, 1); + &service->desc_current); build_service_descriptor(service, now, next_desc_tp, - &service->desc_next, 0); + &service->desc_next); log_info(LD_REND, "Hidden service %s has just started. Both descriptors " "built. Now scheduled for upload.", safe_str_client(service->onion_address)); @@ -1580,7 +1573,7 @@ build_all_descriptors(time_t now) if (service->desc_next == NULL) { build_service_descriptor(service, now, hs_get_next_time_period_num(0), - &service->desc_next, 0); + &service->desc_next); log_info(LD_REND, "Hidden service %s next descriptor successfully " "built. Now scheduled for upload.", safe_str_client(service->onion_address)); @@ -2631,6 +2624,10 @@ run_upload_descriptor_event(time_t now) * accurate because all circuits have been established. */ build_desc_intro_points(service, desc, now); + /* Set the desc revision counter right before uploading */ + set_descriptor_revision_counter(desc, approx_time(), + service->desc_current == desc); + upload_descriptor_to_all(service, desc); } FOR_EACH_DESCRIPTOR_END; } FOR_EACH_SERVICE_END; From 9e6235d290fce3eac4b9aa39da21a4f4479292c6 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 17 Jul 2018 11:00:18 -0400 Subject: [PATCH 12/13] Fix time source bug in sr_state_get_start_time_of_current_protocol_run(). The following bug was causing many issues for this branch in chutney: In sr_state_get_start_time_of_current_protocol_run() we were using the consensus valid-after to calculate beginning_of_current_round, but we were using time(NULL) to calculate the current_round slot. This was causing time sync issues when the consensus valid-after and time(NULL) were disagreeing on what the current round is. Our fix is to use the consensus valid-after in both places. This also means that we are not using 'now' (aka time(NULL)) anymore in that function, and hence we can remove that argument from the function (and its callers). I'll do this in the next commit so that we keep things separated. Furthermore, we fix a unittest that broke. --- src/or/hs_common.c | 3 +-- src/or/hs_service.c | 25 +++++++++---------------- src/or/shared_random_client.c | 27 +++++++++++++++++---------- src/or/shared_random_client.h | 4 ++-- src/test/test_hs_common.c | 9 +++++---- src/test/test_shared_random.c | 14 +++++--------- 6 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/or/hs_common.c b/src/or/hs_common.c index e88a04620e..0750785e44 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1102,8 +1102,7 @@ hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) /* Get start time of next TP and of current SRV protocol run, and check if we * are between them. */ valid_after = consensus->valid_after; - srv_start_time = - sr_state_get_start_time_of_current_protocol_run(valid_after); + srv_start_time = sr_state_get_start_time_of_current_protocol_run(); tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); if (valid_after >= srv_start_time && valid_after < tp_start_time) { diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 5430ef4d87..b4460b2ac4 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -1946,19 +1946,12 @@ cleanup_intro_points(hs_service_t *service, time_t now) /* Set the next rotation time of the descriptors for the given service for the * time now. */ static void -set_rotation_time(hs_service_t *service, time_t now) +set_rotation_time(hs_service_t *service) { - time_t valid_after; - const networkstatus_t *ns = networkstatus_get_live_consensus(now); - if (ns) { - valid_after = ns->valid_after; - } else { - valid_after = now; - } - tor_assert(service); + service->state.next_rotation_time = - sr_state_get_start_time_of_current_protocol_run(valid_after) + + sr_state_get_start_time_of_current_protocol_run() + sr_state_get_protocol_run_duration(); { @@ -2025,7 +2018,7 @@ should_rotate_descriptors(hs_service_t *service, time_t now) * will be freed, the next one put in as the current and finally the next * descriptor pointer is NULLified. */ static void -rotate_service_descriptors(hs_service_t *service, time_t now) +rotate_service_descriptors(hs_service_t *service) { if (service->desc_current) { /* Close all IP circuits for the descriptor. */ @@ -2040,7 +2033,7 @@ rotate_service_descriptors(hs_service_t *service, time_t now) service->desc_next = NULL; /* We've just rotated, set the next time for the rotation. */ - set_rotation_time(service, now); + set_rotation_time(service); } /* Rotate descriptors for each service if needed. A non existing current @@ -2068,7 +2061,7 @@ rotate_all_descriptors(time_t now) service->desc_current, service->desc_next, safe_str_client(service->onion_address)); - rotate_service_descriptors(service, now); + rotate_service_descriptors(service); } FOR_EACH_SERVICE_END; } @@ -2090,7 +2083,7 @@ run_housekeeping_event(time_t now) /* Set the next rotation time of the descriptors. If it's Oct 25th * 23:47:00, the next rotation time is when the next SRV is computed * which is at Oct 26th 00:00:00 that is in 13 minutes. */ - set_rotation_time(service, now); + set_rotation_time(service); } /* Cleanup invalid intro points from the service descriptor. */ @@ -2390,9 +2383,9 @@ set_descriptor_revision_counter(hs_service_descriptor_t *hs_desc, time_t now, * time of the current protocol run. */ if (is_current) { - srv_start = sr_state_get_start_time_of_previous_protocol_run(now); + srv_start = sr_state_get_start_time_of_previous_protocol_run(); } else { - srv_start = sr_state_get_start_time_of_current_protocol_run(now); + srv_start = sr_state_get_start_time_of_current_protocol_run(); } log_info(LD_REND, "Setting rev counter for TP #%u: " diff --git a/src/or/shared_random_client.c b/src/or/shared_random_client.c index d787b6b2ec..a03073b3d9 100644 --- a/src/or/shared_random_client.c +++ b/src/or/shared_random_client.c @@ -223,33 +223,40 @@ sr_parse_srv(const smartlist_t *args) return srv; } -/** 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. */ +/** Return the start time of the current SR protocol run using the times from + * the current consensus. For example, if the latest consensus valid-after is + * 23/06/2017 23:00:00 and a full SR protocol run is 24 hours, this function + * returns 23/06/2017 00:00:00. */ time_t -sr_state_get_start_time_of_current_protocol_run(time_t now) +sr_state_get_start_time_of_current_protocol_run(void) { 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(); + time_t beginning_of_curr_round = get_start_time_of_current_round(); /* Get current SR protocol round */ - int current_round = (now / voting_interval) % total_rounds; + int curr_round_slot; + curr_round_slot = (beginning_of_curr_round / 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; + time_t time_elapsed_since_start_of_run = curr_round_slot * voting_interval; + + log_debug(LD_GENERAL, "Current SRV proto run: Start of current round: %u. " + "Time elapsed: %u (%d)", (unsigned) beginning_of_curr_round, + (unsigned) time_elapsed_since_start_of_run, voting_interval); + + return beginning_of_curr_round - time_elapsed_since_start_of_run; } /** Return the start time of the previous SR protocol run. See * sr_state_get_start_time_of_current_protocol_run() for more details. */ time_t -sr_state_get_start_time_of_previous_protocol_run(time_t now) +sr_state_get_start_time_of_previous_protocol_run(void) { time_t start_time_of_current_run = - sr_state_get_start_time_of_current_protocol_run(now); + sr_state_get_start_time_of_current_protocol_run(); /* We get the start time of previous protocol run, by getting the start time * of current run and the subtracting a full protocol run from that. */ diff --git a/src/or/shared_random_client.h b/src/or/shared_random_client.h index 35ebb1bd57..8261686332 100644 --- a/src/or/shared_random_client.h +++ b/src/or/shared_random_client.h @@ -34,8 +34,8 @@ sr_srv_t *sr_parse_srv(const smartlist_t *args); /* Number of phase we have in a protocol. */ #define SHARED_RANDOM_N_PHASES 2 -time_t sr_state_get_start_time_of_current_protocol_run(time_t now); -time_t sr_state_get_start_time_of_previous_protocol_run(time_t now); +time_t sr_state_get_start_time_of_current_protocol_run(void); +time_t sr_state_get_start_time_of_previous_protocol_run(void); unsigned int sr_state_get_phase_duration(void); unsigned int sr_state_get_protocol_run_duration(void); time_t get_start_time_of_current_round(void); diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index 47a021312a..737c9ce5f5 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -1338,6 +1338,10 @@ run_reachability_scenario(const reachability_cfg_t *cfg, int num_scenario) &mock_service_ns->fresh_until); voting_schedule_recalculate_timing(get_options(), mock_service_ns->valid_after); + /* Check that service is in the right time period point */ + tt_int_op(hs_in_period_between_tp_and_srv(mock_service_ns, 0), OP_EQ, + cfg->service_in_new_tp); + /* Set client consensus time. */ set_consensus_times(cfg->client_valid_after, &mock_client_ns->valid_after); @@ -1347,10 +1351,7 @@ run_reachability_scenario(const reachability_cfg_t *cfg, int num_scenario) &mock_client_ns->fresh_until); voting_schedule_recalculate_timing(get_options(), mock_client_ns->valid_after); - - /* New time period checks for this scenario. */ - tt_int_op(hs_in_period_between_tp_and_srv(mock_service_ns, 0), OP_EQ, - cfg->service_in_new_tp); + /* Check that client is in the right time period point */ tt_int_op(hs_in_period_between_tp_and_srv(mock_client_ns, 0), OP_EQ, cfg->client_in_new_tp); diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 55910a351e..5b3fdbb103 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -249,8 +249,7 @@ test_get_start_time_of_current_run(void *arg) ¤t_time); tt_int_op(retval, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), current_time); - run_start_time = - sr_state_get_start_time_of_current_protocol_run(current_time); + run_start_time = sr_state_get_start_time_of_current_protocol_run(); /* Compare it with the correct result */ format_iso_time(tbuf, run_start_time); @@ -262,8 +261,7 @@ test_get_start_time_of_current_run(void *arg) ¤t_time); tt_int_op(retval, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), current_time); - run_start_time = - sr_state_get_start_time_of_current_protocol_run(current_time); + run_start_time = sr_state_get_start_time_of_current_protocol_run(); /* Compare it with the correct result */ format_iso_time(tbuf, run_start_time); @@ -275,8 +273,7 @@ test_get_start_time_of_current_run(void *arg) ¤t_time); tt_int_op(retval, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), current_time); - run_start_time = - sr_state_get_start_time_of_current_protocol_run(current_time); + run_start_time = sr_state_get_start_time_of_current_protocol_run(); /* Compare it with the correct result */ format_iso_time(tbuf, run_start_time); @@ -298,8 +295,7 @@ test_get_start_time_of_current_run(void *arg) ¤t_time); tt_int_op(retval, OP_EQ, 0); voting_schedule_recalculate_timing(get_options(), current_time); - run_start_time = - sr_state_get_start_time_of_current_protocol_run(current_time); + run_start_time = sr_state_get_start_time_of_current_protocol_run(); /* Compare it with the correct result */ format_iso_time(tbuf, run_start_time); @@ -332,7 +328,7 @@ test_get_start_time_functions(void *arg) voting_schedule_recalculate_timing(get_options(), now); time_t start_time_of_protocol_run = - sr_state_get_start_time_of_current_protocol_run(now); + sr_state_get_start_time_of_current_protocol_run(); tt_assert(start_time_of_protocol_run); /* Check that the round start time of the beginning of the run, is itself */ From 14b507e5207ce7e581c5fc773921f8cf65d08247 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 16 Jul 2018 15:28:11 +0300 Subject: [PATCH 13/13] Improve a log message. --- src/or/hs_service.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/or/hs_service.c b/src/or/hs_service.c index b4460b2ac4..560f48fb56 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2332,13 +2332,17 @@ upload_descriptor_to_hsdir(const hs_service_t *service, int is_next_desc = (service->desc_next == desc); const uint8_t *idx = (is_next_desc) ? hsdir->hsdir_index.store_second: hsdir->hsdir_index.store_first; + char *blinded_pubkey_log_str = + tor_strdup(hex_str((char*)&desc->blinded_kp.pubkey.pubkey, 32)); log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 - " initiated upload request to %s with index %s", + " initiated upload request to %s with index %s (%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 *) idx, 32))); + safe_str_client(hex_str((const char *) idx, 32)), + safe_str_client(blinded_pubkey_log_str)); + tor_free(blinded_pubkey_log_str); /* Fire a UPLOAD control port event. */ hs_control_desc_event_upload(service->onion_address, hsdir->identity,