summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/compat.c35
-rw-r--r--src/common/compress.c6
-rw-r--r--src/common/container.c4
-rw-r--r--src/common/crypto.c29
-rw-r--r--src/common/crypto.h9
-rw-r--r--src/common/crypto_ed25519.c61
-rw-r--r--src/common/crypto_ed25519.h2
-rw-r--r--src/common/memarea.c3
-rw-r--r--src/common/sandbox.c8
-rw-r--r--src/common/tortls.c7
-rw-r--r--src/common/util.c6
-rw-r--r--src/common/util.h12
-rw-r--r--src/common/util_bug.c4
-rw-r--r--src/common/util_bug.h5
-rw-r--r--src/ext/ed25519/donna/curve25519-donna-sse2.h7
-rw-r--r--src/ext/ed25519/donna/ed25519_donna_tor.h5
-rw-r--r--src/ext/ed25519/donna/ed25519_tor.c39
-rw-r--r--src/ext/ed25519/ref10/blinding.c49
-rw-r--r--src/ext/ed25519/ref10/ed25519_ref10.h4
-rw-r--r--src/or/buffers.c96
-rw-r--r--src/or/buffers.h3
-rw-r--r--src/or/channel.c47
-rw-r--r--src/or/channel.h1
-rw-r--r--src/or/channeltls.c3
-rw-r--r--src/or/circuitbuild.c546
-rw-r--r--src/or/circuitbuild.h9
-rw-r--r--src/or/circuitlist.c45
-rw-r--r--src/or/circuitlist.h2
-rw-r--r--src/or/circuitmux_ewma.c6
-rw-r--r--src/or/circuituse.c92
-rw-r--r--src/or/circuituse.h3
-rw-r--r--src/or/command.c3
-rw-r--r--src/or/config.c36
-rw-r--r--src/or/config.h4
-rw-r--r--src/or/connection.c3
-rw-r--r--src/or/connection_edge.c149
-rw-r--r--src/or/connection_edge.h3
-rw-r--r--src/or/connection_or.c5
-rw-r--r--src/or/control.c89
-rw-r--r--src/or/cpuworker.c4
-rw-r--r--src/or/dircollate.c2
-rw-r--r--src/or/directory.c149
-rw-r--r--src/or/directory.h26
-rw-r--r--src/or/dirserv.c16
-rw-r--r--src/or/dirvote.c9
-rw-r--r--src/or/dns.c20
-rw-r--r--src/or/entrynodes.c2
-rw-r--r--src/or/ext_orport.c2
-rw-r--r--src/or/hs_cache.c6
-rw-r--r--src/or/hs_cell.c584
-rw-r--r--src/or/hs_cell.h75
-rw-r--r--src/or/hs_circuit.c1056
-rw-r--r--src/or/hs_circuit.h58
-rw-r--r--src/or/hs_client.c48
-rw-r--r--src/or/hs_client.h16
-rw-r--r--src/or/hs_common.c1037
-rw-r--r--src/or/hs_common.h162
-rw-r--r--src/or/hs_config.c582
-rw-r--r--src/or/hs_config.h24
-rw-r--r--src/or/hs_descriptor.c285
-rw-r--r--src/or/hs_descriptor.h25
-rw-r--r--src/or/hs_ident.c88
-rw-r--r--src/or/hs_ident.h130
-rw-r--r--src/or/hs_intropoint.c16
-rw-r--r--src/or/hs_intropoint.h22
-rw-r--r--src/or/hs_ntor.c50
-rw-r--r--src/or/hs_ntor.h24
-rw-r--r--src/or/hs_service.c3139
-rw-r--r--src/or/hs_service.h341
-rw-r--r--src/or/include.am26
-rw-r--r--src/or/main.c74
-rw-r--r--src/or/microdesc.c2
-rw-r--r--src/or/networkstatus.c19
-rw-r--r--src/or/networkstatus.h1
-rw-r--r--src/or/nodelist.c145
-rw-r--r--src/or/nodelist.h2
-rw-r--r--src/or/ntmain.c5
-rw-r--r--src/or/onion_tap.c7
-rw-r--r--src/or/or.h46
-rw-r--r--src/or/parsecommon.c2
-rw-r--r--src/or/parsecommon.h3
-rw-r--r--src/or/policies.c2
-rw-r--r--src/or/protover.h5
-rw-r--r--src/or/relay.c42
-rw-r--r--src/or/rendclient.c57
-rw-r--r--src/or/rendcommon.c8
-rw-r--r--src/or/rendcommon.h7
-rw-r--r--src/or/rendmid.c1
-rw-r--r--src/or/rendservice.c536
-rw-r--r--src/or/rendservice.h28
-rw-r--r--src/or/rephist.c99
-rw-r--r--src/or/rephist.h3
-rw-r--r--src/or/router.c34
-rw-r--r--src/or/router.h2
-rw-r--r--src/or/routerkeys.c102
-rw-r--r--src/or/routerkeys.h1
-rw-r--r--src/or/routerlist.c9
-rw-r--r--src/or/routerparse.c20
-rw-r--r--src/or/scheduler.c16
-rw-r--r--src/or/shared_random.c46
-rw-r--r--src/or/shared_random.h3
-rw-r--r--src/or/shared_random_state.c44
-rw-r--r--src/or/shared_random_state.h5
-rw-r--r--src/or/statefile.c8
-rw-r--r--src/or/torcert.c2
-rw-r--r--src/test/ed25519_exts_ref.py36
-rw-r--r--src/test/ed25519_vectors.inc32
-rw-r--r--src/test/hs_build_address.py37
-rw-r--r--src/test/hs_test_helpers.c30
-rw-r--r--src/test/hs_test_helpers.h3
-rw-r--r--src/test/include.am8
-rw-r--r--src/test/rend_test_helpers.c16
-rw-r--r--src/test/rend_test_helpers.h1
-rw-r--r--src/test/test.c8
-rw-r--r--src/test/test.h5
-rw-r--r--src/test/test_buffers.c27
-rw-r--r--src/test/test_channel.c30
-rw-r--r--src/test/test_config.c92
-rw-r--r--src/test/test_connection.c93
-rw-r--r--src/test/test_connection.h13
-rw-r--r--src/test/test_containers.c2
-rw-r--r--src/test/test_crypto.c103
-rw-r--r--src/test/test_dir.c148
-rw-r--r--src/test/test_helpers.c138
-rw-r--r--src/test/test_helpers.h10
-rw-r--r--src/test/test_hs.c120
-rw-r--r--src/test/test_hs_cache.c8
-rw-r--r--src/test/test_hs_cell.c130
-rw-r--r--src/test/test_hs_client.c286
-rw-r--r--src/test/test_hs_common.c507
-rw-r--r--src/test/test_hs_config.c487
-rw-r--r--src/test/test_hs_descriptor.c18
-rw-r--r--src/test/test_hs_intropoint.c307
-rw-r--r--src/test/test_hs_ntor.c114
-rw-r--r--src/test/test_hs_service.c1427
-rw-r--r--src/test/test_introduce.c4
-rwxr-xr-xsrc/test/test_key_expiration.sh129
-rw-r--r--src/test/test_rendcache.c16
-rw-r--r--src/test/test_scheduler.c4
-rw-r--r--src/test/test_shared_random.c120
-rw-r--r--src/test/test_socks.c26
-rw-r--r--src/test/test_util.c11
-rw-r--r--src/trunnel/channelpadding_negotiation.c10
-rw-r--r--src/trunnel/channelpadding_negotiation.h10
-rw-r--r--src/trunnel/ed25519_cert.trunnel6
-rw-r--r--src/trunnel/hs/cell_rendezvous.c292
-rw-r--r--src/trunnel/hs/cell_rendezvous.h118
-rw-r--r--src/trunnel/hs/cell_rendezvous.trunnel18
-rw-r--r--src/trunnel/include.am2
-rw-r--r--src/win32/orconfig.h2
150 files changed, 13850 insertions, 2004 deletions
diff --git a/src/common/compat.c b/src/common/compat.c
index 4d110aba35..836b3813e0 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -2349,15 +2349,34 @@ get_parent_directory(char *fname)
static char *
alloc_getcwd(void)
{
-#ifdef PATH_MAX
-#define MAX_CWD PATH_MAX
+#ifdef HAVE_GET_CURRENT_DIR_NAME
+ /* Glibc makes this nice and simple for us. */
+ char *cwd = get_current_dir_name();
+ char *result = NULL;
+ if (cwd) {
+ /* We make a copy here, in case tor_malloc() is not malloc(). */
+ result = tor_strdup(cwd);
+ raw_free(cwd); // alias for free to avoid tripping check-spaces.
+ }
+ return result;
#else
-#define MAX_CWD 4096
-#endif
+ size_t size = 1024;
+ char *buf = NULL;
+ char *ptr = NULL;
- char path_buf[MAX_CWD];
- char *path = getcwd(path_buf, sizeof(path_buf));
- return path ? tor_strdup(path) : NULL;
+ while (ptr == NULL) {
+ buf = tor_realloc(buf, size);
+ ptr = getcwd(buf, size);
+
+ if (ptr == NULL && errno != ERANGE) {
+ tor_free(buf);
+ return NULL;
+ }
+
+ size *= 2;
+ }
+ return buf;
+#endif
}
#endif
@@ -2600,7 +2619,7 @@ tor_inet_pton(int af, const char *src, void *dst)
long r = strtol(src, &next, 16);
if (next == NULL || next == src) {
/* The 'next == src' error case can happen on versions of openbsd
- * where treats "0xfoo" as an error, rather than as "0" followed by
+ * which treat "0xfoo" as an error, rather than as "0" followed by
* "xfoo". */
return 0;
}
diff --git a/src/common/compress.c b/src/common/compress.c
index 7926faaa60..472268a439 100644
--- a/src/common/compress.c
+++ b/src/common/compress.c
@@ -574,6 +574,12 @@ tor_compress_process(tor_compress_state_t *state,
if (BUG((rv == TOR_COMPRESS_OK) &&
*in_len == in_len_orig &&
*out_len == out_len_orig)) {
+ log_warn(LD_GENERAL,
+ "More info on the bug: method == %s, finish == %d, "
+ " *in_len == in_len_orig == %lu, "
+ "*out_len == out_len_orig == %lu",
+ compression_method_get_human_name(state->method), finish,
+ (unsigned long)in_len_orig, (unsigned long)out_len_orig);
return TOR_COMPRESS_ERROR;
}
diff --git a/src/common/container.c b/src/common/container.c
index 689e7e55e9..8645cb4826 100644
--- a/src/common/container.c
+++ b/src/common/container.c
@@ -843,13 +843,13 @@ smartlist_sort_pointers(smartlist_t *sl)
* }
*
* void timer_heap_insert(smartlist_t *heap, timer_t *timer) {
- * smartlist_pqueue_add(heap, compare, STRUCT_OFFSET(timer_t, heap_index),
+ * smartlist_pqueue_add(heap, compare, offsetof(timer_t, heap_index),
* timer);
* }
*
* void timer_heap_pop(smartlist_t *heap) {
* return smartlist_pqueue_pop(heap, compare,
- * STRUCT_OFFSET(timer_t, heap_index));
+ * offsetof(timer_t, heap_index));
* }
*/
diff --git a/src/common/crypto.c b/src/common/crypto.c
index 0fc8474832..4d6a70bc49 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -1238,9 +1238,12 @@ crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen,
* - The beginning of the source data prefixed with a 16-byte symmetric key,
* padded and encrypted with the public key; followed by the rest of
* the source data encrypted in AES-CTR mode with the symmetric key.
+ *
+ * NOTE that this format does not authenticate the symmetrically encrypted
+ * part of the data, and SHOULD NOT BE USED for new protocols.
*/
int
-crypto_pk_public_hybrid_encrypt(crypto_pk_t *env,
+crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env,
char *to, size_t tolen,
const char *from,
size_t fromlen,
@@ -1302,10 +1305,14 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_t *env,
return -1;
}
-/** Invert crypto_pk_public_hybrid_encrypt. Returns the number of bytes
- * written on success, -1 on failure. */
+/** Invert crypto_pk_obsolete_public_hybrid_encrypt. Returns the number of
+ * bytes written on success, -1 on failure.
+ *
+ * NOTE that this format does not authenticate the symmetrically encrypted
+ * part of the data, and SHOULD NOT BE USED for new protocols.
+ */
int
-crypto_pk_private_hybrid_decrypt(crypto_pk_t *env,
+crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env,
char *to,
size_t tolen,
const char *from,
@@ -1854,6 +1861,18 @@ struct crypto_digest_t {
} d;
};
+#ifdef TOR_UNIT_TESTS
+
+digest_algorithm_t
+crypto_digest_get_algorithm(crypto_digest_t *digest)
+{
+ tor_assert(digest);
+
+ return digest->algorithm;
+}
+
+#endif
+
/**
* Return the number of bytes we need to malloc in order to get a
* crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe
@@ -1865,7 +1884,7 @@ crypto_digest_alloc_bytes(digest_algorithm_t alg)
/* Helper: returns the number of bytes in the 'f' field of 'st' */
#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f ))
/* Gives the length of crypto_digest_t through the end of the field 'd' */
-#define END_OF_FIELD(f) (STRUCT_OFFSET(crypto_digest_t, f) + \
+#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \
STRUCT_FIELD_SIZE(crypto_digest_t, f))
switch (alg) {
case DIGEST_SHA1:
diff --git a/src/common/crypto.h b/src/common/crypto.h
index c70d91c262..cde241dd37 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -20,6 +20,9 @@
#include "testsupport.h"
#include "compat.h"
+#include <openssl/engine.h>
+#include "keccak-tiny/keccak-tiny.h"
+
/*
Macro to create an arbitrary OpenSSL version number as used by
OPENSSL_VERSION_NUMBER or SSLeay(), since the actual numbers are a bit hard
@@ -194,11 +197,11 @@ int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen);
int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen);
-int crypto_pk_public_hybrid_encrypt(crypto_pk_t *env, char *to,
+int crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to,
size_t tolen,
const char *from, size_t fromlen,
int padding, int force);
-int crypto_pk_private_hybrid_decrypt(crypto_pk_t *env, char *to,
+int crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, char *to,
size_t tolen,
const char *from, size_t fromlen,
int padding, int warnOnFailure);
@@ -335,6 +338,7 @@ struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh);
void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in);
#ifdef CRYPTO_PRIVATE
+
STATIC int crypto_force_rand_ssleay(void);
STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len);
@@ -346,6 +350,7 @@ extern int break_strongest_rng_fallback;
#ifdef TOR_UNIT_TESTS
void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src);
+digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest);
#endif
#endif
diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c
index 188e18c710..abf23c4b38 100644
--- a/src/common/crypto_ed25519.c
+++ b/src/common/crypto_ed25519.c
@@ -28,6 +28,7 @@
#include "crypto_format.h"
#include "torlog.h"
#include "util.h"
+#include "util_format.h"
#include "ed25519/ref10/ed25519_ref10.h"
#include "ed25519/donna/ed25519_donna_tor.h"
@@ -57,6 +58,9 @@ typedef struct {
int (*pubkey_from_curve25519_pubkey)(unsigned char *, const unsigned char *,
int);
+
+ int (*ed25519_scalarmult_with_group_order)(unsigned char *,
+ const unsigned char *);
} ed25519_impl_t;
/** The Ref10 Ed25519 implementation. This one is pure C and lightly
@@ -77,6 +81,7 @@ static const ed25519_impl_t impl_ref10 = {
ed25519_ref10_blind_public_key,
ed25519_ref10_pubkey_from_curve25519_pubkey,
+ ed25519_ref10_scalarmult_with_group_order,
};
/** The Ref10 Ed25519 implementation. This one is heavily optimized, but still
@@ -97,6 +102,7 @@ static const ed25519_impl_t impl_donna = {
ed25519_donna_blind_public_key,
ed25519_donna_pubkey_from_curve25519_pubkey,
+ ed25519_donna_scalarmult_with_group_order,
};
/** Which Ed25519 implementation are we using? NULL if we haven't decided
@@ -462,7 +468,6 @@ ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out,
tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32));
memwipe(&pubkey_check, 0, sizeof(pubkey_check));
- memwipe(&ctx, 0, sizeof(ctx));
memwipe(sha512_output, 0, sizeof(sha512_output));
return 0;
@@ -491,7 +496,8 @@ ed25519_public_key_from_curve25519_public_key(ed25519_public_key_t *pubkey,
* service descriptors are encrypted with a key derived from the service's
* long-term public key, and then signed with (and stored at a position
* indexed by) a short-term key derived by blinding the long-term keys.
- */
+ *
+ * Return 0 if blinding was successful, else return -1. */
int
ed25519_keypair_blind(ed25519_keypair_t *out,
const ed25519_keypair_t *inp,
@@ -502,7 +508,9 @@ ed25519_keypair_blind(ed25519_keypair_t *out,
get_ed_impl()->blind_secret_key(out->seckey.seckey,
inp->seckey.seckey, param);
- ed25519_public_blind(&pubkey_check, &inp->pubkey, param);
+ if (ed25519_public_blind(&pubkey_check, &inp->pubkey, param) < 0) {
+ return -1;
+ }
ed25519_public_key_generate(&out->pubkey, &out->seckey);
tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32));
@@ -522,8 +530,7 @@ ed25519_public_blind(ed25519_public_key_t *out,
const ed25519_public_key_t *inp,
const uint8_t *param)
{
- get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param);
- return 0;
+ return get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param);
}
/**
@@ -754,3 +761,47 @@ ed25519_init(void)
pick_ed25519_impl();
}
+/* Return true if <b>point</b> is the identity element of the ed25519 group. */
+static int
+ed25519_point_is_identity_element(const uint8_t *point)
+{
+ /* The identity element in ed25159 is the point with coordinates (0,1). */
+ static const uint8_t ed25519_identity[32] = {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ tor_assert(sizeof(ed25519_identity) == ED25519_PUBKEY_LEN);
+ return tor_memeq(point, ed25519_identity, sizeof(ed25519_identity));
+}
+
+/** Validate <b>pubkey</b> to ensure that it has no torsion component.
+ * Return 0 if <b>pubkey</b> is valid, else return -1. */
+int
+ed25519_validate_pubkey(const ed25519_public_key_t *pubkey)
+{
+ uint8_t result[32] = {9};
+
+ /* First check that we were not given the identity element */
+ if (ed25519_point_is_identity_element(pubkey->pubkey)) {
+ log_warn(LD_CRYPTO, "ed25519 pubkey is the identity");
+ return -1;
+ }
+
+ /* For any point on the curve, doing l*point should give the identity element
+ * (where l is the group order). Do the computation and check that the
+ * identity element is returned. */
+ if (get_ed_impl()->ed25519_scalarmult_with_group_order(result,
+ pubkey->pubkey) < 0) {
+ log_warn(LD_CRYPTO, "ed25519 group order scalarmult failed");
+ return -1;
+ }
+
+ if (!ed25519_point_is_identity_element(result)) {
+ log_warn(LD_CRYPTO, "ed25519 validation failed");
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h
index 77a3313adc..3a439207b3 100644
--- a/src/common/crypto_ed25519.h
+++ b/src/common/crypto_ed25519.h
@@ -127,6 +127,8 @@ void ed25519_pubkey_copy(ed25519_public_key_t *dest,
void ed25519_set_impl_params(int use_donna);
void ed25519_init(void);
+int ed25519_validate_pubkey(const ed25519_public_key_t *pubkey);
+
#ifdef TOR_UNIT_TESTS
void crypto_ed25519_testing_force_impl(const char *name);
void crypto_ed25519_testing_restore_impl(void);
diff --git a/src/common/memarea.c b/src/common/memarea.c
index 659d1edf54..4e2a5e5fc5 100644
--- a/src/common/memarea.c
+++ b/src/common/memarea.c
@@ -7,6 +7,7 @@
*/
#include "orconfig.h"
+#include <stddef.h>
#include <stdlib.h>
#include "memarea.h"
#include "util.h"
@@ -101,7 +102,7 @@ typedef struct memarea_chunk_t {
/** How many bytes are needed for overhead before we get to the memory part
* of a chunk? */
-#define CHUNK_HEADER_SIZE STRUCT_OFFSET(memarea_chunk_t, U_MEM)
+#define CHUNK_HEADER_SIZE offsetof(memarea_chunk_t, U_MEM)
/** What's the smallest that we'll allocate a chunk? */
#define CHUNK_SIZE 4096
diff --git a/src/common/sandbox.c b/src/common/sandbox.c
index 5063717355..c06f9694b1 100644
--- a/src/common/sandbox.c
+++ b/src/common/sandbox.c
@@ -480,7 +480,7 @@ sb_chmod(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chmod),
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
if (rc != 0) {
- log_err(LD_BUG,"(Sandbox) failed to add open syscall, received "
+ log_err(LD_BUG,"(Sandbox) failed to add chmod syscall, received "
"libseccomp error %d", rc);
return rc;
}
@@ -505,7 +505,7 @@ sb_chown(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chown),
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
if (rc != 0) {
- log_err(LD_BUG,"(Sandbox) failed to add open syscall, received "
+ log_err(LD_BUG,"(Sandbox) failed to add chown syscall, received "
"libseccomp error %d", rc);
return rc;
}
@@ -1045,8 +1045,8 @@ sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter)
rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64),
SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value));
if (rc != 0) {
- log_err(LD_BUG,"(Sandbox) failed to add open syscall, received "
- "libseccomp error %d", rc);
+ log_err(LD_BUG,"(Sandbox) failed to add stat64 syscall, received "
+ "libseccomp error %d", rc);
return rc;
}
}
diff --git a/src/common/tortls.c b/src/common/tortls.c
index 44db3aec58..dfc85ee318 100644
--- a/src/common/tortls.c
+++ b/src/common/tortls.c
@@ -1174,17 +1174,20 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
SSL_CTX_set_options(result->ctx,
SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
}
+
+ /* Don't actually allow compression; it uses RAM and time, it makes TLS
+ * vulnerable to CRIME-style attacks, and most of the data we transmit over
+ * TLS is encrypted (and therefore uncompressible) anyway. */
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(result->ctx, SSL_OP_NO_COMPRESSION);
#endif
#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0)
#ifndef OPENSSL_NO_COMP
- /* Don't actually allow compression; it uses ram and time, but the data
- * we transmit is all encrypted anyway. */
if (result->ctx->comp_methods)
result->ctx->comp_methods = NULL;
#endif
#endif
+
#ifdef SSL_MODE_RELEASE_BUFFERS
SSL_CTX_set_mode(result->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif
diff --git a/src/common/util.c b/src/common/util.c
index 5b47028097..0858d17fe6 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -1172,7 +1172,7 @@ tor_parse_long(const char *s, int base, long min, long max,
char *endptr;
long r;
- if (base < 0) {
+ if (BUG(base < 0)) {
if (ok)
*ok = 0;
return 0;
@@ -1191,7 +1191,7 @@ tor_parse_ulong(const char *s, int base, unsigned long min,
char *endptr;
unsigned long r;
- if (base < 0) {
+ if (BUG(base < 0)) {
if (ok)
*ok = 0;
return 0;
@@ -1223,7 +1223,7 @@ tor_parse_uint64(const char *s, int base, uint64_t min,
char *endptr;
uint64_t r;
- if (base < 0) {
+ if (BUG(base < 0)) {
if (ok)
*ok = 0;
return 0;
diff --git a/src/common/util.h b/src/common/util.h
index d56abcee2e..df581d2405 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -109,19 +109,11 @@ extern int dmalloc_free(const char *file, const int line, void *pnt,
void tor_log_mallinfo(int severity);
-/** Return the offset of <b>member</b> within the type <b>tp</b>, in bytes */
-#if defined(__GNUC__) && __GNUC__ > 3
-#define STRUCT_OFFSET(tp, member) __builtin_offsetof(tp, member)
-#else
- #define STRUCT_OFFSET(tp, member) \
- ((off_t) (((char*)&((tp*)0)->member)-(char*)0))
-#endif
-
/** Macro: yield a pointer to the field at position <b>off</b> within the
* structure <b>st</b>. Example:
* <pre>
* struct a { int foo; int bar; } x;
- * off_t bar_offset = STRUCT_OFFSET(struct a, bar);
+ * off_t bar_offset = offsetof(struct a, bar);
* int *bar_p = STRUCT_VAR_P(&x, bar_offset);
* *bar_p = 3;
* </pre>
@@ -138,7 +130,7 @@ void tor_log_mallinfo(int severity);
* </pre>
*/
#define SUBTYPE_P(p, subtype, basemember) \
- ((void*) ( ((char*)(p)) - STRUCT_OFFSET(subtype, basemember) ))
+ ((void*) ( ((char*)(p)) - offsetof(subtype, basemember) ))
/* Logic */
/** Macro: true if two values have the same boolean value. */
diff --git a/src/common/util_bug.c b/src/common/util_bug.c
index 3d990e3700..cc1ac2ff84 100644
--- a/src/common/util_bug.c
+++ b/src/common/util_bug.c
@@ -13,6 +13,10 @@
#include "backtrace.h"
#include "container.h"
+#ifdef __COVERITY__
+int bug_macro_deadcode_dummy__ = 0;
+#endif
+
#ifdef TOR_UNIT_TESTS
static void (*failed_assertion_cb)(void) = NULL;
static int n_bugs_to_capture = 0;
diff --git a/src/common/util_bug.h b/src/common/util_bug.h
index ae7e7a37fd..de39317d1c 100644
--- a/src/common/util_bug.h
+++ b/src/common/util_bug.h
@@ -59,10 +59,13 @@
*/
#ifdef __COVERITY__
+extern int bug_macro_deadcode_dummy__;
#undef BUG
// Coverity defines this in global headers; let's override it. This is a
// magic coverity-only preprocessor thing.
-#nodef BUG(x) ((x)?(__coverity_panic__(),1):0)
+// We use this "deadcode_dummy__" trick to prevent coverity from
+// complaining about unreachable bug cases.
+#nodef BUG(x) ((x)?(__coverity_panic__(),1):(0+bug_macro_deadcode_dummy__))
#endif
#if defined(__COVERITY__) || defined(__clang_analyzer__)
diff --git a/src/ext/ed25519/donna/curve25519-donna-sse2.h b/src/ext/ed25519/donna/curve25519-donna-sse2.h
index 1dbfd44d8b..1123cb1e91 100644
--- a/src/ext/ed25519/donna/curve25519-donna-sse2.h
+++ b/src/ext/ed25519/donna/curve25519-donna-sse2.h
@@ -29,26 +29,19 @@ typedef packedelem32 packed32bignum25519[5];
typedef packedelem64 packed64bignum25519[10];
static const packedelem32 bot32bitmask = {{0xffffffff, 0x00000000, 0xffffffff, 0x00000000}};
-static const packedelem32 top32bitmask = {{0x00000000, 0xffffffff, 0x00000000, 0xffffffff}};
static const packedelem32 top64bitmask = {{0x00000000, 0x00000000, 0xffffffff, 0xffffffff}};
static const packedelem32 bot64bitmask = {{0xffffffff, 0xffffffff, 0x00000000, 0x00000000}};
/* reduction masks */
static const packedelem64 packedmask26 = {{0x03ffffff, 0x03ffffff}};
static const packedelem64 packedmask25 = {{0x01ffffff, 0x01ffffff}};
-static const packedelem32 packedmask2625 = {{0x3ffffff,0,0x1ffffff,0}};
static const packedelem32 packedmask26262626 = {{0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff}};
static const packedelem32 packedmask25252525 = {{0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff}};
/* multipliers */
static const packedelem64 packednineteen = {{19, 19}};
-static const packedelem64 packednineteenone = {{19, 1}};
static const packedelem64 packedthirtyeight = {{38, 38}};
static const packedelem64 packed3819 = {{19*2,19}};
-static const packedelem64 packed9638 = {{19*4,19*2}};
-
-/* 121666,121665 */
-static const packedelem64 packed121666121665 = {{121666, 121665}};
/* 2*(2^255 - 19) = 0 mod p */
static const packedelem32 packed2p0 = {{0x7ffffda,0x3fffffe,0x7fffffe,0x3fffffe}};
diff --git a/src/ext/ed25519/donna/ed25519_donna_tor.h b/src/ext/ed25519/donna/ed25519_donna_tor.h
index d225407b1c..7d7b8c0625 100644
--- a/src/ext/ed25519/donna/ed25519_donna_tor.h
+++ b/src/ext/ed25519/donna/ed25519_donna_tor.h
@@ -30,4 +30,9 @@ int ed25519_donna_blind_public_key(unsigned char *out, const unsigned char *inp,
int ed25519_donna_pubkey_from_curve25519_pubkey(unsigned char *out,
const unsigned char *inp, int signbit);
+
+int
+ed25519_donna_scalarmult_with_group_order(unsigned char *out,
+ const unsigned char *pubkey);
+
#endif
diff --git a/src/ext/ed25519/donna/ed25519_tor.c b/src/ext/ed25519/donna/ed25519_tor.c
index 9537ae66a1..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;
@@ -304,7 +298,9 @@ ed25519_donna_blind_public_key(unsigned char *out, const unsigned char *inp,
/* No "ge25519_unpack", negate the public key. */
memcpy(pkcopy, inp, 32);
pkcopy[31] ^= (1<<7);
- ge25519_unpack_negative_vartime(&A, pkcopy);
+ if (!ge25519_unpack_negative_vartime(&A, pkcopy)) {
+ return -1;
+ }
/* A' = [tweak] * A + [0] * basepoint. */
ge25519_double_scalarmult_vartime(&Aprime, &A, t, zero);
@@ -340,5 +336,32 @@ ed25519_donna_pubkey_from_curve25519_pubkey(unsigned char *out,
return 0;
}
+/* Do the scalar multiplication of <b>pubkey</b> with the group order
+ * <b>modm_m</b>. Place the result in <b>out</b> which must be at least 32
+ * bytes long. */
+int
+ed25519_donna_scalarmult_with_group_order(unsigned char *out,
+ const unsigned char *pubkey)
+{
+ static const bignum256modm ALIGN(16) zero = { 0 };
+ unsigned char pkcopy[32];
+ ge25519 ALIGN(16) Point, Result;
+
+ /* No "ge25519_unpack", negate the public key and unpack it back.
+ * See ed25519_donna_blind_public_key() */
+ memcpy(pkcopy, pubkey, 32);
+ pkcopy[31] ^= (1<<7);
+ if (!ge25519_unpack_negative_vartime(&Point, pkcopy)) {
+ return -1; /* error: bail out */
+ }
+
+ /* There is no regular scalarmult function so we have to do:
+ * Result = l*P + 0*B */
+ ge25519_double_scalarmult_vartime(&Result, &Point, modm_m, zero);
+ ge25519_pack(out, &Result);
+
+ return 0;
+}
+
#include "test-internals.c"
diff --git a/src/ext/ed25519/ref10/blinding.c b/src/ext/ed25519/ref10/blinding.c
index ee3e8666fa..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;
@@ -49,6 +49,7 @@ int ed25519_ref10_blind_public_key(unsigned char *out,
unsigned char pkcopy[32];
ge_p3 A;
ge_p2 Aprime;
+ int retval = -1;
ed25519_ref10_gettweak(tweak, param);
@@ -62,15 +63,57 @@ int ed25519_ref10_blind_public_key(unsigned char *out,
* "ge_frombytes", we'd use that, but there isn't. */
memcpy(pkcopy, inp, 32);
pkcopy[31] ^= (1<<7);
- ge_frombytes_negate_vartime(&A, pkcopy);
+ if (ge_frombytes_negate_vartime(&A, pkcopy) != 0) {
+ goto done;
+ }
/* There isn't a regular ge_scalarmult -- we have to do tweak*A + zero*B. */
ge_double_scalarmult_vartime(&Aprime, tweak, &A, zero);
ge_tobytes(out, &Aprime);
+ retval = 0;
+
+ done:
memwipe(tweak, 0, sizeof(tweak));
memwipe(&A, 0, sizeof(A));
memwipe(&Aprime, 0, sizeof(Aprime));
memwipe(pkcopy, 0, sizeof(pkcopy));
+ return retval;
+}
+
+/* This is the group order encoded in a format that
+ * ge_double_scalarmult_vartime() understands. The group order m is:
+ * m = 2^252 + 27742317777372353535851937790883648493 =
+ * 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
+ */
+static const uint8_t modm_m[32] = {0xed,0xd3,0xf5,0x5c,0x1a,0x63,0x12,0x58,
+ 0xd6,0x9c,0xf7,0xa2,0xde,0xf9,0xde,0x14,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10};
+
+/* Do the scalar multiplication of <b>pubkey</b> with the group order
+ * <b>modm_m</b>. Place the result in <b>out</b> which must be at least 32
+ * bytes long. */
+int
+ed25519_ref10_scalarmult_with_group_order(unsigned char *out,
+ const unsigned char *pubkey)
+{
+ unsigned char pkcopy[32];
+ unsigned char zero[32] = {0};
+ ge_p3 Point;
+ ge_p2 Result;
+
+ /* All this is done to fit 'pubkey' in 'Point' so that it can be used by
+ * ed25519 ref code. Same thing as in blinding function */
+ memcpy(pkcopy, pubkey, 32);
+ pkcopy[31] ^= (1<<7);
+ if (ge_frombytes_negate_vartime(&Point, pkcopy) != 0) {
+ return -1; /* error: bail out */
+ }
+
+ /* There isn't a regular scalarmult -- we have to do r = l*P + 0*B */
+ ge_double_scalarmult_vartime(&Result, modm_m, &Point, zero);
+ ge_tobytes(out, &Result);
+
return 0;
}
diff --git a/src/ext/ed25519/ref10/ed25519_ref10.h b/src/ext/ed25519/ref10/ed25519_ref10.h
index af7e21a2ad..5965694977 100644
--- a/src/ext/ed25519/ref10/ed25519_ref10.h
+++ b/src/ext/ed25519/ref10/ed25519_ref10.h
@@ -27,4 +27,8 @@ int ed25519_ref10_blind_public_key(unsigned char *out,
const unsigned char *inp,
const unsigned char *param);
+int
+ed25519_ref10_scalarmult_with_group_order(unsigned char *out,
+ const unsigned char *pubkey);
+
#endif
diff --git a/src/or/buffers.c b/src/or/buffers.c
index 12a6c0239b..bd84103c37 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -80,7 +80,7 @@ static int parse_socks_client(const uint8_t *data, size_t datalen,
/* Chunk manipulation functions */
-#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0])
+#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0])
/* We leave this many NUL bytes at the end of the buffer. */
#ifdef DISABLE_MEMORY_SENTINELS
@@ -1478,6 +1478,32 @@ socks_request_set_socks5_error(socks_request_t *req,
req->reply[3] = 0x01; // ATYP field.
}
+static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor is not an HTTP Proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor is not an HTTP Proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor as "
+ "an HTTP proxy.\n\n"
+ "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Implementation helper to implement fetch_from_*_socks. Instead of looking
* at a buffer's contents, we look at the <b>datalen</b> bytes of data in
* <b>data</b>. Instead of removing data from the buffer, we set
@@ -1684,15 +1710,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
req->port = ntohs(get_uint16(data+5+len));
*drain_out = 5+len+2;
- if (string_is_valid_ipv4_address(req->address) ||
- string_is_valid_ipv6_address(req->address)) {
- log_unsafe_socks_warning(5,req->address,req->port,safe_socks);
-
- if (safe_socks) {
- socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED);
- return -1;
- }
- } else if (!string_is_valid_hostname(req->address)) {
+ if (!string_is_valid_hostname(req->address)) {
socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR);
log_warn(LD_PROTOCOL,
@@ -1814,7 +1832,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
log_debug(LD_APP,"socks4: Everything is here. Success.");
strlcpy(req->address, startaddr ? startaddr : tmpbuf,
sizeof(req->address));
- if (!tor_strisprint(req->address) || strchr(req->address,'\"')) {
+ if (!string_is_valid_hostname(req->address)) {
log_warn(LD_PROTOCOL,
"Your application (using socks4 to port %d) gave Tor "
"a malformed hostname: %s. Rejecting the connection.",
@@ -1834,32 +1852,8 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req,
case 'H': /* head */
case 'P': /* put/post */
case 'C': /* connect */
- strlcpy((char*)req->reply,
-"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n"
-"Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
-"<html>\n"
-"<head>\n"
-"<title>Tor is not an HTTP Proxy</title>\n"
-"</head>\n"
-"<body>\n"
-"<h1>Tor is not an HTTP Proxy</h1>\n"
-"<p>\n"
-"It appears you have configured your web browser to use Tor as an HTTP proxy."
-"\n"
-"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n"
-"Please configure your client accordingly.\n"
-"</p>\n"
-"<p>\n"
-"See <a href=\"https://www.torproject.org/documentation.html\">"
- "https://www.torproject.org/documentation.html</a> for more "
- "information.\n"
-"<!-- Plus this comment, to make the body response more than 512 bytes, so "
-" IE will be willing to display it. Comment comment comment comment "
-" comment comment comment comment comment comment comment comment.-->\n"
-"</p>\n"
-"</body>\n"
-"</html>\n"
- , MAX_SOCKS_REPLY_LEN);
+ strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG,
+ MAX_SOCKS_REPLY_LEN);
req->replylen = strlen((char*)req->reply)+1;
/* fall through */
default: /* version is not socks4 or socks5 */
@@ -2022,6 +2016,34 @@ parse_socks_client(const uint8_t *data, size_t datalen,
return -1;
}
+/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */
+int
+peek_buf_has_http_command(const buf_t *buf)
+{
+ if (peek_buf_startswith(buf, "CONNECT ") ||
+ peek_buf_startswith(buf, "DELETE ") ||
+ peek_buf_startswith(buf, "GET ") ||
+ peek_buf_startswith(buf, "POST ") ||
+ peek_buf_startswith(buf, "PUT " ))
+ return 1;
+ return 0;
+}
+
+/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null
+ * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */
+int
+peek_buf_startswith(const buf_t *buf, const char *cmd)
+{
+ char tmp[PEEK_BUF_STARTSWITH_MAX];
+ size_t clen = strlen(cmd);
+ if (BUG(clen > sizeof(tmp)))
+ return 0;
+ if (buf->datalen < clen)
+ return 0;
+ peek_from_buf(tmp, clen, buf);
+ return fast_memeq(tmp, cmd, clen);
+}
+
/** Return 1 iff buf looks more like it has an (obsolete) v0 controller
* command on it than any valid v1 controller command. */
int
diff --git a/src/or/buffers.h b/src/or/buffers.h
index 23b58a571a..d884084385 100644
--- a/src/or/buffers.h
+++ b/src/or/buffers.h
@@ -53,6 +53,9 @@ int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason);
int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len);
int peek_buf_has_control0_command(buf_t *buf);
+#define PEEK_BUF_STARTSWITH_MAX 16
+int peek_buf_startswith(const buf_t *buf, const char *cmd);
+int peek_buf_has_http_command(const buf_t *buf);
int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out);
diff --git a/src/or/channel.c b/src/or/channel.c
index df6d7d3423..9f8a03683f 100644
--- a/src/or/channel.c
+++ b/src/or/channel.c
@@ -2086,8 +2086,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell)
* are appropriate to the state transition in question.
*/
-void
-channel_change_state(channel_t *chan, channel_state_t to_state)
+static void
+channel_change_state_(channel_t *chan, channel_state_t to_state)
{
channel_state_t from_state;
unsigned char was_active, is_active;
@@ -2206,18 +2206,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
estimated_total_queue_size += chan->bytes_in_queue;
}
- /* Tell circuits if we opened and stuff */
- if (to_state == CHANNEL_STATE_OPEN) {
- channel_do_open_actions(chan);
- chan->has_been_open = 1;
-
- /* Check for queued cells to process */
- if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
- channel_process_cells(chan);
- if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
- channel_flush_cells(chan);
- } else if (to_state == CHANNEL_STATE_CLOSED ||
- to_state == CHANNEL_STATE_ERROR) {
+ if (to_state == CHANNEL_STATE_CLOSED ||
+ to_state == CHANNEL_STATE_ERROR) {
/* Assert that all queues are empty */
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue));
tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue));
@@ -2225,6 +2215,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state)
}
/**
+ * As channel_change_state_, but change the state to any state but open.
+ */
+void
+channel_change_state(channel_t *chan, channel_state_t to_state)
+{
+ tor_assert(to_state != CHANNEL_STATE_OPEN);
+ channel_change_state_(chan, to_state);
+}
+
+/**
+ * As channel_change_state, but change the state to open.
+ */
+void
+channel_change_state_open(channel_t *chan)
+{
+ channel_change_state_(chan, CHANNEL_STATE_OPEN);
+
+ /* Tell circuits if we opened and stuff */
+ channel_do_open_actions(chan);
+ chan->has_been_open = 1;
+
+ /* Check for queued cells to process */
+ if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue))
+ channel_process_cells(chan);
+ if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue))
+ channel_flush_cells(chan);
+}
+
+/**
* Change channel listener state
*
* This internal and subclass use only function is used to change channel
diff --git a/src/or/channel.h b/src/or/channel.h
index ea280f2fd2..2d0ec39924 100644
--- a/src/or/channel.h
+++ b/src/or/channel.h
@@ -522,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l);
/* State/metadata setters */
void channel_change_state(channel_t *chan, channel_state_t to_state);
+void channel_change_state_open(channel_t *chan);
void channel_clear_identity_digest(channel_t *chan);
void channel_clear_remote_end(channel_t *chan);
void channel_mark_local(channel_t *chan);
diff --git a/src/or/channeltls.c b/src/or/channeltls.c
index f44e4fc8ea..6547451181 100644
--- a/src/or/channeltls.c
+++ b/src/or/channeltls.c
@@ -993,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan,
* We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or
* CHANNEL_STATE_MAINT on this.
*/
- channel_change_state(base_chan, CHANNEL_STATE_OPEN);
+ channel_change_state_open(base_chan);
/* We might have just become writeable; check and tell the scheduler */
if (connection_or_num_cells_writeable(conn) > 0) {
scheduler_channel_wants_writes(base_chan);
@@ -1915,7 +1915,6 @@ certs_cell_typenum_to_cert_type(int typenum)
* of the connection, we then authenticate the server or mark the connection.
* If it's the server side, wait for an AUTHENTICATE cell.
*/
-
STATIC void
channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
{
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 16cef0e56b..257edab50b 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -46,6 +46,7 @@
#include "crypto.h"
#include "directory.h"
#include "entrynodes.h"
+#include "hs_ntor.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -74,6 +75,10 @@ static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit);
static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath);
static int onion_extend_cpath(origin_circuit_t *circ);
static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice);
+static int circuit_send_first_onion_skin(origin_circuit_t *circ);
+static int circuit_build_no_more_hops(origin_circuit_t *circ);
+static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop);
/** This function tries to get a channel to the specified endpoint,
* and then calls command_setup_channel() to give it the right
@@ -912,234 +917,275 @@ circuit_purpose_may_omit_guard(int purpose)
* If circ's first hop is closed, then we need to build a create
* cell and send it forward.
*
- * Otherwise, we need to build a relay extend cell and send it
- * forward.
+ * Otherwise, if circ's cpath still has any non-open hops, we need to
+ * build a relay extend cell and send it forward to the next non-open hop.
+ *
+ * If all hops on the cpath are open, we're done building the circuit
+ * and we should do housekeeping for the newly opened circuit.
*
* Return -reason if we want to tear down circ, else return 0.
*/
int
circuit_send_next_onion_skin(origin_circuit_t *circ)
{
- crypt_path_t *hop;
- const node_t *node;
-
tor_assert(circ);
if (circ->cpath->state == CPATH_STATE_CLOSED) {
- /* This is the first hop. */
- create_cell_t cc;
- int fast;
- int len;
- log_debug(LD_CIRC,"First skin; sending create cell.");
- memset(&cc, 0, sizeof(cc));
- if (circ->build_state->onehop_tunnel)
- control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
- else {
- control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
-
- /* If this is not a one-hop tunnel, the channel is being used
- * for traffic that wants anonymity and protection from traffic
- * analysis (such as netflow record retention). That means we want
- * to pad it.
- */
- if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS)
- circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
- }
+ /* Case one: we're on the first hop. */
+ return circuit_send_first_onion_skin(circ);
+ }
- node = node_get_by_id(circ->base_.n_chan->identity_digest);
- fast = should_use_create_fast_for_circuit(circ);
- if (!fast) {
- /* We are an OR and we know the right onion key: we should
- * send a create cell.
- */
- circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
- circ->cpath->extend_info);
- } else {
- /* We are not an OR, and we're building the first hop of a circuit to a
- * new OR: we can be speedy and use CREATE_FAST to save an RSA operation
- * and a DH operation. */
- cc.cell_type = CELL_CREATE_FAST;
- cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST;
- }
+ tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
+ tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
+ crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath);
- len = onion_skin_create(cc.handshake_type,
- circ->cpath->extend_info,
- &circ->cpath->handshake_state,
- cc.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
- return - END_CIRC_REASON_INTERNAL;
- }
- cc.handshake_len = len;
+ if (hop) {
+ /* Case two: we're on a hop after the first. */
+ return circuit_send_intermediate_onion_skin(circ, hop);
+ }
- if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
- return - END_CIRC_REASON_RESOURCELIMIT;
+ /* Case three: the circuit is finished. Do housekeeping tasks on it. */
+ return circuit_build_no_more_hops(circ);
+}
- circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
- log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
- fast ? "CREATE_FAST" : "CREATE",
- node ? node_describe(node) : "<unnamed>");
+/**
+ * Called from circuit_send_next_onion_skin() when we find ourselves connected
+ * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST
+ * cell to that hop. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_send_first_onion_skin(origin_circuit_t *circ)
+{
+ int fast;
+ int len;
+ const node_t *node;
+ create_cell_t cc;
+ memset(&cc, 0, sizeof(cc));
+
+ log_debug(LD_CIRC,"First skin; sending create cell.");
+
+ if (circ->build_state->onehop_tunnel) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0);
} else {
- extend_cell_t ec;
- int len;
- tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
- tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING);
- log_debug(LD_CIRC,"starting to send subsequent skin.");
- hop = onion_next_hop_in_cpath(circ->cpath);
- memset(&ec, 0, sizeof(ec));
- if (!hop) {
- /* done building the circuit. whew. */
- guard_usable_t r;
- if (! circ->guard_state) {
- if (circuit_get_cpath_len(circ) != 1 &&
- ! circuit_purpose_may_omit_guard(circ->base_.purpose) &&
- get_options()->UseEntryGuards) {
- log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no "
- "guard state",
- circuit_get_cpath_len(circ), circ, circ->base_.purpose);
- }
- r = GUARD_USABLE_NOW;
- } else {
- r = entry_guard_succeeded(&circ->guard_state);
- }
- const int is_usable_for_streams = (r == GUARD_USABLE_NOW);
- if (r == GUARD_USABLE_NOW) {
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
- } else if (r == GUARD_MAYBE_USABLE_LATER) {
- // Wait till either a better guard succeeds, or till
- // all better guards fail.
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT);
- } else {
- tor_assert_nonfatal(r == GUARD_USABLE_NEVER);
- return - END_CIRC_REASON_INTERNAL;
- }
+ control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0);
- /* XXXX #21422 -- the rest of this branch needs careful thought!
- * Some of the things here need to happen when a circuit becomes
- * mechanically open; some need to happen when it is actually usable.
- * I think I got them right, but more checking would be wise. -NM
- */
+ /* If this is not a one-hop tunnel, the channel is being used
+ * for traffic that wants anonymity and protection from traffic
+ * analysis (such as netflow record retention). That means we want
+ * to pad it.
+ */
+ if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS)
+ circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS;
+ }
- if (circuit_timeout_want_to_count_circ(circ)) {
- struct timeval end;
- long timediff;
- tor_gettimeofday(&end);
- timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
+ node = node_get_by_id(circ->base_.n_chan->identity_digest);
+ fast = should_use_create_fast_for_circuit(circ);
+ if (!fast) {
+ /* We know the right onion key: we should send a create cell. */
+ circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type,
+ circ->cpath->extend_info);
+ } else {
+ /* We don't know an onion key, so we need to fall back to CREATE_FAST. */
+ cc.cell_type = CELL_CREATE_FAST;
+ cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST;
+ }
- /*
- * If the circuit build time is much greater than we would have cut
- * it off at, we probably had a suspend event along this codepath,
- * and we should discard the value.
- */
- if (timediff < 0 ||
- timediff > 2*get_circuit_build_close_time_ms()+1000) {
- log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
- "Assuming clock jump. Purpose %d (%s)", timediff,
- circ->base_.purpose,
- circuit_purpose_to_string(circ->base_.purpose));
- } else if (!circuit_build_times_disabled(get_options())) {
- /* Only count circuit times if the network is live */
- if (circuit_build_times_network_check_live(
- get_circuit_build_times())) {
- circuit_build_times_add_time(get_circuit_build_times_mutable(),
- (build_time_t)timediff);
- circuit_build_times_set_timeout(get_circuit_build_times_mutable());
- }
-
- if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_build_times_network_circ_success(
- get_circuit_build_times_mutable());
- }
- }
- }
- log_info(LD_CIRC,"circuit built!");
- circuit_reset_failure_count(0);
+ len = onion_skin_create(cc.handshake_type,
+ circ->cpath->extend_info,
+ &circ->cpath->handshake_state,
+ cc.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create (first hop) failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ cc.handshake_len = len;
- if (circ->build_state->onehop_tunnel || circ->has_opened) {
- control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
- }
+ if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0)
+ return - END_CIRC_REASON_RESOURCELIMIT;
- pathbias_count_build_success(circ);
- circuit_rep_hist_note_result(circ);
- if (is_usable_for_streams)
- circuit_has_opened(circ); /* do other actions as necessary */
-
- if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
- const or_options_t *options = get_options();
- note_that_we_completed_a_circuit();
- /* FFFF Log a count of known routers here */
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
- log_notice(LD_GENERAL,
- "Tor has successfully opened a circuit. "
- "Looks like client functionality is working.");
- }
- control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
- clear_broken_connection_map(1);
- if (server_mode(options) && !check_whether_orport_reachable(options)) {
- inform_testing_reachability();
- consider_testing_reachability(1, 1);
- }
+ circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
+ log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'",
+ fast ? "CREATE_FAST" : "CREATE",
+ node ? node_describe(node) : "<unnamed>");
+ return 0;
+}
+
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have no
+ * more hops: mark the circuit as finished, and perform the necessary
+ * bookkeeping. Return 0 on success; -reason on failure (if the circuit
+ * should be torn down).
+ */
+static int
+circuit_build_no_more_hops(origin_circuit_t *circ)
+{
+ guard_usable_t r;
+ if (! circ->guard_state) {
+ if (circuit_get_cpath_len(circ) != 1 &&
+ ! circuit_purpose_may_omit_guard(circ->base_.purpose) &&
+ get_options()->UseEntryGuards) {
+ log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no "
+ "guard state",
+ circuit_get_cpath_len(circ), circ, circ->base_.purpose);
+ }
+ r = GUARD_USABLE_NOW;
+ } else {
+ r = entry_guard_succeeded(&circ->guard_state);
+ }
+ const int is_usable_for_streams = (r == GUARD_USABLE_NOW);
+ if (r == GUARD_USABLE_NOW) {
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ } else if (r == GUARD_MAYBE_USABLE_LATER) {
+ // Wait till either a better guard succeeds, or till
+ // all better guards fail.
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT);
+ } else {
+ tor_assert_nonfatal(r == GUARD_USABLE_NEVER);
+ return - END_CIRC_REASON_INTERNAL;
+ }
+
+ /* XXXX #21422 -- the rest of this branch needs careful thought!
+ * Some of the things here need to happen when a circuit becomes
+ * mechanically open; some need to happen when it is actually usable.
+ * I think I got them right, but more checking would be wise. -NM
+ */
+
+ if (circuit_timeout_want_to_count_circ(circ)) {
+ struct timeval end;
+ long timediff;
+ tor_gettimeofday(&end);
+ timediff = tv_mdiff(&circ->base_.timestamp_began, &end);
+
+ /*
+ * If the circuit build time is much greater than we would have cut
+ * it off at, we probably had a suspend event along this codepath,
+ * and we should discard the value.
+ */
+ if (timediff < 0 ||
+ timediff > 2*get_circuit_build_close_time_ms()+1000) {
+ log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. "
+ "Assuming clock jump. Purpose %d (%s)", timediff,
+ circ->base_.purpose,
+ circuit_purpose_to_string(circ->base_.purpose));
+ } else if (!circuit_build_times_disabled(get_options())) {
+ /* Only count circuit times if the network is live */
+ if (circuit_build_times_network_check_live(
+ get_circuit_build_times())) {
+ circuit_build_times_add_time(get_circuit_build_times_mutable(),
+ (build_time_t)timediff);
+ circuit_build_times_set_timeout(get_circuit_build_times_mutable());
}
- /* We're done with measurement circuits here. Just close them */
- if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_build_times_network_circ_success(
+ get_circuit_build_times_mutable());
}
- return 0;
}
-
- if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
- log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
- return - END_CIRC_REASON_INTERNAL;
+ }
+ log_info(LD_CIRC,"circuit built!");
+ circuit_reset_failure_count(0);
+
+ if (circ->build_state->onehop_tunnel || circ->has_opened) {
+ control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0);
+ }
+
+ pathbias_count_build_success(circ);
+ circuit_rep_hist_note_result(circ);
+ if (is_usable_for_streams)
+ circuit_has_opened(circ); /* do other actions as necessary */
+
+ if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) {
+ const or_options_t *options = get_options();
+ note_that_we_completed_a_circuit();
+ /* FFFF Log a count of known routers here */
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
+ if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) {
+ log_notice(LD_GENERAL,
+ "Tor has successfully opened a circuit. "
+ "Looks like client functionality is working.");
}
-
- circuit_pick_extend_handshake(&ec.cell_type,
- &ec.create_cell.cell_type,
- &ec.create_cell.handshake_type,
- hop->extend_info);
-
- tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
- ec.orport_ipv4.port = hop->extend_info->port;
- tor_addr_make_unspec(&ec.orport_ipv6.addr);
- memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
- /* Set the ED25519 identity too -- it will only get included
- * in the extend2 cell if we're configured to use it, though. */
- ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity);
-
- len = onion_skin_create(ec.create_cell.handshake_type,
- hop->extend_info,
- &hop->handshake_state,
- ec.create_cell.onionskin);
- if (len < 0) {
- log_warn(LD_CIRC,"onion_skin_create failed.");
- return - END_CIRC_REASON_INTERNAL;
+ control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED");
+ clear_broken_connection_map(1);
+ if (server_mode(options) && !check_whether_orport_reachable(options)) {
+ inform_testing_reachability();
+ consider_testing_reachability(1, 1);
}
- ec.create_cell.handshake_len = len;
+ }
- log_info(LD_CIRC,"Sending extend relay cell.");
- {
- uint8_t command = 0;
- uint16_t payload_len=0;
- uint8_t payload[RELAY_PAYLOAD_SIZE];
- if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
- log_warn(LD_CIRC,"Couldn't format extend cell");
- return -END_CIRC_REASON_INTERNAL;
- }
+ /* We're done with measurement circuits here. Just close them */
+ if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
+ }
+ return 0;
+}
- /* send it to hop->prev, because it will transfer
- * it to a create cell and then send to hop */
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
- command,
- (char*)payload, payload_len,
- hop->prev) < 0)
- return 0; /* circuit is closed */
+/**
+ * Called from circuit_send_next_onion_skin() when we find that we have a hop
+ * other than the first that we need to extend to: use <b>hop</b>'s
+ * information to extend the circuit another step. Return 0 on success;
+ * -reason on failure (if the circuit should be torn down).
+ */
+static int
+circuit_send_intermediate_onion_skin(origin_circuit_t *circ,
+ crypt_path_t *hop)
+{
+ int len;
+ extend_cell_t ec;
+ memset(&ec, 0, sizeof(ec));
+
+ log_debug(LD_CIRC,"starting to send subsequent skin.");
+
+ if (tor_addr_family(&hop->extend_info->addr) != AF_INET) {
+ log_warn(LD_BUG, "Trying to extend to a non-IPv4 address.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+
+ circuit_pick_extend_handshake(&ec.cell_type,
+ &ec.create_cell.cell_type,
+ &ec.create_cell.handshake_type,
+ hop->extend_info);
+
+ tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr);
+ ec.orport_ipv4.port = hop->extend_info->port;
+ tor_addr_make_unspec(&ec.orport_ipv6.addr);
+ memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN);
+ /* Set the ED25519 identity too -- it will only get included
+ * in the extend2 cell if we're configured to use it, though. */
+ ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity);
+
+ len = onion_skin_create(ec.create_cell.handshake_type,
+ hop->extend_info,
+ &hop->handshake_state,
+ ec.create_cell.onionskin);
+ if (len < 0) {
+ log_warn(LD_CIRC,"onion_skin_create failed.");
+ return - END_CIRC_REASON_INTERNAL;
+ }
+ ec.create_cell.handshake_len = len;
+
+ log_info(LD_CIRC,"Sending extend relay cell.");
+ {
+ uint8_t command = 0;
+ uint16_t payload_len=0;
+ uint8_t payload[RELAY_PAYLOAD_SIZE];
+ if (extend_cell_format(&command, &payload_len, payload, &ec)<0) {
+ log_warn(LD_CIRC,"Couldn't format extend cell");
+ return -END_CIRC_REASON_INTERNAL;
}
- hop->state = CPATH_STATE_AWAITING_KEYS;
+
+ /* send it to hop->prev, because that relay will transfer
+ * it to a create cell and then send to hop */
+ if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ command,
+ (char*)payload, payload_len,
+ hop->prev) < 0)
+ return 0; /* circuit is closed */
}
+ hop->state = CPATH_STATE_AWAITING_KEYS;
return 0;
}
@@ -1325,40 +1371,77 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return 0;
}
-/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in
- * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are
- * used as follows:
+/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data.
+ *
+ * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden
+ * service circuits and <b>key_data</b> must be at least
+ * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length.
+ *
+ * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN
+ * bytes, which are used as follows:
* - 20 to initialize f_digest
* - 20 to initialize b_digest
* - 16 to key f_crypto
* - 16 to key b_crypto
*
* (If 'reverse' is true, then f_XX and b_XX are swapped.)
+ *
+ * Return 0 if init was successful, else -1 if it failed.
*/
int
-circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse)
+circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3)
{
crypto_digest_t *tmp_digest;
crypto_cipher_t *tmp_crypto;
+ size_t digest_len = 0;
+ size_t cipher_key_len = 0;
tor_assert(cpath);
tor_assert(key_data);
tor_assert(!(cpath->f_crypto || cpath->b_crypto ||
cpath->f_digest || cpath->b_digest));
- cpath->f_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN);
- cpath->b_digest = crypto_digest_new();
- crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN);
+ /* Basic key size validation */
+ if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) {
+ return -1;
+ }
- if (!(cpath->f_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)))) {
+ /* If we are using this cpath for next gen onion services use SHA3-256,
+ otherwise use good ol' SHA1 */
+ if (is_hs_v3) {
+ digest_len = DIGEST256_LEN;
+ cipher_key_len = CIPHER256_KEY_LEN;
+ cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256);
+ } else {
+ digest_len = DIGEST_LEN;
+ cipher_key_len = CIPHER_KEY_LEN;
+ cpath->f_digest = crypto_digest_new();
+ cpath->b_digest = crypto_digest_new();
+ }
+
+ tor_assert(digest_len != 0);
+ tor_assert(cipher_key_len != 0);
+ const int cipher_key_bits = (int) cipher_key_len * 8;
+
+ crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len);
+ crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len);
+
+ cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len),
+ cipher_key_bits);
+ if (!cpath->f_crypto) {
log_warn(LD_BUG,"Forward cipher initialization failed.");
return -1;
}
- if (!(cpath->b_crypto =
- crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) {
+
+ cpath->b_crypto = crypto_cipher_new_with_bits(
+ key_data+(2*digest_len)+cipher_key_len,
+ cipher_key_bits);
+ if (!cpath->b_crypto) {
log_warn(LD_BUG,"Backward cipher initialization failed.");
return -1;
}
@@ -1424,7 +1507,7 @@ circuit_finish_handshake(origin_circuit_t *circ,
onion_handshake_state_release(&hop->handshake_state);
- if (circuit_init_cpath_crypto(hop, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) {
return -END_CIRC_REASON_TORPROTOCOL;
}
@@ -1491,12 +1574,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason)
int
onionskin_answer(or_circuit_t *circ,
const created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce)
{
cell_t cell;
crypt_path_t *tmp_cpath;
+ tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN);
+
if (created_cell_format(&cell, created_cell) < 0) {
log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)",
(int)created_cell->cell_type, (int)created_cell->handshake_len);
@@ -1512,7 +1597,7 @@ onionskin_answer(or_circuit_t *circ,
log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.",
(unsigned int)get_uint32(keys),
(unsigned int)get_uint32(keys+20));
- if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) {
+ if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) {
log_warn(LD_BUG,"Circuit initialization failed");
tor_free(tmp_cpath);
return -1;
@@ -1956,9 +2041,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity)
}
if (options->ExitNodes) {
log_warn(LD_CIRC,
- "No specified %sexit routers seem to be running: "
+ "No exits in ExitNodes%s seem to be running: "
"can't choose an exit.",
- options->ExcludeExitNodesUnion_ ? "non-excluded " : "");
+ options->ExcludeExitNodesUnion_ ?
+ ", except possibly those excluded by your configuration, " : "");
}
return NULL;
}
@@ -2311,6 +2397,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop)
}
}
+#ifdef TOR_UNIT_TESTS
+
+/** Unittest helper function: Count number of hops in cpath linked list. */
+unsigned int
+cpath_get_n_hops(crypt_path_t **head_ptr)
+{
+ unsigned int n_hops = 0;
+ crypt_path_t *tmp;
+
+ if (!*head_ptr) {
+ return 0;
+ }
+
+ tmp = *head_ptr;
+ if (tmp) {
+ n_hops++;
+ tmp = (*head_ptr)->next;
+ }
+
+ return n_hops;
+}
+
+#endif
+
/** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
* and <b>state</b> and the cpath <b>head</b> (currently populated only
* to length <b>cur_len</b> to decide a suitable middle hop for a
@@ -2580,7 +2690,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect)
ed_pubkey = node_get_ed25519_id(node);
} else if (node_get_ed25519_id(node)) {
log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't "
- " be able to authenticate it.",
+ "be able to authenticate it.",
node_describe(node));
}
diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h
index 45d9b2fb75..62a6367ed2 100644
--- a/src/or/circuitbuild.h
+++ b/src/or/circuitbuild.h
@@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ);
int circuit_send_next_onion_skin(origin_circuit_t *circ);
void circuit_note_clock_jumped(int seconds_elapsed);
int circuit_extend(cell_t *cell, circuit_t *circ);
-int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data,
- int reverse);
+int circuit_init_cpath_crypto(crypt_path_t *cpath,
+ const char *key_data, size_t key_data_len,
+ int reverse, int is_hs_v3);
struct created_cell_t;
int circuit_finish_handshake(origin_circuit_t *circ,
const struct created_cell_t *created_cell);
@@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer,
int reason);
int onionskin_answer(or_circuit_t *circ,
const struct created_cell_t *created_cell,
- const char *keys,
+ const char *keys, size_t keys_len,
const uint8_t *rend_circ_nonce);
MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
int *need_uptime,
@@ -83,6 +84,8 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes));
#if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS)
STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags,
const or_options_t *options);
+unsigned int cpath_get_n_hops(crypt_path_t **head_ptr);
+
#endif
#endif
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 86b0aa097a..d891c89f38 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -65,8 +65,10 @@
#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"
#include "networkstatus.h"
#include "nodelist.h"
#include "onion.h"
@@ -957,6 +959,7 @@ circuit_free(circuit_t *circ)
crypto_pk_free(ocirc->intro_key);
rend_data_free(ocirc->rend_data);
+ hs_ident_circuit_free(ocirc->hs_ident);
tor_free(ocirc->dest_address);
if (ocirc->socks_username) {
@@ -1530,6 +1533,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 <b>start</b> or at the start of the list if <b>start</b>
+ * 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
* <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if
* set) matches the private key digest of the rend data associated with the
@@ -1911,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/circuitlist.h b/src/or/circuitlist.h
index d647062f46..048cd5f763 100644
--- a/src/or/circuitlist.h
+++ b/src/or/circuitlist.h
@@ -48,6 +48,8 @@ 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);
void circuit_mark_all_unused_circs(void);
diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c
index c2440b13f0..fde2d22a89 100644
--- a/src/or/circuitmux_ewma.c
+++ b/src/or/circuitmux_ewma.c
@@ -731,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_add(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -746,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma)
smartlist_pqueue_remove(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index),
+ offsetof(cell_ewma_t, heap_index),
ewma);
}
@@ -760,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol)
return smartlist_pqueue_pop(pol->active_circuit_pqueue,
compare_cell_ewma_counts,
- STRUCT_OFFSET(cell_ewma_t, heap_index));
+ offsetof(cell_ewma_t, heap_index));
}
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 9f9d3abf7c..21cc9c540f 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -42,6 +42,9 @@
#include "control.h"
#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"
#include "policies.h"
@@ -55,6 +58,36 @@
static void circuit_expire_old_circuits_clientside(void);
static void circuit_increment_failure_count(void);
+/** Check whether the hidden service destination of the stream at
+ * <b>edge_conn</b> is the same as the destination of the circuit at
+ * <b>origin_circ</b>. */
+static int
+circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
+ const origin_circuit_t *origin_circ)
+{
+ /* Check if this is a v2 rendezvous circ/stream */
+ if ((edge_conn->rend_data && !origin_circ->rend_data) ||
+ (!edge_conn->rend_data && origin_circ->rend_data) ||
+ (edge_conn->rend_data && origin_circ->rend_data &&
+ rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
+ rend_data_get_address(origin_circ->rend_data)))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ /* Check if this is a v3 rendezvous circ/stream */
+ if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
+ (!edge_conn->hs_ident && origin_circ->hs_ident) ||
+ (edge_conn->hs_ident && origin_circ->hs_ident &&
+ !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ &origin_circ->hs_ident->identity_pk))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ return 1;
+}
+
/** Return 1 if <b>circ</b> could be returned by circuit_get_best().
* Else return 0.
*/
@@ -169,14 +202,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
/* can't exit from this router */
return 0;
}
- } else { /* not general */
+ } else { /* not general: this might be a rend circuit */
const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
- if ((edge_conn->rend_data && !origin_circ->rend_data) ||
- (!edge_conn->rend_data && origin_circ->rend_data) ||
- (edge_conn->rend_data && origin_circ->rend_data &&
- rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
- rend_data_get_address(origin_circ->rend_data)))) {
- /* this circ is not for this conn */
+ if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
return 0;
}
}
@@ -755,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;
}
@@ -1086,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 (num_rend_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 */
@@ -1189,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);
@@ -1253,11 +1302,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)
@@ -1339,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;
}
@@ -1614,11 +1657,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);
@@ -1768,7 +1811,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
@@ -2348,8 +2391,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
/* We are attaching a stream to a rendezvous circuit. That means
* that an attempt to connect to a hidden service just
* succeeded. Tell rendclient.c. */
- rend_client_note_connection_attempt_ended(
- ENTRY_TO_EDGE_CONN(apconn)->rend_data);
+ hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
}
if (cpath) { /* we were given one; use it */
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/command.c b/src/or/command.c
index c667cbbe52..2c82984901 100644
--- a/src/or/command.c
+++ b/src/or/command.c
@@ -381,7 +381,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan)
created_cell.handshake_len = len;
if (onionskin_answer(circ, &created_cell,
- (const char *)keys, rend_circ_nonce)<0) {
+ (const char *)keys, sizeof(keys),
+ rend_circ_nonce)<0) {
log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return;
diff --git a/src/or/config.c b/src/or/config.c
index 67bf3b9671..9b6bf40ebf 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -91,6 +91,7 @@
#include "relay.h"
#include "rendclient.h"
#include "rendservice.h"
+#include "hs_config.h"
#include "rephist.h"
#include "router.h"
#include "sandbox.h"
@@ -176,7 +177,7 @@ static config_abbrev_t option_abbrevs_[] = {
* or_options_t.<b>member</b>"
*/
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -674,6 +675,13 @@ static const config_deprecation_t option_deprecation_notes_[] = {
"easier to fingerprint, and may open you to esoteric attacks." },
/* End of options deprecated since 0.2.9.2-alpha. */
+ /* Deprecated since 0.3.2.0-alpha. */
+ { "HTTPProxy", "It only applies to direct unencrypted HTTP connections "
+ "to your directory server, which your Tor probably wasn't using." },
+ { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy "
+ "which should be used with HTTPSProxyAuthenticator." },
+ /* End of options deprecated since 0.3.2.0-alpha. */
+
{ NULL, NULL }
};
@@ -725,7 +733,7 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val,
STATIC config_format_t options_format = {
sizeof(or_options_t),
OR_OPTIONS_MAGIC,
- STRUCT_OFFSET(or_options_t, magic_),
+ offsetof(or_options_t, magic_),
option_abbrevs_,
option_deprecation_notes_,
option_vars_,
@@ -1675,7 +1683,7 @@ options_act(const or_options_t *old_options)
sweep_bridge_list();
}
- if (running_tor && rend_config_services(options, 0)<0) {
+ if (running_tor && hs_config_service_all(options, 0)<0) {
log_warn(LD_BUG,
"Previously validated hidden services line could not be added!");
return -1;
@@ -1792,7 +1800,7 @@ options_act(const or_options_t *old_options)
monitor_owning_controller_process(options->OwningControllerProcess);
/* reload keys as needed for rendezvous services. */
- if (rend_service_load_all_keys(NULL)<0) {
+ if (hs_service_load_all_keys() < 0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
return -1;
}
@@ -2129,6 +2137,7 @@ static const struct {
{ "--dump-config", ARGUMENT_OPTIONAL },
{ "--list-fingerprint", TAKES_NO_ARGUMENT },
{ "--keygen", TAKES_NO_ARGUMENT },
+ { "--key-expiration", ARGUMENT_OPTIONAL },
{ "--newpass", TAKES_NO_ARGUMENT },
{ "--no-passphrase", TAKES_NO_ARGUMENT },
{ "--passphrase-fd", ARGUMENT_NECESSARY },
@@ -2798,10 +2807,6 @@ compute_publishserverdescriptor(or_options_t *options)
* will generate too many circuits and potentially overload the network. */
#define MIN_CIRCUIT_STREAM_TIMEOUT 10
-/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
- * expose more information than we're comfortable with. */
-#define MIN_HEARTBEAT_PERIOD (30*60)
-
/** Lowest recommended value for CircuitBuildTimeout; if it is set too low
* and LearnCircuitBuildTimeout is off, the failure rate for circuit
* construction may be very high. In that case, if it is set below this
@@ -3155,7 +3160,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
"UseEntryGuards. Disabling.");
options->UseEntryGuards = 0;
}
- if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) {
+ if (!options->DownloadExtraInfo && authdir_mode_v3(options)) {
log_info(LD_CONFIG, "Authoritative directories always try to download "
"extra-info documents. Setting DownloadExtraInfo.");
options->DownloadExtraInfo = 1;
@@ -4003,7 +4008,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours.");
}
- if (rend_config_services(options, 1) < 0)
+ if (hs_config_service_all(options, 1) < 0)
REJECT("Failed to configure rendezvous options. See logs for details.");
/* Parse client-side authorization for hidden services. */
@@ -4928,6 +4933,9 @@ options_init_from_torrc(int argc, char **argv)
for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
if (!strcmp(p_index->key,"--keygen")) {
command = CMD_KEYGEN;
+ } else if (!strcmp(p_index->key, "--key-expiration")) {
+ command = CMD_KEY_EXPIRATION;
+ command_arg = p_index->value;
} else if (!strcmp(p_index->key,"--list-fingerprint")) {
command = CMD_LIST_FINGERPRINT;
} else if (!strcmp(p_index->key, "--hash-password")) {
@@ -6252,8 +6260,9 @@ port_cfg_free(port_cfg_t *port)
/** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is
* on a publicly routable address. */
static void
-warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname,
- int listener_type)
+warn_nonlocal_client_ports(const smartlist_t *ports,
+ const char *portname,
+ const int listener_type)
{
SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
if (port->type != listener_type)
@@ -6938,7 +6947,8 @@ parse_ports(or_options_t *options, int validate_only,
options->SocksPort_lines,
"Socks", CONN_TYPE_AP_LISTENER,
"127.0.0.1", 9050,
- CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) {
+ ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+ | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
*msg = tor_strdup("Invalid SocksPort configuration");
goto err;
}
diff --git a/src/or/config.h b/src/or/config.h
index 27aec7fe3d..3cfa7c4e5b 100644
--- a/src/or/config.h
+++ b/src/or/config.h
@@ -18,6 +18,10 @@
#define KERNEL_MAY_SUPPORT_IPFW
#endif
+/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might
+ * expose more information than we're comfortable with. */
+#define MIN_HEARTBEAT_PERIOD (30*60)
+
MOCK_DECL(const char*, get_dirportfrontpage, (void));
MOCK_DECL(const or_options_t *, get_options, (void));
MOCK_DECL(or_options_t *, get_options_mutable, (void));
diff --git a/src/or/connection.c b/src/or/connection.c
index 4e890497e9..5c65e886c0 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -84,6 +84,7 @@
#include "geoip.h"
#include "main.h"
#include "hs_common.h"
+#include "hs_ident.h"
#include "nodelist.h"
#include "policies.h"
#include "reasons.h"
@@ -605,6 +606,7 @@ connection_free_(connection_t *conn)
}
if (CONN_IS_EDGE(conn)) {
rend_data_free(TO_EDGE_CONN(conn)->rend_data);
+ hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident);
}
if (conn->type == CONN_TYPE_CONTROL) {
control_connection_t *control_conn = TO_CONTROL_CONN(conn);
@@ -636,6 +638,7 @@ connection_free_(connection_t *conn)
}
rend_data_free(dir_conn->rend_data);
+ hs_ident_dir_conn_free(dir_conn->hs_ident);
if (dir_conn->guard_state) {
/* Cancel before freeing, if it's still there. */
entry_guard_cancel(&dir_conn->guard_state);
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 8480a35458..12ddc7e829 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"
@@ -2455,8 +2456,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn)
*
* If ap_conn is broken, mark it for close and return -1. Else return 0.
*/
-int
-connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+MOCK_IMPL(int,
+connection_ap_handshake_send_begin,(entry_connection_t *ap_conn))
{
char payload[CELL_PAYLOAD_SIZE];
int payload_len;
@@ -3007,7 +3008,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply,
* <0 and set *<b>end_reason_out</b> to the end reason we should send back to
* the client.
*
- * Return -1 in the case where want to send a RELAY_END cell, and < -1 when
+ * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when
* we don't.
**/
STATIC int
@@ -3066,6 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
return 0;
}
+/** For the given <b>circ</b> and the edge connection <b>conn</b>, 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 *conn)
+{
+ int ret;
+ origin_circuit_t *origin_circ;
+
+ 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);
+ 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_nonfatal_unreached();
+ return -1;
+ }
+ 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(conn->stream_id, circ,
+ END_STREAM_REASON_DONE,
+ origin_circ->cpath->prev);
+ 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 (ret < -1) {
+ return END_CIRC_AT_ORIGIN;
+ } else {
+ return 0;
+ }
+ }
+
+ /* 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);
+
+ hs_inc_rdv_stream_counter(origin_circ);
+
+ /* 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);
+ 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 +3300,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;
@@ -3566,8 +3601,12 @@ int
connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
{
tor_assert(conn);
- if (conn->rend_data)
+ /* It should not be possible to set both of these structs */
+ tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident));
+
+ if (conn->rend_data || conn->hs_ident) {
return 1;
+ }
return 0;
}
diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h
index e4780b3c7d..9987f88b85 100644
--- a/src/or/connection_edge.h
+++ b/src/or/connection_edge.h
@@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn);
void connection_ap_about_to_close(entry_connection_t *edge_conn);
void connection_exit_about_to_close(edge_connection_t *edge_conn);
-int connection_ap_handshake_send_begin(entry_connection_t *ap_conn);
+MOCK_DECL(int,
+ connection_ap_handshake_send_begin,(entry_connection_t *ap_conn));
int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn);
entry_connection_t *connection_ap_make_link(connection_t *partner,
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index 753148291c..7c929e5272 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -472,7 +472,7 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out, int wide_circ_ids)
var_cell_t *
var_cell_new(uint16_t payload_len)
{
- size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len;
+ size_t size = offsetof(var_cell_t, payload) + payload_len;
var_cell_t *cell = tor_malloc_zero(size);
cell->payload_len = payload_len;
cell->command = 0;
@@ -491,7 +491,7 @@ var_cell_copy(const var_cell_t *src)
size_t size = 0;
if (src != NULL) {
- size = STRUCT_OFFSET(var_cell_t, payload) + src->payload_len;
+ size = offsetof(var_cell_t, payload) + src->payload_len;
copy = tor_malloc_zero(size);
copy->payload_len = src->payload_len;
copy->command = src->command;
@@ -1369,7 +1369,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving))
connection_start_reading(TO_CONN(conn));
log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT,
conn->base_.s);
- note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C);
if (connection_tls_continue_handshake(conn) < 0)
return -1;
diff --git a/src/or/control.c b/src/or/control.c
index 9bcf1ee364..724d4b35c0 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -60,6 +60,7 @@
#include "hibernate.h"
#include "hs_common.h"
#include "main.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
@@ -802,7 +803,7 @@ queued_events_flush_all(int force)
}
/** Libevent callback: Flushes pending events to controllers that are
- * interested in them */
+ * interested in them. */
static void
flush_queued_events_cb(evutil_socket_t fd, short what, void *arg)
{
@@ -1892,6 +1893,12 @@ getinfo_helper_dir(control_connection_t *control_conn,
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/id/* "
+ "instead of desc/id/*.";
+ return 0;
}
} else if (!strcmpstart(question, "desc/name/")) {
const routerinfo_t *ri = NULL;
@@ -1905,7 +1912,16 @@ getinfo_helper_dir(control_connection_t *control_conn,
const char *body = signed_descriptor_get_body(&ri->cache_info);
if (body)
*answer = tor_strndup(body, ri->cache_info.signed_descriptor_len);
+ } else if (! we_fetch_router_descriptors(get_options())) {
+ /* Descriptors won't be available, provide proper error */
+ *errmsg = "We fetch microdescriptors, not router "
+ "descriptors. You'll need to use md/name/* "
+ "instead of desc/name/*.";
+ return 0;
}
+ } else if (!strcmp(question, "desc/download-enabled")) {
+ int r = we_fetch_router_descriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmp(question, "desc/all-recent")) {
routerlist_t *routerlist = router_get_routerlist();
smartlist_t *sl = smartlist_new();
@@ -1991,6 +2007,9 @@ getinfo_helper_dir(control_connection_t *control_conn,
if (md && md->body) {
*answer = tor_strndup(md->body, md->bodylen);
}
+ } else if (!strcmp(question, "md/download-enabled")) {
+ int r = we_fetch_microdescriptors(get_options());
+ tor_asprintf(answer, "%d", !!r);
} else if (!strcmpstart(question, "desc-annotations/id/")) {
const routerinfo_t *ri = NULL;
const node_t *node =
@@ -2907,7 +2926,8 @@ getinfo_helper_sr(control_connection_t *control_conn,
* *<b>a</b>. If an internal error occurs, return -1 and optionally set
* *<b>error_out</b> to point to an error message to be delivered to the
* controller. On success, _or if the key is not recognized_, return 0. Do not
- * set <b>a</b> if the key is not recognized.
+ * set <b>a</b> if the key is not recognized but you may set <b>error_out</b>
+ * to improve the error message.
*/
typedef int (*getinfo_helper_t)(control_connection_t *,
const char *q, char **a,
@@ -3012,9 +3032,13 @@ static const getinfo_item_t getinfo_items[] = {
PREFIX("desc/name/", dir, "Router descriptors by nickname."),
ITEM("desc/all-recent", dir,
"All non-expired, non-superseded router descriptors."),
+ ITEM("desc/download-enabled", dir,
+ "Do we try to download router descriptors?"),
ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */
PREFIX("md/id/", dir, "Microdescriptors by ID"),
PREFIX("md/name/", dir, "Microdescriptors by name"),
+ ITEM("md/download-enabled", dir,
+ "Do we try to download microdescriptors?"),
PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."),
PREFIX("hs/client/desc/id", dir,
"Hidden Service descriptor in client's cache by onion."),
@@ -3162,7 +3186,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_t *questions = smartlist_new();
smartlist_t *answers = smartlist_new();
smartlist_t *unrecognized = smartlist_new();
- char *msg = NULL, *ans = NULL;
+ char *ans = NULL;
int i;
(void) len; /* body is NUL-terminated, so it's safe to ignore the length. */
@@ -3177,20 +3201,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
goto done;
}
if (!ans) {
- smartlist_add(unrecognized, (char*)q);
+ if (errmsg) /* use provided error message */
+ smartlist_add_strdup(unrecognized, errmsg);
+ else /* use default error message */
+ smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q);
} else {
smartlist_add_strdup(answers, q);
smartlist_add(answers, ans);
}
} SMARTLIST_FOREACH_END(q);
+
if (smartlist_len(unrecognized)) {
+ /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */
for (i=0; i < smartlist_len(unrecognized)-1; ++i)
connection_printf_to_buf(conn,
- "552-Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552-%s\r\n",
+ (char *)smartlist_get(unrecognized, i));
+
connection_printf_to_buf(conn,
- "552 Unrecognized key \"%s\"\r\n",
- (char*)smartlist_get(unrecognized, i));
+ "552 %s\r\n",
+ (char *)smartlist_get(unrecognized, i));
goto done;
}
@@ -3217,8 +3247,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
smartlist_free(answers);
SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp));
smartlist_free(questions);
+ SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp));
smartlist_free(unrecognized);
- tor_free(msg);
return 0;
}
@@ -4882,6 +4912,38 @@ peek_connection_has_control0_command(connection_t *conn)
return peek_buf_has_control0_command(conn->inbuf);
}
+static int
+peek_connection_has_http_command(connection_t *conn)
+{
+ return peek_buf_has_http_command(conn->inbuf);
+}
+
+static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] =
+ "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy"
+ "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Tor's ControlPort is not an HTTP proxy</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n"
+ "<p>\n"
+ "It appears you have configured your web browser to use Tor's control port"
+ " as an HTTP proxy.\n"
+ "This is not correct: Tor's default SOCKS proxy port is 9050.\n"
+ "Please configure your client accordingly.\n"
+ "</p>\n"
+ "<p>\n"
+ "See <a href=\"https://www.torproject.org/documentation.html\">"
+ "https://www.torproject.org/documentation.html</a> for more "
+ "information.\n"
+ "<!-- Plus this comment, to make the body response more than 512 bytes, so "
+ " IE will be willing to display it. Comment comment comment comment "
+ " comment comment comment comment comment comment comment comment.-->\n"
+ "</p>\n"
+ "</body>\n"
+ "</html>\n";
+
/** Called when data has arrived on a v1 control connection: Try to fetch
* commands from conn->inbuf, and execute them.
*/
@@ -4921,6 +4983,15 @@ connection_control_process_inbuf(control_connection_t *conn)
return 0;
}
+ /* If the user has the HTTP proxy port and the control port confused. */
+ if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH &&
+ peek_connection_has_http_command(TO_CONN(conn))) {
+ connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn);
+ log_notice(LD_CONTROL, "Received HTTP request on ControlPort");
+ connection_mark_and_flush(TO_CONN(conn));
+ return 0;
+ }
+
again:
while (1) {
size_t last_idx;
diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c
index f5fff2b331..d9371b3446 100644
--- a/src/or/cpuworker.c
+++ b/src/or/cpuworker.c
@@ -382,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_)
if (onionskin_answer(circ,
&rpl.created_cell,
- (const char*)rpl.keys,
+ (const char*)rpl.keys, sizeof(rpl.keys),
rpl.rend_auth_material) < 0) {
log_warn(LD_OR,"onionskin_answer failed. Closing.");
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
@@ -484,7 +484,7 @@ queue_pending_tasks(void)
if (!circ)
return;
- if (assign_onionskin_to_cpuworker(circ, onionskin))
+ if (assign_onionskin_to_cpuworker(circ, onionskin) < 0)
log_info(LD_OR,"assign_to_cpuworker failed. Ignoring.");
}
}
diff --git a/src/or/dircollate.c b/src/or/dircollate.c
index 172364c5f5..d34ebe8af5 100644
--- a/src/or/dircollate.c
+++ b/src/or/dircollate.c
@@ -53,7 +53,7 @@ ddmap_entry_free(ddmap_entry_t *e)
static ddmap_entry_t *
ddmap_entry_new(int n_votes)
{
- return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) +
+ return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) +
sizeof(vote_routerstatus_t *) * n_votes);
}
diff --git a/src/or/directory.c b/src/or/directory.c
index 45fbd1dd33..e079a5941f 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -18,7 +18,6 @@
#include "consdiffmgr.h"
#include "control.h"
#include "compat.h"
-#define DIRECTORY_PRIVATE
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
@@ -187,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:
@@ -245,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";
}
@@ -1035,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. */
@@ -1269,6 +1275,20 @@ 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 <b>ident</b> is stored, so the
+ * <b>ident</b> object must outlive the request.
+ */
+void
+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_UPLOAD_HSDESC);
+ }
+ req->hs_ident = ident;
+}
/** Set a static circuit_guard_state_t object to affliate with the request in
* <b>req</b>. This object will receive notification when the attempt to
* connect to the guard either succeeds or fails. */
@@ -1390,6 +1410,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);
@@ -1477,8 +1498,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 */
@@ -1836,6 +1865,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;
@@ -2190,6 +2225,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,
@@ -2490,6 +2527,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;
@@ -3181,6 +3221,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)
@@ -4918,7 +5004,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers,
goto done;
}
- if (authdir_mode_handles_descs(options, -1) &&
+ if (authdir_mode(options) &&
!strcmp(url,"/tor/")) { /* server descriptor post */
const char *msg = "[None]";
uint8_t purpose = authdir_mode_bridge(options) ?
@@ -5124,7 +5210,7 @@ connection_dir_finished_connecting(dir_connection_t *conn)
* Helper function for download_status_increment_failure(),
* download_status_reset(), and download_status_increment_attempt(). */
STATIC const smartlist_t *
-find_dl_schedule(download_status_t *dls, const or_options_t *options)
+find_dl_schedule(const download_status_t *dls, const or_options_t *options)
{
const int dir_server = dir_server_mode(options);
const int multi_d = networkstatus_consensus_can_use_multiple_directories(
@@ -5193,6 +5279,8 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options,
const smartlist_t *schedule = find_dl_schedule(dls, options);
tor_assert(schedule != NULL && smartlist_len(schedule) >= 2);
*min = *((int *)(smartlist_get(schedule, 0)));
+ /* Increment on failure schedules always use exponential backoff, but they
+ * have a smaller limit when they're deterministic */
if (dls->backoff == DL_SCHED_DETERMINISTIC)
*max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1))));
else
@@ -5201,8 +5289,9 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options,
/** Advance one delay step. The algorithm is to use the previous delay to
* compute an increment, we construct a value uniformly at random between
- * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger
- * than max_delay, and return it.
+ * delay+1 and (delay*(DIR_DEFAULT_RANDOM_MULTIPLIER+1))+1 (or
+ * DIR_TEST_NET_RANDOM_MULTIPLIER in test networks).
+ * We then clamp that value to be no larger than max_delay, and return it.
*
* Requires that delay is less than INT_MAX, and delay is in [0,max_delay].
*/
@@ -5221,11 +5310,11 @@ next_random_exponential_delay(int delay, int max_delay)
/* How much are we willing to add to the delay? */
int max_increment;
- int multiplier = 3; /* no more than quadruple the previous delay */
+ int multiplier = DIR_DEFAULT_RANDOM_MULTIPLIER;
if (get_options()->TestingTorNetwork) {
/* Decrease the multiplier in testing networks. This reduces the variance,
* so that bootstrap is more reliable. */
- multiplier = 2; /* no more than triple the previous delay */
+ multiplier = DIR_TEST_NET_RANDOM_MULTIPLIER;
}
if (delay && delay < (INT_MAX-1) / multiplier) {
@@ -5377,6 +5466,11 @@ download_status_increment_failure(download_status_t *dls, int status_code,
tor_assert(dls);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
/* count the failure */
if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) {
++dls->n_download_failures;
@@ -5401,14 +5495,16 @@ download_status_increment_failure(download_status_t *dls, int status_code,
download_status_log_helper(item, !dls->increment_on, "failed",
"concurrently", dls->n_download_failures,
- increment, dls->next_attempt_at, now);
+ increment,
+ download_status_get_next_attempt_at(dls),
+ now);
if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) {
/* stop this schedule retrying on failure, it will launch concurrent
* connections instead */
return TIME_MAX;
} else {
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
}
}
@@ -5429,6 +5525,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
tor_assert(dls);
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) {
/* this schedule should retry on failure, and not launch any concurrent
attempts */
@@ -5447,9 +5548,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item,
download_status_log_helper(item, dls->increment_on, "attempted",
"on failure", dls->n_download_attempts,
- delay, dls->next_attempt_at, now);
+ delay, download_status_get_next_attempt_at(dls),
+ now);
- return dls->next_attempt_at;
+ return download_status_get_next_attempt_at(dls);
+}
+
+static time_t
+download_status_get_initial_delay_from_now(const download_status_t *dls)
+{
+ const smartlist_t *schedule = find_dl_schedule(dls, get_options());
+ /* We use constant initial delays, even in exponential backoff
+ * schedules. */
+ return time(NULL) + *(int *)smartlist_get(schedule, 0);
}
/** Reset <b>dls</b> so that it will be considered downloadable
@@ -5470,11 +5581,9 @@ download_status_reset(download_status_t *dls)
|| dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD)
return; /* Don't reset this. */
- const smartlist_t *schedule = find_dl_schedule(dls, get_options());
-
dls->n_download_failures = 0;
dls->n_download_attempts = 0;
- dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0);
+ dls->next_attempt_at = download_status_get_initial_delay_from_now(dls);
dls->last_backoff_position = 0;
dls->last_delay_used = 0;
/* Don't reset dls->want_authority or dls->increment_on */
@@ -5501,6 +5610,12 @@ download_status_get_n_attempts(const download_status_t *dls)
time_t
download_status_get_next_attempt_at(const download_status_t *dls)
{
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ /* so give the answer we would have given if it had been */
+ return download_status_get_initial_delay_from_now(dls);
+ }
+
return dls->next_attempt_at;
}
diff --git a/src/or/directory.h b/src/or/directory.h
index 14d5ae9ef4..d3f8a45a82 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_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);
@@ -123,12 +127,19 @@ time_t download_status_increment_attempt(download_status_t *dls,
void download_status_reset(download_status_t *dls);
static int download_status_is_ready(download_status_t *dls, time_t now,
int max_failures);
+time_t download_status_get_next_attempt_at(const download_status_t *dls);
+
/** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is
* ready to get its download reattempted. */
static inline int
download_status_is_ready(download_status_t *dls, time_t now,
int max_failures)
{
+ /* dls wasn't reset before it was used */
+ if (dls->next_attempt_at == 0) {
+ download_status_reset(dls);
+ }
+
if (dls->backoff == DL_SCHED_DETERMINISTIC) {
/* Deterministic schedules can hit an endpoint; exponential backoff
* schedules just wait longer and longer. */
@@ -137,7 +148,7 @@ download_status_is_ready(download_status_t *dls, time_t now,
if (!under_failure_limit)
return 0;
}
- return dls->next_attempt_at <= now;
+ return download_status_get_next_attempt_at(dls) <= now;
}
static void download_status_mark_impossible(download_status_t *dl);
@@ -151,7 +162,6 @@ download_status_mark_impossible(download_status_t *dl)
int download_status_get_n_failures(const download_status_t *dls);
int download_status_get_n_attempts(const download_status_t *dls);
-time_t download_status_get_next_attempt_at(const download_status_t *dls);
int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
const char *resource);
@@ -193,7 +203,7 @@ STATIC char* authdir_type_to_string(dirinfo_type_t auth);
STATIC const char * dir_conn_purpose_to_string(int purpose);
STATIC int should_use_directory_guards(const or_options_t *options);
STATIC compression_level_t choose_compression_level(ssize_t n_bytes);
-STATIC const smartlist_t *find_dl_schedule(download_status_t *dls,
+STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls,
const or_options_t *options);
STATIC void find_dl_min_and_max_delay(download_status_t *dls,
const or_options_t *options,
@@ -206,5 +216,15 @@ STATIC int parse_hs_version_from_post(const char *url, const char *prefix,
STATIC unsigned parse_accept_encoding_header(const char *h);
#endif
+#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE)
+/* Used only by directory.c and test_dir.c */
+
+/* no more than quadruple the previous delay (multiplier + 1) */
+#define DIR_DEFAULT_RANDOM_MULTIPLIER (3)
+/* no more than triple the previous delay */
+#define DIR_TEST_NET_RANDOM_MULTIPLIER (2)
+
+#endif
+
#endif
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index 468fdbd488..e5654e3b90 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -704,10 +704,22 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source)
/* Do keypinning again ... this time, to add the pin if appropriate */
int keypin_status;
if (ri->cache_info.signing_key_cert) {
+ ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key;
+ /* First let's validate this pubkey before pinning it */
+ if (ed25519_validate_pubkey(pkey) < 0) {
+ log_warn(LD_DIRSERV, "Received bad key from %s (source %s)",
+ router_describe(ri), source);
+ control_event_or_authdir_new_descriptor("REJECTED",
+ ri->cache_info.signed_descriptor_body,
+ desclen, *msg);
+ routerinfo_free(ri);
+ return ROUTER_AUTHDIR_REJECTS;
+ }
+
+ /* Now pin it! */
keypin_status = keypin_check_and_add(
(const uint8_t*)ri->cache_info.identity_digest,
- ri->cache_info.signing_key_cert->signing_key.pubkey,
- ! key_pinning);
+ pkey->pubkey, ! key_pinning);
} else {
keypin_status = keypin_check_lone_rsa(
(const uint8_t*)ri->cache_info.identity_digest);
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index f5e29eb786..c65945fea7 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -306,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
signing_key_fingerprint);
}
- note_crypto_pk_op(SIGN_DIR);
{
char *sig = router_get_dirobj_signature(digest, DIGEST_LEN,
private_signing_key);
@@ -737,12 +736,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list,
}
} SMARTLIST_FOREACH_END(k_v_pair);
- if (n_found == 1)
+ if (n_found == 1) {
return value;
- else if (BUG(n_found > 1))
- return default_val;
- else
+ } else {
+ tor_assert_nonfatal(n_found == 0);
return default_val;
+ }
}
/** Minimum number of directory authorities voting for a parameter to
diff --git a/src/or/dns.c b/src/or/dns.c
index 98b684c904..2d642773f3 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -182,6 +182,18 @@ evdns_log_cb(int warn, const char *msg)
} else if (!strcmp(msg, "All nameservers have failed")) {
control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN");
all_down = 1;
+ } else if (!strcmpstart(msg, "Address mismatch on received DNS")) {
+ static ratelim_t mismatch_limit = RATELIM_INIT(3600);
+ const char *src = strstr(msg, " Apparent source");
+ if (!src || get_options()->SafeLogging) {
+ src = "";
+ }
+ log_fn_ratelim(&mismatch_limit, severity, LD_EXIT,
+ "eventdns: Received a DNS packet from "
+ "an IP address to which we did not send a request. This "
+ "could be a DNS spoofing attempt, or some kind of "
+ "misconfiguration.%s", src);
+ return;
}
tor_log(severity, LD_EXIT, "eventdns: %s", msg);
}
@@ -366,7 +378,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires)
resolve->expire = expires;
smartlist_pqueue_add(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx),
+ offsetof(cached_resolve_t, minheap_idx),
resolve);
}
@@ -413,7 +425,7 @@ purge_expired_resolves(time_t now)
break;
smartlist_pqueue_pop(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
if (resolve->state == CACHE_STATE_PENDING) {
log_debug(LD_EXIT,
@@ -1928,7 +1940,7 @@ dns_launch_wildcard_checks(void)
launch_wildcard_check(8, 16, ipv6, ".com");
launch_wildcard_check(8, 16, ipv6, ".org");
launch_wildcard_check(8, 16, ipv6, ".net");
- }
+ }
}
}
@@ -2071,7 +2083,7 @@ assert_cache_ok_(void)
smartlist_pqueue_assert_ok(cached_resolve_pqueue,
compare_cached_resolves_by_expiry_,
- STRUCT_OFFSET(cached_resolve_t, minheap_idx));
+ offsetof(cached_resolve_t, minheap_idx));
SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
{
diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c
index fa768fc4a6..739ec82484 100644
--- a/src/or/entrynodes.c
+++ b/src/or/entrynodes.c
@@ -813,7 +813,7 @@ STATIC entry_guard_t *
entry_guard_add_to_sample(guard_selection_t *gs,
const node_t *node)
{
- log_info(LD_GUARD, "Adding %s as to the entry guard sample set.",
+ log_info(LD_GUARD, "Adding %s to the entry guard sample set.",
node_describe(node));
/* make sure that the guard is not already sampled. */
diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c
index b60d2e55c8..01dc06ce13 100644
--- a/src/or/ext_orport.c
+++ b/src/or/ext_orport.c
@@ -31,7 +31,7 @@
ext_or_cmd_t *
ext_or_cmd_new(uint16_t len)
{
- size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len;
+ size_t size = offsetof(ext_or_cmd_t, body) + len;
ext_or_cmd_t *cmd = tor_malloc(size);
cmd->len = len;
return cmd;
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_cell.c b/src/or/hs_cell.c
new file mode 100644
index 0000000000..a0e9074601
--- /dev/null
+++ b/src/or/hs_cell.c
@@ -0,0 +1,584 @@
+/* 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 "config.h"
+#include "rendservice.h"
+#include "replaycache.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"
+#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.
+ * 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 %u but "
+ "expected %d on circuit %u for service %s",
+ (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;
+ }
+ /* 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
+ * 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;
+
+ tor_assert(circ_nonce);
+ tor_assert(enc_key);
+ tor_assert(cell_out);
+
+ 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);
+ return cell_len;
+}
+
+/* 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.
+ *
+ * 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,
+ const origin_circuit_t *circ, const uint8_t *payload,
+ 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);
+
+ /* 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;
+ }
+
+ /* Success. */
+ *cell_ptr_out = cell;
+ return 0;
+ err:
+ return -1;
+}
+
+/* ========== */
+/* 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));
+
+ memwipe(mac, 0, sizeof(mac));
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* 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);
+
+ memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc));
+ }
+
+ /* 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;
+}
+
+/* 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;
+}
+
+/* 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;
+ time_t elapsed;
+ 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;
+
+ tor_assert(data);
+ tor_assert(circ);
+ tor_assert(service);
+
+ /* Parse the cell into a decoded data structure pointed by cell_ptr. */
+ if (parse_introduce2_cell(service, circ, data->payload, data->payload_len,
+ &cell) < 0) {
+ goto done;
+ }
+
+ 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;
+ }
+
+ /* 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,
+ 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",
+ 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:
+ if (intro_keys) {
+ memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t));
+ tor_free(intro_keys);
+ }
+ tor_free(decrypted);
+ trn_cell_introduce_encrypted_free(enc_cell);
+ trn_cell_introduce1_free(cell);
+ 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
new file mode 100644
index 0000000000..f32f7a4216
--- /dev/null
+++ b/src/or/hs_cell.h
@@ -0,0 +1,75 @@
+/* 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 "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. This structure is only used during
+ * INTRO2 parsing and discarded after that. */
+typedef struct hs_cell_introduce2_data_t {
+ /*** Immutable Section: Set on structure init. ***/
+
+ /* 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. Pointer
+ owned by the introduction point object through which we received the
+ INTRO2 cell*/
+ const curve25519_keypair_t *enc_kp;
+ /* 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;
+
+ /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/
+
+ /* 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;
+ /* Replay cache of the introduction point. */
+ replaycache_t *replay_cache;
+} 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,
+ 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
new file mode 100644
index 0000000000..d0265dc548
--- /dev/null
+++ b/src/or/hs_circuit.c
@@ -0,0 +1,1056 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.c
+ **/
+
+#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "policies.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "rephist.h"
+#include "router.h"
+
+#include "hs_cell.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "hs_ntor.h"
+#include "hs_service.h"
+
+/* Trunnel. */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+
+/* A circuit is about to become an e2e rendezvous circuit. Check
+ * <b>circ_purpose</b> and ensure that it's properly set. Return true iff
+ * circuit purpose is properly set, otherwise return false. */
+static int
+circuit_purpose_is_correct_for_rend(unsigned int circ_purpose,
+ int is_service_side)
+{
+ if (is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_warn(LD_BUG,
+ "HS e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ if (!is_service_side) {
+ if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ log_warn(LD_BUG,
+ "Client e2e circuit setup with wrong purpose (%d)", circ_purpose);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous
+ * circuit. Initialize the crypt path crypto using the output material from the
+ * ntor key exchange at <b>ntor_key_seed</b>.
+ *
+ * If <b>is_service_side</b> is set, we are the hidden service and the final
+ * hop of the rendezvous circuit is the client on the other side. */
+static crypt_path_t *
+create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len,
+ int is_service_side)
+{
+ uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN];
+ crypt_path_t *cpath = NULL;
+
+ /* Do the key expansion */
+ if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len,
+ keys, sizeof(keys)) < 0) {
+ goto err;
+ }
+
+ /* Setup the cpath */
+ cpath = tor_malloc_zero(sizeof(crypt_path_t));
+ cpath->magic = CRYPT_PATH_MAGIC;
+
+ if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys),
+ is_service_side, 1) < 0) {
+ tor_free(cpath);
+ goto err;
+ }
+
+ err:
+ memwipe(keys, 0, sizeof(keys));
+ return cpath;
+}
+
+/* We are a v2 legacy HS client: Create and return a crypt path for the hidden
+ * service on the other side of the rendezvous circuit <b>circ</b>. Initialize
+ * the crypt path crypto using the body of the RENDEZVOUS1 cell at
+ * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes).
+ */
+static crypt_path_t *
+create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body)
+{
+ crypt_path_t *hop = NULL;
+ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+
+ /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
+ * handshake...*/
+ tor_assert(circ->build_state);
+ tor_assert(circ->build_state->pending_final_cpath);
+ hop = circ->build_state->pending_final_cpath;
+
+ tor_assert(hop->rend_dh_handshake_state);
+ if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state,
+ (char*)rend_cell_body, DH_KEY_LEN,
+ keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
+ log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ goto err;
+ }
+ /* ... and set up cpath. */
+ if (circuit_init_cpath_crypto(hop,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 0, 0) < 0)
+ goto err;
+
+ /* Check whether the digest is right... */
+ if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) {
+ log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
+ goto err;
+ }
+
+ /* clean up the crypto stuff we just made */
+ crypto_dh_free(hop->rend_dh_handshake_state);
+ hop->rend_dh_handshake_state = NULL;
+
+ goto done;
+
+ err:
+ hop = NULL;
+
+ done:
+ memwipe(keys, 0, sizeof(keys));
+ return hop;
+}
+
+/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark
+ * <b>circ</b> ready for use to transfer HS relay cells. */
+static void
+finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop,
+ int is_service_side)
+{
+ tor_assert(circ);
+ tor_assert(hop);
+
+ /* Notify the circuit state machine that we are splicing this circuit */
+ int new_circ_purpose = is_service_side ?
+ CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED;
+ circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose);
+
+ /* All is well. Extend the circuit. */
+ hop->state = CPATH_STATE_OPEN;
+ /* Set the windows to default. */
+ hop->package_window = circuit_initial_package_window();
+ hop->deliver_window = CIRCWINDOW_START;
+
+ /* Now that this circuit has finished connecting to its destination,
+ * make sure circuit_get_open_circ_or_launch is willing to return it
+ * so we can actually use it. */
+ circ->hs_circ_has_timed_out = 0;
+
+ /* Append the hop to the cpath of this circuit */
+ onion_append_to_cpath(&circ->cpath, hop);
+
+ /* In legacy code, 'pending_final_cpath' points to the final hop we just
+ * appended to the cpath. We set the original pointer to NULL so that we
+ * don't double free it. */
+ if (circ->build_state) {
+ circ->build_state->pending_final_cpath = NULL;
+ }
+
+ /* Finally, mark circuit as ready to be used for client streams */
+ if (!is_service_side) {
+ circuit_try_attaching_streams(circ);
+ }
+}
+
+/* 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);
+ }
+}
+
+/* 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) {
+ const circuit_t *circ;
+ const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip);
+ 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,
+ &ocirc->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, 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,
+ 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 *
+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 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));
+}
+
+/* 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'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)
+{
+ 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 are mandatory for rend points.
+ * ed25519 keys and ipv6 are optional for rend points */
+ if (!have_v4 || !have_legacy_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,
+ have_ed25519_id ? &ed25519_pk : NULL,
+ 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);
+
+ /* 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);
+}
+
+/* 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. */
+
+ 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 */
+/* ========== */
+
+/* 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.
+ *
+ * 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)
+{
+ 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. */
+ if (circ->hs_ident) {
+ retry_service_rendezvous_point(circ);
+ } else {
+ 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
+ * 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)
+{
+ /* 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 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;
+}
+
+/* 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);
+
+ /* 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) {
+ /* 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;
+}
+
+/* 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));
+}
+
+/* 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,
+ 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);
+
+ 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. */
+ 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;
+}
+
+/* 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,
+ hs_service_intro_point_t *ip,
+ const uint8_t *subcredential,
+ const uint8_t *payload, size_t payload_len)
+{
+ int ret = -1;
+ time_t elapsed;
+ 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();
+ data.replay_cache = ip->replay_cache;
+
+ if (hs_cell_parse_introduce2(&data, circ, service) < 0) {
+ 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++;
+
+ /* Launch rendezvous circuit with the onion key and rend cookie. */
+ launch_rendezvous_point_circuit(service, ip, &data);
+ /* 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 <b>circ</b> just finished the rend ntor key exchange. Use the key
+ * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to
+ * serve as a rendezvous end-to-end circuit between the client and the
+ * service. If <b>is_service_side</b> is set, then we are the hidden service
+ * and the other side is the client.
+ *
+ * Return 0 if the operation went well; in case of error return -1. */
+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)
+{
+ if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose,
+ is_service_side))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len,
+ is_service_side);
+ if (!hop) {
+ log_warn(LD_REND, "Couldn't get v3 %s cpath!",
+ is_service_side ? "service-side" : "client-side");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, is_service_side);
+
+ return 0;
+}
+
+/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell
+ * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then
+ * extend the crypt path of <b>circ</b> so that the hidden service is on the
+ * other side. */
+int
+hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body)
+{
+
+ if (BUG(!circuit_purpose_is_correct_for_rend(
+ TO_CIRCUIT(circ)->purpose, 0))) {
+ return -1;
+ }
+
+ crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body);
+ if (!hop) {
+ log_warn(LD_GENERAL, "Couldn't get v2 cpath.");
+ return -1;
+ }
+
+ finalize_rend_circuit(circ, hop, 0);
+
+ return 0;
+}
+
diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h
new file mode 100644
index 0000000000..9e359394e8
--- /dev/null
+++ b/src/or/hs_circuit.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuit.h
+ * \brief Header file containing circuit data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_CIRCUIT_H
+#define TOR_HS_CIRCUIT_H
+
+#include "or.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#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);
+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);
+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);
+
+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,
+ 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. */
+
+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);
+int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ,
+ const uint8_t *rend_cell_body);
+
+#endif /* TOR_HS_CIRCUIT_H */
+
diff --git a/src/or/hs_client.c b/src/or/hs_client.c
new file mode 100644
index 0000000000..051490aaaf
--- /dev/null
+++ b/src/or/hs_client.c
@@ -0,0 +1,48 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service client functionality
+ **/
+
+#include "or.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "connection_edge.h"
+#include "rendclient.h"
+
+#include "hs_client.h"
+
+/** A prop224 v3 HS circuit successfully connected to the hidden
+ * service. Update the stream state at <b>hs_conn_ident</b> appropriately. */
+static void
+hs_client_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident)
+{
+ (void) hs_conn_ident;
+
+ /* TODO: When implementing client side */
+ return;
+}
+
+/** A circuit just finished connecting to a hidden service that the stream
+ * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */
+void
+hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn)
+{
+ tor_assert(connection_edge_is_rendezvous_stream(conn));
+
+ if (BUG(conn->rend_data && conn->hs_ident)) {
+ log_warn(LD_BUG, "Stream had both rend_data and hs_ident..."
+ "Prioritizing hs_ident");
+ }
+
+ if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */
+ hs_client_attempt_succeeded(conn->hs_ident);
+ return;
+ } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */
+ rend_client_note_connection_attempt_ended(conn->rend_data);
+ return;
+ }
+}
+
diff --git a/src/or/hs_client.h b/src/or/hs_client.h
new file mode 100644
index 0000000000..4f28937b03
--- /dev/null
+++ b/src/or/hs_client.h
@@ -0,0 +1,16 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_client.h
+ * \brief Header file containing client data for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CLIENT_H
+#define TOR_HS_CLIENT_H
+
+void hs_client_note_connection_attempt_succeeded(
+ const edge_connection_t *conn);
+
+#endif /* TOR_HS_CLIENT_H */
+
diff --git a/src/or/hs_common.c b/src/or/hs_common.c
index 42508126f8..6c860b0cf0 100644
--- a/src/or/hs_common.c
+++ b/src/or/hs_common.c
@@ -15,8 +15,133 @@
#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"
+#include "shared_random_state.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)";
+
+#ifdef HAVE_SYS_UN_H
+
+/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
+ * add the given <b>p</b>, 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 <b>conn</b> set it to use the given port <b>p</b> 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
+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 *
+hs_path_from_filename(const char *directory, const char *filename)
+{
+ char *file_path = NULL;
+
+ tor_assert(directory);
+ tor_assert(filename);
+
+ tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename);
+ return file_path;
+}
/* Make sure that the directory for <b>service</b> is private, using the config
* <b>username</b>.
@@ -56,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,
@@ -67,17 +203,22 @@ get_time_period_length(void)
}
/** Get the HS time period number at time <b>now</b> */
-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;
+
+ /* 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;
@@ -89,7 +230,22 @@ 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;
+}
+
+/* Return the start time of the upcoming time period based on <b>now</b>. */
+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 (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset);
}
/* Create a new rend_data_t for a specific given <b>version</b>.
@@ -344,3 +500,870 @@ 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
+compute_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);
+
+ /* 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);
+
+ /* 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_length));
+ offset += sizeof(uint64_t);
+ set_uint64(period_stuff+offset, tor_htonll(time_period_num));
+ 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);
+}
+
+/** 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 <b>time_period_num</b> and put it
+ * in <b>srv_out</b> (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).
+ *
+ * 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 period_length,
+ 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;
+
+ 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(period_length) */
+ memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN);
+ offset += HS_KEYBLIND_NONCE_PREFIX_LEN;
+ set_uint64(nonce + offset, tor_htonll(period_num));
+ offset += sizeof(uint64_t);
+ set_uint64(nonce + offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ tor_assert(offset == HS_KEYBLIND_NONCE_LEN);
+
+ /* Generate the parameter h and the construction is as follow:
+ * 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) {
+ 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);
+
+ memwipe(nonce, 0, sizeof(nonce));
+}
+
+/* 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)
+ *
+ * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */
+static void
+build_hs_checksum(const ed25519_public_key_t *key, uint8_t version,
+ uint8_t *checksum_out)
+{
+ size_t offset = 0;
+ char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN];
+
+ /* Build checksum data. */
+ memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX,
+ HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN);
+ offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN;
+ memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ set_uint8(data + offset, version);
+ offset += sizeof(version);
+ tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN);
+
+ /* Hash the data payload to create the checksum. */
+ crypto_digest256((char *) checksum_out, data, sizeof(data),
+ DIGEST_SHA3_256);
+}
+
+/* Using an ed25519 public key, checksum and version to build the binary
+ * representation of a service address. Put in addr_out. Format is:
+ * addr_out = PUBKEY || CHECKSUM || VERSION
+ *
+ * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */
+static void
+build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum,
+ uint8_t version, char *addr_out)
+{
+ size_t offset = 0;
+
+ tor_assert(key);
+ tor_assert(checksum);
+
+ memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN);
+ offset += ED25519_PUBKEY_LEN;
+ memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ set_uint8(addr_out + offset, version);
+ offset += sizeof(uint8_t);
+ tor_assert(offset == HS_SERVICE_ADDR_LEN);
+}
+
+/* Helper for hs_parse_address(): Using a binary 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 bytes long. address must be at least
+ * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */
+static void
+hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ size_t offset = 0;
+
+ tor_assert(address);
+
+ if (key_out) {
+ /* First is the key. */
+ memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN);
+ }
+ offset += ED25519_PUBKEY_LEN;
+ if (checksum_out) {
+ /* Followed by a 2 bytes checksum. */
+ memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED);
+ }
+ offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED;
+ if (version_out) {
+ /* Finally, version value is 1 byte. */
+ *version_out = get_uint8(address + offset);
+ }
+ offset += sizeof(uint8_t);
+ /* Extra safety. */
+ 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 (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,
+ 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);
+
+ memwipe(credential, 0, sizeof(credential));
+}
+
+/* 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)
+{
+ 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
+ * bytes long.
+ *
+ * Return 0 if parsing went well; return -1 in case of error. */
+int
+hs_parse_address(const char *address, ed25519_public_key_t *key_out,
+ uint8_t *checksum_out, uint8_t *version_out)
+{
+ char decoded[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(address);
+
+ /* Obvious length check. */
+ if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) {
+ log_warn(LD_REND, "Service address %s has an invalid length. "
+ "Expected %lu but got %lu.",
+ escaped_safe_str(address),
+ (unsigned long) HS_SERVICE_ADDR_LEN_BASE32,
+ (unsigned long) strlen(address));
+ goto invalid;
+ }
+
+ /* Decode address so we can extract needed fields. */
+ if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) {
+ log_warn(LD_REND, "Service address %s can't be decoded.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Parse the decoded address into the fields we need. */
+ hs_parse_address_impl(decoded, key_out, checksum_out, version_out);
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Validate a given onion address. The length, the base32 decoding and
+ * checksum are validated. Return 1 if valid else 0. */
+int
+hs_address_is_valid(const char *address)
+{
+ uint8_t version;
+ uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED];
+ uint8_t target_checksum[DIGEST256_LEN];
+ ed25519_public_key_t key;
+
+ /* Parse the decoded address into the fields we need. */
+ if (hs_parse_address(address, &key, checksum, &version) < 0) {
+ goto invalid;
+ }
+
+ /* Get the checksum it's suppose to be and compare it with what we have
+ * encoded in the address. */
+ build_hs_checksum(&key, version, target_checksum);
+ if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) {
+ log_warn(LD_REND, "Service address %s invalid checksum.",
+ escaped_safe_str(address));
+ goto invalid;
+ }
+
+ /* Valid address. */
+ return 1;
+ invalid:
+ return 0;
+}
+
+/* Build a service address using an ed25519 public key and a given version.
+ * The returned address is base32 encoded and put in addr_out. The caller MUST
+ * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long.
+ *
+ * Format is as follow:
+ * base32(PUBKEY || CHECKSUM || VERSION)
+ * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION)
+ * */
+void
+hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out)
+{
+ uint8_t checksum[DIGEST256_LEN];
+ char address[HS_SERVICE_ADDR_LEN];
+
+ tor_assert(key);
+ tor_assert(addr_out);
+
+ /* Get the checksum of the address. */
+ build_hs_checksum(key, version, checksum);
+ /* Get the binary address representation. */
+ build_hs_address(key, checksum, version, address);
+
+ /* Encode the address. addr_out will be NUL terminated after this. */
+ base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address,
+ sizeof(address));
+ /* Validate what we just built. */
+ 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;
+}
+
+/* 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);
+
+ memwipe(param, 0, sizeof(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);
+
+ memwipe(param, 0, sizeof(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. */
+MOCK_IMPL(int,
+hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now))
+{
+ time_t valid_after;
+ time_t srv_start_time, tp_start_time;
+
+ if (!consensus) {
+ consensus = networkstatus_get_live_consensus(now);
+ if (!consensus) {
+ return 0;
+ }
+ }
+
+ /* 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);
+
+ if (valid_after >= srv_start_time && valid_after < tp_start_time) {
+ 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;
+}
+
+/* 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_length) | 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);
+
+ /* 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+offset, tor_htonll(period_length));
+ offset += sizeof(uint64_t);
+ set_uint64(buf+offset, 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);
+}
+
+/* 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_length) | 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);
+
+ {
+ 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);
+}
+
+/* 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. If a consensus is provided in <b>ns</b>, use it to get the SRV
+ * value. This function can't fail. */
+uint8_t *
+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(ns);
+
+ 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, const networkstatus_t *ns)
+{
+ uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN);
+ const sr_srv_t *previous_srv = sr_get_previous(ns);
+
+ 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;
+}
+
+/* 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);
+}
+
+/** <b>node</b> 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
+ * 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 (!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);
+ }
+ } 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
+hs_init(void)
+{
+ hs_circuitmap_init();
+ hs_service_init();
+ hs_cache_init();
+}
+
+/* Release and cleanup all memory of the HS subsystem (all version). This is
+ * called by tor_free_all(). */
+void
+hs_free_all(void)
+{
+ hs_circuitmap_free_all();
+ hs_service_free_all();
+ 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 a8fded652a..fd2a1f4e32 100644
--- a/src/or/hs_common.h
+++ b/src/or/hs_common.h
@@ -11,15 +11,21 @@
#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
/* Version 3 of the protocol (prop224). */
#define HS_VERSION_THREE 3
+/* Earliest and latest version we support. */
+#define HS_VERSION_MIN HS_VERSION_TWO
+#define HS_VERSION_MAX HS_VERSION_THREE
/** Try to maintain this many intro points per service by default. */
#define NUM_INTRO_POINTS_DEFAULT 3
-/** Maximum number of intro points per service. */
+/** Maximum number of intro points per generic and version 2 service. */
#define NUM_INTRO_POINTS_MAX 10
/** Number of extra intro points we launch if our set of intro nodes is empty.
* See proposal 155, section 4. */
@@ -46,12 +52,124 @@
#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 */
+
+/* Prefix of the onion address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum"
+/* Length of the checksum prefix minus the NUL terminated byte. */
+#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \
+ (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1)
+/* Length of the resulting checksum of the address. The construction of this
+ * checksum looks like:
+ * CHECKSUM = ".onion checksum" || PUBKEY || VERSION
+ * where VERSION is 1 byte. This is pre-hashing. */
+#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \
+ (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t))
+/* The amount of bytes we use from the address checksum. */
+#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2
+/* Length of the binary encoded service address which is of course before the
+ * base32 encoding. Construction is:
+ * PUBKEY || CHECKSUM || VERSION
+ * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */
+#define HS_SERVICE_ADDR_LEN \
+ (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t))
+/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the
+ * length ends up to 56 bytes (not counting the terminated NUL byte.) */
+#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))
+
+/* 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)
+
+/* 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)
+
+/* 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,
+ 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;
+
+/* 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);
+
int hs_check_service_private_dir(const char *username, const char *path,
unsigned int dir_group_readable,
unsigned int create);
+char *hs_path_from_filename(const char *directory, const char *filename);
+void hs_build_address(const ed25519_public_key_t *key, uint8_t version,
+ char *addr_out);
+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);
+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);
@@ -69,14 +187,54 @@ 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);
+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);
+
+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,
+ 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,
+ 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);
+
+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);
+
+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);
+
#ifdef TOR_UNIT_TESTS
STATIC uint64_t get_time_period_length(void);
-STATIC uint64_t get_time_period_num(time_t now);
+
+STATIC uint8_t *get_first_cached_disaster_srv(void);
+STATIC uint8_t *get_second_cached_disaster_srv(void);
#endif /* TOR_UNIT_TESTS */
diff --git a/src/or/hs_config.c b/src/or/hs_config.c
new file mode 100644
index 0000000000..5f9282ea79
--- /dev/null
+++ b/src/or/hs_config.c
@@ -0,0 +1,582 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.c
+ * \brief Implement hidden service configuration subsystem.
+ *
+ * \details
+ *
+ * This file has basically one main entry point: hs_config_service_all(). It
+ * takes the torrc options and configure hidden service from it. In validate
+ * mode, nothing is added to the global service list or keys are not generated
+ * nor loaded.
+ *
+ * A service is configured in two steps. It is first created using the tor
+ * options and then put in a staging list. It will stay there until
+ * hs_service_load_all_keys() is called. That function is responsible to
+ * load/generate the keys for the service in the staging list and if
+ * successful, transfert the service to the main global service list where
+ * at that point it is ready to be used.
+ *
+ * Configuration functions are per-version and there is a main generic one for
+ * every option that is common to all version (config_generic_service).
+ **/
+
+#define HS_CONFIG_PRIVATE
+
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_service.h"
+#include "rendservice.h"
+
+/* Using the given list of services, stage them into our global state. Every
+ * service version are handled. This function can remove entries in the given
+ * service_list.
+ *
+ * Staging a service means that we take all services in service_list and we
+ * put them in the staging list (global) which acts as a temporary list that
+ * is used by the service loading key process. In other words, staging a
+ * service puts it in a list to be considered when loading the keys and then
+ * moved to the main global list. */
+static void
+stage_services(smartlist_t *service_list)
+{
+ tor_assert(service_list);
+
+ /* This is v2 specific. Trigger service pruning which will make sure the
+ * just configured services end up in the main global list. It should only
+ * be done in non validation mode because v2 subsystem handles service
+ * object differently. */
+ rend_service_prune_list();
+
+ /* Cleanup v2 service from the list, we don't need those object anymore
+ * because we validated them all against the others and we want to stage
+ * only >= v3 service. And remember, v2 has a different object type which is
+ * shadow copied from an hs_service_t type. */
+ SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) {
+ if (s->config.version == HS_VERSION_TWO) {
+ SMARTLIST_DEL_CURRENT(service_list, s);
+ hs_service_free(s);
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ /* This is >= v3 specific. Using the newly configured service list, stage
+ * them into our global state. Every object ownership is lost after. */
+ hs_service_stage_services(service_list);
+}
+
+/* Validate the given service against all service in the given list. If the
+ * service is ephemeral, this function ignores it. Services with the same
+ * directory path aren't allowed and will return an error. If a duplicate is
+ * found, 1 is returned else 0 if none found. */
+static int
+service_is_duplicate_in_list(const smartlist_t *service_list,
+ const hs_service_t *service)
+{
+ int ret = 0;
+
+ tor_assert(service_list);
+ tor_assert(service);
+
+ /* Ephemeral service don't have a directory configured so no need to check
+ * for a service in the list having the same path. */
+ if (service->config.is_ephemeral) {
+ goto end;
+ }
+
+ /* XXX: Validate if we have any service that has the given service dir path.
+ * This has two problems:
+ *
+ * a) It's O(n^2), but the same comment from the bottom of
+ * rend_config_services() should apply.
+ *
+ * b) We only compare directory paths as strings, so we can't
+ * detect two distinct paths that specify the same directory
+ * (which can arise from symlinks, case-insensitivity, bind
+ * mounts, etc.).
+ *
+ * It also can't detect that two separate Tor instances are trying
+ * to use the same HiddenServiceDir; for that, we would need a
+ * lock file. But this is enough to detect a simple mistake that
+ * at least one person has actually made. */
+ SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) {
+ if (!strcmp(s->config.directory_path, service->config.directory_path)) {
+ log_warn(LD_REND, "Another hidden service is already configured "
+ "for directory %s",
+ escaped(service->config.directory_path));
+ ret = 1;
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ end:
+ return ret;
+}
+
+/* Helper function: Given an configuration option name, its value, a minimum
+ * min and a maxium max, parse the value as a uint64_t. On success, ok is set
+ * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be
+ * ignored. This function logs both on error and success. */
+static uint64_t
+helper_parse_uint64(const char *opt, const char *value, uint64_t min,
+ uint64_t max, int *ok)
+{
+ uint64_t ret = 0;
+
+ tor_assert(opt);
+ tor_assert(value);
+ tor_assert(ok);
+
+ *ok = 0;
+ ret = tor_parse_uint64(value, 10, min, max, ok, NULL);
+ if (!*ok) {
+ log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64
+ ", not %s.",
+ opt, min, max, value);
+ goto err;
+ }
+ log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret);
+ err:
+ return ret;
+}
+
+/* Return true iff the given options starting at line_ for a hidden service
+ * contains at least one invalid option. Each hidden service option don't
+ * apply to all versions so this function can find out. The line_ MUST start
+ * right after the HiddenServiceDir line of this service.
+ *
+ * This is mainly for usability so we can inform the user of any invalid
+ * option for the hidden service version instead of silently ignoring. */
+static int
+config_has_invalid_options(const config_line_t *line_,
+ const hs_service_t *service)
+{
+ int ret = 0;
+ const char **optlist;
+ const config_line_t *line;
+
+ tor_assert(service);
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+
+ /* List of options that a v3 service doesn't support thus must exclude from
+ * its configuration. */
+ const char *opts_exclude_v3[] = {
+ "HiddenServiceAuthorizeClient",
+ NULL /* End marker. */
+ };
+
+ /* Defining the size explicitly allows us to take advantage of the compiler
+ * which warns us if we ever bump the max version but forget to grow this
+ * array. The plus one is because we have a version 0 :). */
+ struct {
+ const char **list;
+ } exclude_lists[HS_VERSION_MAX + 1] = {
+ { NULL }, /* v0. */
+ { NULL }, /* v1. */
+ { NULL }, /* v2 */
+ { opts_exclude_v3 }, /* v3. */
+ };
+
+ optlist = exclude_lists[service->config.version].list;
+ if (optlist == NULL) {
+ /* No exclude options to look at for this version. */
+ goto end;
+ }
+ for (int i = 0; optlist[i]; i++) {
+ const char *opt = optlist[i];
+ for (line = line_; line; line = line->next) {
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ goto end;
+ }
+ if (!strcasecmp(line->key, opt)) {
+ log_warn(LD_CONFIG, "Hidden service option %s is incompatible with "
+ "version %" PRIu32 " of service in %s",
+ opt, service->config.version,
+ service->config.directory_path);
+ ret = 1;
+ /* Continue the loop so we can find all possible options. */
+ continue;
+ }
+ }
+ }
+ end:
+ return ret;
+}
+
+/* Validate service configuration. This is used when loading the configuration
+ * and once we've setup a service object, it's config object is passed to this
+ * function for further validation. This does not validate service key
+ * material. Return 0 if valid else -1 if invalid. */
+static int
+config_validate_service(const hs_service_config_t *config)
+{
+ tor_assert(config);
+
+ /* Amount of ports validation. */
+ if (!config->ports || smartlist_len(config->ports) == 0) {
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
+ escaped(config->directory_path));
+ goto invalid;
+ }
+
+ /* Valid. */
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* Configuration funcion for a version 3 service. The line_ must be pointing
+ * to the directive directly after a HiddenServiceDir. That way, when hitting
+ * the next HiddenServiceDir line or reaching the end of the list of lines, we
+ * know that we have to stop looking for more options. The given service
+ * object must be already allocated and passed through
+ * config_generic_service() prior to calling this function.
+ *
+ * Return 0 on success else a negative value. */
+static int
+config_service_v3(const config_line_t *line_,
+ hs_service_config_t *config)
+{
+ int have_num_ip = 0;
+ const char *dup_opt_seen = NULL;
+ const config_line_t *line;
+
+ tor_assert(config);
+
+ for (line = line_; line; line = line->next) {
+ int ok = 0;
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* We just hit the next hidden service, stop right now. */
+ break;
+ }
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ config->num_intro_points =
+ (unsigned int) helper_parse_uint64(line->key, line->value,
+ NUM_INTRO_POINTS_DEFAULT,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
+ &ok);
+ if (!ok || have_num_ip) {
+ if (have_num_ip)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_num_ip = 1;
+ continue;
+ }
+ }
+
+ /* We do not load the key material for the service at this stage. This is
+ * done later once tor can confirm that it is in a running state. */
+
+ /* We are about to return a fully configured service so do one last pass of
+ * validation at it. */
+ if (config_validate_service(config) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given options in line_ and options. This is
+ * called for any service regardless of its version which means that all
+ * directives in this function are generic to any service version. This
+ * function will also check the validity of the service directory path.
+ *
+ * The line_ must be pointing to the directive directly after a
+ * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or
+ * reaching the end of the list of lines, we know that we have to stop looking
+ * for more options.
+ *
+ * Return 0 on success else -1. */
+static int
+config_generic_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_t *service)
+{
+ int dir_seen = 0;
+ const config_line_t *line;
+ hs_service_config_t *config;
+ /* If this is set, we've seen a duplicate of this option. Keep the string
+ * so we can log the directive. */
+ const char *dup_opt_seen = NULL;
+ /* These variables will tell us if we ever have duplicate. */
+ int have_version = 0, have_allow_unknown_ports = 0;
+ int have_dir_group_read = 0, have_max_streams = 0;
+ int have_max_streams_close = 0;
+
+ tor_assert(line_);
+ tor_assert(options);
+ tor_assert(service);
+
+ /* Makes thing easier. */
+ config = &service->config;
+
+ /* The first line starts with HiddenServiceDir so we consider what's next is
+ * the configuration of the service. */
+ for (line = line_; line ; line = line->next) {
+ int ok = 0;
+
+ /* This indicate that we have a new service to configure. */
+ if (!strcasecmp(line->key, "HiddenServiceDir")) {
+ /* This function only configures one service at a time so if we've
+ * already seen one, stop right now. */
+ if (dir_seen) {
+ break;
+ }
+ /* Ok, we've seen one and we are about to configure it. */
+ dir_seen = 1;
+ config->directory_path = tor_strdup(line->value);
+ log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...",
+ escaped(config->directory_path));
+ continue;
+ }
+ if (BUG(!dir_seen)) {
+ goto err;
+ }
+ /* Version of the service. */
+ if (!strcasecmp(line->key, "HiddenServiceVersion")) {
+ service->config.version =
+ (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN,
+ HS_VERSION_MAX, &ok);
+ if (!ok || have_version) {
+ if (have_version)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_version = 1;
+ continue;
+ }
+ /* Virtual port. */
+ if (!strcasecmp(line->key, "HiddenServicePort")) {
+ char *err_msg = NULL;
+ /* XXX: Can we rename this? */
+ rend_service_port_config_t *portcfg =
+ rend_service_parse_port_config(line->value, " ", &err_msg);
+ if (!portcfg) {
+ if (err_msg) {
+ log_warn(LD_CONFIG, "%s", err_msg);
+ }
+ tor_free(err_msg);
+ goto err;
+ }
+ tor_assert(!err_msg);
+ smartlist_add(config->ports, portcfg);
+ log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
+ line->value, escaped(config->directory_path));
+ continue;
+ }
+ /* Do we allow unknown ports. */
+ if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
+ config->allow_unknown_ports =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_allow_unknown_ports) {
+ if (have_allow_unknown_ports)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_allow_unknown_ports = 1;
+ continue;
+ }
+ /* Directory group readable. */
+ if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) {
+ config->dir_group_readable =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_dir_group_read) {
+ if (have_dir_group_read)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_dir_group_read = 1;
+ continue;
+ }
+ /* Maximum streams per circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
+ config->max_streams_per_rdv_circuit =
+ helper_parse_uint64(line->key, line->value, 0,
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok);
+ if (!ok || have_max_streams) {
+ if (have_max_streams)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams = 1;
+ continue;
+ }
+ /* Maximum amount of streams before we close the circuit. */
+ if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
+ config->max_streams_close_circuit =
+ (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok);
+ if (!ok || have_max_streams_close) {
+ if (have_max_streams_close)
+ dup_opt_seen = line->key;
+ goto err;
+ }
+ have_max_streams_close = 1;
+ continue;
+ }
+ }
+
+ /* Check if we are configured in non anonymous mode and single hop mode
+ * meaning every service become single onion. */
+ if (rend_service_allow_non_anonymous_connection(options) &&
+ rend_service_non_anonymous_mode_enabled(options)) {
+ config->is_single_onion = 1;
+ }
+
+ /* Success */
+ return 0;
+ err:
+ if (dup_opt_seen) {
+ log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen);
+ }
+ return -1;
+}
+
+/* Configure a service using the given line and options. This function will
+ * call the corresponding configuration function for a specific service
+ * version and validate the service against the other ones. On success, add
+ * the service to the given list and return 0. On error, nothing is added to
+ * the list and a negative value is returned. */
+static int
+config_service(const config_line_t *line, const or_options_t *options,
+ smartlist_t *service_list)
+{
+ int ret;
+ hs_service_t *service = NULL;
+
+ tor_assert(line);
+ tor_assert(options);
+ tor_assert(service_list);
+
+ /* We have a new hidden service. */
+ service = hs_service_new(options);
+ /* We'll configure that service as a generic one and then pass it to a
+ * specific function according to the configured version number. */
+ if (config_generic_service(line, options, service) < 0) {
+ goto err;
+ }
+ tor_assert(service->config.version <= HS_VERSION_MAX);
+ /* Before we configure the service on a per-version basis, we'll make
+ * sure that this set of options for a service are valid that is for
+ * instance an option only for v2 is not used for v3. */
+ if (config_has_invalid_options(line->next, service)) {
+ goto err;
+ }
+ /* Check permission on service directory that was just parsed. And this must
+ * be done regardless of the service version. Do not ask for the directory
+ * to be created, this is done when the keys are loaded because we could be
+ * in validation mode right now. */
+ if (hs_check_service_private_dir(options->User,
+ service->config.directory_path,
+ service->config.dir_group_readable,
+ 0) < 0) {
+ goto err;
+ }
+ /* Different functions are in charge of specific options for a version. We
+ * start just after the service directory line so once we hit another
+ * directory line, the function knows that it has to stop parsing. */
+ switch (service->config.version) {
+ case HS_VERSION_TWO:
+ ret = rend_config_service(line->next, options, &service->config);
+ break;
+ case HS_VERSION_THREE:
+ ret = config_service_v3(line->next, &service->config);
+ break;
+ default:
+ /* We do validate before if we support the parsed version. */
+ tor_assert_nonfatal_unreached();
+ goto err;
+ }
+ if (ret < 0) {
+ goto err;
+ }
+ /* We'll check if this service can be kept depending on the others
+ * configured previously. */
+ if (service_is_duplicate_in_list(service_list, service)) {
+ goto err;
+ }
+ /* Passes, add it to the given list. */
+ smartlist_add(service_list, service);
+ return 0;
+
+ err:
+ hs_service_free(service);
+ return -1;
+}
+
+/* From a set of <b>options</b>, setup every hidden service found. Return 0 on
+ * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and
+ * return as normal, but don't actually change the configured services. */
+int
+hs_config_service_all(const or_options_t *options, int validate_only)
+{
+ int dir_option_seen = 0, ret = -1;
+ const config_line_t *line;
+ smartlist_t *new_service_list = NULL;
+
+ tor_assert(options);
+
+ /* Newly configured service are put in that list which is then used for
+ * validation and staging for >= v3. */
+ new_service_list = smartlist_new();
+
+ for (line = options->RendConfigLines; line; line = line->next) {
+ /* Ignore all directives that aren't the start of a service. */
+ if (strcasecmp(line->key, "HiddenServiceDir")) {
+ if (!dir_option_seen) {
+ log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
+ line->key);
+ goto err;
+ }
+ continue;
+ }
+ /* Flag that we've seen a directory directive and we'll use it to make
+ * sure that the torrc options ordering is actually valid. */
+ dir_option_seen = 1;
+
+ /* Try to configure this service now. On success, it will be added to the
+ * list and validated against the service in that same list. */
+ if (config_service(line, options, new_service_list) < 0) {
+ goto err;
+ }
+ }
+
+ /* In non validation mode, we'll stage those services we just successfully
+ * configured. Service ownership is transfered from the list to the global
+ * state. If any service is invalid, it will be removed from the list and
+ * freed. All versions are handled in that function. */
+ if (!validate_only) {
+ stage_services(new_service_list);
+ } else {
+ /* We've just validated that we were able to build a clean working list of
+ * services. We don't need those objects anymore. */
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s,
+ hs_service_free(s));
+ /* For the v2 subsystem, the configuration function adds the service
+ * object to the staging list and it is transferred in the main list
+ * through the prunning process. In validation mode, we thus have to purge
+ * the staging list so it's not kept in memory as valid service. */
+ rend_service_free_staging_list();
+ }
+
+ /* Success. Note that the service list has no ownership of its content. */
+ ret = 0;
+ goto end;
+
+ err:
+ SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s));
+
+ end:
+ smartlist_free(new_service_list);
+ /* Tor main should call the free all function on error. */
+ return ret;
+}
+
diff --git a/src/or/hs_config.h b/src/or/hs_config.h
new file mode 100644
index 0000000000..2f8cbdc130
--- /dev/null
+++ b/src/or/hs_config.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_config.h
+ * \brief Header file containing configuration ABI/API for the HS subsytem.
+ **/
+
+#ifndef TOR_HS_CONFIG_H
+#define TOR_HS_CONFIG_H
+
+#include "or.h"
+
+/* Max value for HiddenServiceMaxStreams */
+#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535
+/* Maximum number of intro points per version 3 services. */
+#define HS_CONFIG_V3_MAX_INTRO_POINTS 20
+
+/* API */
+
+int hs_config_service_all(const or_options_t *options, int validate_only);
+
+#endif /* TOR_HS_CONFIG_H */
+
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
index 2a000f5002..9a1e377155 100644
--- a/src/or/hs_descriptor.c
+++ b/src/or/hs_descriptor.c
@@ -58,10 +58,12 @@
#include "hs_descriptor.h"
#include "or.h"
+#include "circuitbuild.h"
#include "ed25519_cert.h" /* Trunnel interface. */
#include "parsecommon.h"
#include "rendcache.h"
#include "hs_cache.h"
+#include "hs_config.h"
#include "torcert.h" /* tor_cert_encode_ed22519() */
/* Constant string value used for the descriptor format. */
@@ -77,6 +79,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"
@@ -135,6 +138,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),
@@ -143,29 +147,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)
@@ -196,7 +177,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));
@@ -255,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);
}
@@ -382,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);
}
@@ -478,6 +467,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 *
@@ -497,6 +506,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;
@@ -987,6 +1006,10 @@ desc_encode_v3(const hs_descriptor_t *desc,
tor_assert(encoded_out);
tor_assert(desc->plaintext_data.version == 3);
+ if (BUG(desc->subcredential == NULL)) {
+ goto err;
+ }
+
/* Build the non-encrypted values. */
{
char *encoded_cert;
@@ -1133,6 +1156,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;
}
@@ -1625,6 +1657,50 @@ decode_intro_legacy_key(const directory_token_t *tok,
return -1;
}
+/* Dig into the descriptor <b>tokens</b> to find the onion key we should use
+ * for this intro point, and set it into <b>onion_key_out</b>. 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
@@ -1650,17 +1726,24 @@ 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");
goto err;
}
+ /* "onion-key" SP ntor SP key NL */
+ if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) {
+ goto err;
+ }
+
/* "auth-key" NL certificate NL */
tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
tor_assert(tok->object_body);
@@ -1732,7 +1815,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:
@@ -1747,18 +1830,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start)
/* Given a descriptor string at <b>data</b>, decode all possible introduction
* points that we can find. Add the introduction point object to desc_enc as we
- * find them. Return 0 on success.
- *
- * On error, a negative value is returned. It is possible that some intro
- * point object have been added to the desc_enc, they should be considered
- * invalid. One single bad encoded introduction point will make this function
- * return an error. */
-STATIC int
+ * find them. This function can't fail and it is possible that zero
+ * introduction points can be decoded. */
+static void
decode_intro_points(const hs_descriptor_t *desc,
hs_desc_encrypted_data_t *desc_enc,
const char *data)
{
- int retval = -1;
smartlist_t *chunked_desc = smartlist_new();
smartlist_t *intro_points = smartlist_new();
@@ -1799,22 +1877,19 @@ decode_intro_points(const hs_descriptor_t *desc,
SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) {
hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point);
if (!ip) {
- /* Malformed introduction point section. Stop right away, this
- * descriptor shouldn't be used. */
- goto err;
+ /* Malformed introduction point section. We'll ignore this introduction
+ * point and continue parsing. New or unknown fields are possible for
+ * forward compatibility. */
+ continue;
}
smartlist_add(desc_enc->intro_points, ip);
} SMARTLIST_FOREACH_END(intro_point);
done:
- retval = 0;
-
- err:
SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a));
smartlist_free(chunked_desc);
SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a));
smartlist_free(intro_points);
- return retval;
}
/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded
* descriptor in encoded_desc validates the descriptor content. */
@@ -2040,14 +2115,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc,
/* Initialize the descriptor's introduction point list before we start
* decoding. Having 0 intro point is valid. Then decode them all. */
desc_encrypted_out->intro_points = smartlist_new();
- if (decode_intro_points(desc, desc_encrypted_out, message) < 0) {
- goto err;
- }
+ decode_intro_points(desc, desc_encrypted_out, message);
+
/* Validation of maximum introduction points allowed. */
- if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) {
+ if (smartlist_len(desc_encrypted_out->intro_points) >
+ HS_CONFIG_V3_MAX_INTRO_POINTS) {
log_warn(LD_REND, "Service descriptor contains too many introduction "
"points. Maximum allowed is %d but we have %d",
- MAX_INTRO_POINTS,
+ HS_CONFIG_V3_MAX_INTRO_POINTS,
smartlist_len(desc_encrypted_out->intro_points));
goto err;
}
@@ -2222,7 +2297,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);
@@ -2230,10 +2305,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;
@@ -2359,3 +2437,110 @@ 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, hs_desc_link_specifier_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);
+}
+
+/* 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:
+ /* 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:
+ /* ed25519 keys are optional for intro points */
+ 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;
+ 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_clear_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 b8b94792de..fa211d3917 100644
--- a/src/or/hs_descriptor.h
+++ b/src/or/hs_descriptor.h
@@ -23,12 +23,15 @@
/* 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)
/* 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
@@ -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;
@@ -80,6 +85,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. */
@@ -197,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_clear_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);
@@ -211,6 +225,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. */
@@ -223,16 +240,12 @@ STATIC smartlist_t *decode_link_specifiers(const char *encoded);
STATIC hs_desc_intro_point_t *decode_introduction_point(
const hs_descriptor_t *desc,
const char *text);
-STATIC int decode_intro_points(const hs_descriptor_t *desc,
- hs_desc_encrypted_data_t *desc_enc,
- const char *data);
STATIC int encrypted_data_length_is_valid(size_t len);
STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type,
const char *log_obj_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/or/hs_ident.c b/src/or/hs_ident.c
new file mode 100644
index 0000000000..e69350d82e
--- /dev/null
+++ b/src/or/hs_ident.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.c
+ * \brief Contains circuit and connection identifier code for the whole HS
+ * subsytem.
+ **/
+
+#include "hs_ident.h"
+
+/* Return a newly allocated circuit identifier. The given public key is copied
+ * identity_pk into the identifier. */
+hs_ident_circuit_t *
+hs_ident_circuit_new(const ed25519_public_key_t *identity_pk,
+ hs_ident_circuit_type_t circuit_type)
+{
+ tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO ||
+ circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS);
+ hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ ident->circuit_type = circuit_type;
+ return ident;
+}
+
+/* Free the given circuit identifier. */
+void
+hs_ident_circuit_free(hs_ident_circuit_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_circuit_t));
+ 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 *
+hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src)
+{
+ hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ memcpy(ident, src, sizeof(*ident));
+ return ident;
+}
+
+/* Free the given directory connection identifier. */
+void
+hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_dir_conn_t));
+ tor_free(ident);
+}
+
+/* Return a newly allocated edge connection identifier. The given public key
+ * identity_pk is copied into the identifier. */
+hs_ident_edge_conn_t *
+hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk)
+{
+ hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident));
+ ed25519_pubkey_copy(&ident->identity_pk, identity_pk);
+ return ident;
+}
+
+/* Free the given edge connection identifier. */
+void
+hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident)
+{
+ if (ident == NULL) {
+ return;
+ }
+ memwipe(ident, 0, sizeof(hs_ident_edge_conn_t));
+ tor_free(ident);
+}
+
diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h
new file mode 100644
index 0000000000..e259fde54d
--- /dev/null
+++ b/src/or/hs_ident.h
@@ -0,0 +1,130 @@
+/* Copyright (c) 2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_ident.h
+ * \brief Header file containing circuit and connection identifier data for
+ * the whole HS subsytem.
+ *
+ * \details
+ * This interface is used to uniquely identify a hidden service on a circuit
+ * or connection using the service identity public key. Once the circuit or
+ * connection subsystem calls in the hidden service one, we use those
+ * identifiers to lookup the corresponding objects like service, intro point
+ * and descriptor.
+ *
+ * Furthermore, the circuit identifier holds cryptographic material needed for
+ * the e2e encryption on the rendezvous circuit which is set once the
+ * rendezvous circuit has opened and ready to be used.
+ **/
+
+#ifndef TOR_HS_IDENT_H
+#define TOR_HS_IDENT_H
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+#include "hs_common.h"
+
+/* Length of the rendezvous cookie that is used to connect circuits at the
+ * rendezvous point. */
+#define HS_REND_COOKIE_LEN DIGEST_LEN
+
+/* Type of circuit an hs_ident_t object is associated with. */
+typedef enum {
+ HS_IDENT_CIRCUIT_INTRO = 1,
+ HS_IDENT_CIRCUIT_RENDEZVOUS = 2,
+} hs_ident_circuit_type_t;
+
+/* Client and service side circuit identifier that is used for hidden service
+ * circuit establishment. Not all fields contain data, it depends on the
+ * circuit purpose. This is attached to an origin_circuit_t. All fields are
+ * used by both client and service. */
+typedef struct hs_ident_circuit_t {
+ /* (All circuit) The public key used to uniquely identify the service. It is
+ * the one found in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* (All circuit) The type of circuit this identifier is attached to.
+ * Accessors of the fields in this object assert non fatal on this circuit
+ * type. In other words, if a rendezvous field is being accessed, the
+ * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is
+ * set when an object is initialized in its constructor. */
+ 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. 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
+ * 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 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];
+
+ /* (Only rendezvous circuit) Number of streams associated with this
+ * rendezvous circuit. We track this because there is a check on a maximum
+ * value. */
+ uint64_t num_rdv_streams;
+} hs_ident_circuit_t;
+
+/* Client and service side directory connection identifier used for a
+ * directory connection to identify which service is being queried. This is
+ * attached to a dir_connection_t. */
+typedef struct hs_ident_dir_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_dir_conn_t;
+
+/* Client and service side edge connection identifier used for an edge
+ * connection to identify which service is being queried. This is attached to
+ * a edge_connection_t. */
+typedef struct hs_ident_edge_conn_t {
+ /* The public key used to uniquely identify the service. It is the one found
+ * in the onion address. */
+ ed25519_public_key_t identity_pk;
+
+ /* XXX: Client authorization. */
+} hs_ident_edge_conn_t;
+
+/* Circuit identifier API. */
+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);
+void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident);
+
+/* Edge connection identifier API. */
+hs_ident_edge_conn_t *hs_ident_edge_conn_new(
+ const ed25519_public_key_t *identity_pk);
+void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident);
+
+#endif /* TOR_HS_IDENT_H */
+
diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c
index 636b345b5b..cb4d6c02e5 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"
@@ -591,3 +592,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
return -1;
}
+/* Clear memory allocated by the given intropoint object ip (but don't free the
+ * object itself). */
+void
+hs_intropoint_clear(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);
+ memset(ip, 0, sizeof(hs_intropoint_t));
+}
+
diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h
index 163ed810e7..5c77f07ec3 100644
--- a/src/or/hs_intropoint.h
+++ b/src/or/hs_intropoint.h
@@ -9,12 +9,15 @@
#ifndef TOR_HS_INTRO_H
#define TOR_HS_INTRO_H
+#include "crypto_curve25519.h"
+#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 {
@@ -24,6 +27,18 @@ typedef enum {
HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003,
} hs_intro_ack_status_t;
+/* 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. */
+ smartlist_t *link_specifiers;
+} hs_intropoint_t;
+
int hs_intro_received_establish_intro(or_circuit_t *circ,
const uint8_t *request,
size_t request_len);
@@ -35,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_intropoint_clear(hs_intropoint_t *ip);
+
#ifdef HS_INTROPOINT_PRIVATE
#include "hs/cell_establish_intro.h"
diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c
index 119899817e..a416bc46c3 100644
--- a/src/or/hs_ntor.c
+++ b/src/or/hs_ntor.c
@@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good(
/* Input length to KDF for key expansion */
#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN)
-/* Output length of KDF for key expansion */
-#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2)
-
-/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the
- * circuit key expansion as specified by section '4.2.1. Key expansion' and
- * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */
-hs_ntor_rend_circuit_keys_t *
-hs_ntor_circuit_key_expansion(
- const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys)
+
+/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size
+ * DIGEST256_LEN), do the circuit key expansion as specified by section
+ * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be
+ * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN).
+ *
+ * Return 0 if things went well, else return -1. */
+int
+hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len)
{
uint8_t *ptr;
uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN];
- uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN];
crypto_xof_t *xof;
- hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL;
+
+ /* Sanity checks on lengths to make sure we are good */
+ if (BUG(seed_len != DIGEST256_LEN)) {
+ return -1;
+ }
+ if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) {
+ return -1;
+ }
/* Let's build the input to the KDF */
ptr = kdf_input;
- APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN);
+ APPEND(ptr, ntor_key_seed, DIGEST256_LEN);
APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND));
tor_assert(ptr == kdf_input + sizeof(kdf_input));
/* Generate the keys */
xof = crypto_xof_new();
crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input));
- crypto_xof_squeeze_bytes(xof, keys, sizeof(keys));
+ crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN);
crypto_xof_free(xof);
- /* Generate keys structure and assign keys to it */
- rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t));
- ptr = keys;
- memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;;
- memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;
- memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN);
- ptr += DIGEST256_LEN;
- memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN);
- ptr += CIPHER256_KEY_LEN;
- memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN);
- ptr += CIPHER256_KEY_LEN;
- tor_assert(ptr == keys + sizeof(keys));
-
- return rend_circuit_keys;
+ return 0;
}
diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h
index cd75f46a4c..d07bff8cf0 100644
--- a/src/or/hs_ntor.h
+++ b/src/or/hs_ntor.h
@@ -6,6 +6,10 @@
#include "or.h"
+/* Output length of KDF for key expansion */
+#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \
+ (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2)
+
/* Key material needed to encode/decode INTRODUCE1 cells */
typedef struct {
/* Key used for encryption of encrypted INTRODUCE1 blob */
@@ -23,21 +27,6 @@ typedef struct {
uint8_t ntor_key_seed[DIGEST256_LEN];
} hs_ntor_rend_cell_keys_t;
-/* Key material resulting from key expansion as detailed in section "4.2.1. Key
- * expansion" of rend-spec-ng.txt. */
-typedef struct {
- /* Per-circuit key material used in ESTABLISH_INTRO cell */
- uint8_t KH[DIGEST256_LEN];
- /* Authentication key for outgoing RELAY cells */
- uint8_t Df[DIGEST256_LEN];
- /* Authentication key for incoming RELAY cells */
- uint8_t Db[DIGEST256_LEN];
- /* Encryption key for outgoing RELAY cells */
- uint8_t Kf[CIPHER256_KEY_LEN];
- /* Decryption key for incoming RELAY cells */
- uint8_t Kb[CIPHER256_KEY_LEN];
-} hs_ntor_rend_circuit_keys_t;
-
int hs_ntor_client_get_introduce1_keys(
const ed25519_public_key_t *intro_auth_pubkey,
const curve25519_public_key_t *intro_enc_pubkey,
@@ -66,8 +55,9 @@ int hs_ntor_service_get_rendezvous1_keys(
const curve25519_public_key_t *client_ephemeral_enc_pubkey,
hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out);
-hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion(
- const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys);
+int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed,
+ size_t seed_len,
+ uint8_t *keys_out, size_t keys_out_len);
int hs_ntor_client_rendezvous2_mac_is_good(
const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys,
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 205ef11c92..cb3a49cb8a 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -6,169 +6,3082 @@
* \brief Implement next generation hidden service functionality
**/
+#define HS_SERVICE_PRIVATE
+
#include "or.h"
+#include "circpathbias.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "directory.h"
+#include "main.h"
+#include "networkstatus.h"
+#include "nodelist.h"
#include "relay.h"
#include "rendservice.h"
-#include "circuitlist.h"
-#include "circpathbias.h"
+#include "router.h"
+#include "routerkeys.h"
+#include "routerlist.h"
+#include "statefile.h"
+#include "hs_circuit.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"
-#include "hs_common.h"
-#include "hs/cell_establish_intro.h"
+/* Trunnel */
+#include "ed25519_cert.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. */
+#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 ;
+
+/* 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";
+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
+ * map once the keys have been loaded. These two steps are seperated because
+ * loading keys requires that we are an actual running tor process. */
+static smartlist_t *hs_service_staging_list;
-/* 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. */
+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
+hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second)
+{
+ tor_assert(first);
+ tor_assert(second);
+ /* Simple key compare. */
+ return ed25519_pubkey_eq(&first->keys.identity_pk,
+ &second->keys.identity_pk);
+}
+
+/* Helper: Function for the service hash table code below. The key used is the
+ * master public identity key which is ultimately the onion address. */
+static inline unsigned int
+hs_service_ht_hash(const hs_service_t *service)
+{
+ tor_assert(service);
+ return (unsigned int) siphash24g(service->keys.identity_pk.pubkey,
+ sizeof(service->keys.identity_pk.pubkey));
+}
+
+/* This is _the_ global hash map of hidden services which indexed the service
+ * contained in it by master public identity key which is roughly the onion
+ * address of the service. */
+static struct hs_service_ht *hs_service_map;
+
+/* Register the service hash table. */
+HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */
+ hs_service_t, /* Object contained in the map. */
+ hs_service_node, /* The name of the HT_ENTRY member. */
+ hs_service_ht_hash, /* Hashing function. */
+ hs_service_ht_eq) /* Compare function for objects. */
+
+HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node,
+ hs_service_ht_hash, hs_service_ht_eq,
+ 0.6, tor_reallocarray, tor_free_)
+
+/* Query the given service map with a public key and return a service object
+ * if found else NULL. It is also possible to set a directory path in the
+ * search query. If pk is NULL, then it will be set to zero indicating the
+ * hash table to compare the directory path instead. */
+STATIC hs_service_t *
+find_service(hs_service_ht *map, const ed25519_public_key_t *pk)
+{
+ hs_service_t dummy_service;
+ tor_assert(map);
+ tor_assert(pk);
+ memset(&dummy_service, 0, sizeof(dummy_service));
+ ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk);
+ return HT_FIND(hs_service_ht, map, &dummy_service);
+}
-/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in
- * <b>buf_out</b> which has size <b>buf_out_len</b>. 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)
+/* Register the given service in the given map. If the service already exists
+ * in the map, -1 is returned. On success, 0 is returned and the service
+ * ownership has been transfered to the global map. */
+STATIC int
+register_service(hs_service_ht *map, hs_service_t *service)
{
- ssize_t bytes_used = 0;
+ tor_assert(map);
+ tor_assert(service);
+ tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk));
- if (buf_out_len < RELAY_PAYLOAD_SIZE) {
+ if (find_service(map, &service->keys.identity_pk)) {
+ /* Existing service with the same key. Do not register it. */
return -1;
}
+ /* Taking ownership of the object at this point. */
+ HT_INSERT(hs_service_ht, map, service);
+ return 0;
+}
+
+/* Remove a given service from the given map. If service is NULL or the
+ * service key is unset, return gracefully. */
+STATIC void
+remove_service(hs_service_ht *map, hs_service_t *service)
+{
+ hs_service_t *elm;
+
+ tor_assert(map);
+
+ /* Ignore if no service or key is zero. */
+ if (BUG(service == NULL) ||
+ BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) {
+ return;
+ }
- bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len,
- cell);
- return bytes_used;
+ elm = HT_REMOVE(hs_service_ht, map, service);
+ if (elm) {
+ tor_assert(elm == service);
+ } else {
+ log_warn(LD_BUG, "Could not find service in the global map "
+ "while removing service %s",
+ escaped(service->config.directory_path));
+ }
+}
+
+/* Set the default values for a service configuration object <b>c</b>. */
+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->max_streams_per_rdv_circuit = 0;
+ c->max_streams_close_circuit = 0;
+ c->num_intro_points = NUM_INTRO_POINTS_DEFAULT;
+ c->allow_unknown_ports = 0;
+ c->is_single_onion = 0;
+ c->dir_group_readable = 0;
+ c->is_ephemeral = 0;
}
-/* Set the cell extensions of <b>cell</b>. */
+/* From a service configuration object config, clear everything from it
+ * meaning free allocated pointers and reset the values. */
static void
-set_trn_cell_extensions(trn_cell_establish_intro_t *cell)
+service_clear_config(hs_service_config_t *config)
+{
+ if (config == NULL) {
+ return;
+ }
+ tor_free(config->directory_path);
+ if (config->ports) {
+ SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p,
+ rend_service_port_config_free(p););
+ smartlist_free(config->ports);
+ }
+ memset(config, 0, sizeof(*config));
+}
+
+/* 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)
{
- trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new();
+ /* 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);
+}
- /* 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);
+/* 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)
+{
+ /* 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);
}
-/** Given the circuit handshake info in <b>circuit_key_material</b>, 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)
+/* 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)
{
- trn_cell_establish_intro_t *cell = NULL;
- ssize_t encoded_len;
+#define MIN_INTRO_POINT_LIFETIME_TESTING 10
+ if (get_options()->TestingTorNetwork) {
+ return MIN_INTRO_POINT_LIFETIME_TESTING;
+ }
- log_warn(LD_GENERAL,
- "Generating ESTABLISH_INTRO cell (key_material_len: %u)",
- (unsigned) circuit_key_material_len);
+ /* 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);
+}
- /* Generate short-term keypair for use in ESTABLISH_INTRO */
- ed25519_keypair_t key_struct;
- if (ed25519_keypair_generate(&key_struct, 0) < 0) {
- goto err;
+/* 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)
+{
+#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",
+ INTRO_POINT_LIFETIME_MAX_SECONDS,
+ 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
+ht_free_service_(struct hs_service_t *service, void *data)
+{
+ (void) data;
+ hs_service_free(service);
+ /* This function MUST return 1 so the given object is then removed from the
+ * service map leading to this free of the object being safe. */
+ return 1;
+}
+
+/* Free every service that can be found in the global map. Once done, clear
+ * and free the global map. */
+static void
+service_free_all(void)
+{
+ if (hs_service_map) {
+ /* The free helper function returns 1 so this is safe. */
+ hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL);
+ HT_CLEAR(hs_service_ht, hs_service_map);
+ tor_free(hs_service_map);
+ hs_service_map = NULL;
}
- cell = trn_cell_establish_intro_new();
+ if (hs_service_staging_list) {
+ /* Cleanup staging list. */
+ SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s,
+ hs_service_free(s));
+ smartlist_free(hs_service_staging_list);
+ hs_service_staging_list = NULL;
+ }
+}
- /* Set AUTH_KEY_TYPE: 2 means ed25519 */
- trn_cell_establish_intro_set_auth_key_type(cell,
- HS_INTRO_AUTH_KEY_TYPE_ED25519);
+/* 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_intropoint_clear(&ip->base);
+ tor_free(ip);
+}
- /* 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);
+/* 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);
+}
- /* 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);
+/* 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;
- /* No cell extensions needed */
- set_trn_cell_extensions(cell);
+ 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);
- /* 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);
+ { /* 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);
+ }
- /* XXX How to make this process easier and nicer? */
+ ip->replay_cache = replaycache_new(0, 0);
- /* 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.");
+ /* 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. */
+ 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. */
+ if (BUG(!ls)) {
+ goto err;
+ }
+ smartlist_add(ip->base.link_specifiers, ls);
+
+ /* 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);
+ }
+
+ /* 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)
+{
+ hs_service_intro_point_t *old_ip_entry;
+
+ tor_assert(map);
+ tor_assert(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'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)
+{
+ 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 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) {
+ 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 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.
+ *
+ * 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);
+ 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);
+}
+
+/* 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 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)
+{
+ origin_circuit_t *ocirc = NULL;
+
+ tor_assert(service);
+
+ /* The reason we go over all circuit instead of using the circuitmap API is
+ * because most hidden service circuits are rendezvous circuits so there is
+ * no real improvement at getting all rendezvous circuits from the
+ * circuitmap and then going over them all to find the right ones.
+ * Furthermore, another option would have been to keep a list of RP cookies
+ * for a service but it creates an engineering complexity since we don't
+ * have a "RP circuit closed" event to clean it up properly so we avoid a
+ * memory DoS possibility. */
+
+ while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) {
+ /* Only close circuits that are v3 and for this service. */
+ if (ocirc->hs_ident != NULL &&
+ ed25519_pubkey_eq(&ocirc->hs_ident->identity_pk,
+ &service->keys.identity_pk)) {
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. When freed, it is removed from the
+ * hs circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ }
+}
+
+/* Close the circuit(s) for the given map of introduction points. */
+static void
+close_intro_circuits(hs_service_intropoints_t *intro_points)
+{
+ tor_assert(intro_points);
+
+ DIGEST256MAP_FOREACH(intro_points->map, key,
+ const hs_service_intro_point_t *, 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
+ * from the HS circuitmap. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ } DIGEST256MAP_FOREACH_END;
+}
- /* sanity check */
- tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN);
+/* Close all introduction circuits for the given service. */
+static void
+close_service_intro_circuits(hs_service_t *service)
+{
+ tor_assert(service);
- /* 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));
+ FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+ close_intro_circuits(&desc->intro_points);
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/* Close any circuits related to the given service. */
+static void
+close_service_circuits(hs_service_t *service)
+{
+ tor_assert(service);
+
+ /* Only support for version >= 3. */
+ if (BUG(service->config.version < HS_VERSION_THREE)) {
+ return;
+ }
+ /* Close intro points. */
+ close_service_intro_circuits(service);
+ /* Close rendezvous points. */
+ close_service_rp_circuits(service);
+}
+
+/* Move introduction points from the src descriptor to the dst descriptor. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_descriptor_intro_points(hs_service_descriptor_t *src,
+ hs_service_descriptor_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ 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;
+}
+
+/* Move introduction points from the src service to the dst service. The
+ * destination service intropoints are wiped out if any before moving. */
+static void
+move_intro_points(hs_service_t *src, hs_service_t *dst)
+{
+ tor_assert(src);
+ tor_assert(dst);
+
+ if (src->desc_current && dst->desc_current) {
+ move_descriptor_intro_points(src->desc_current, dst->desc_current);
}
+ if (src->desc_next && dst->desc_next) {
+ move_descriptor_intro_points(src->desc_next, dst->desc_next);
+ }
+}
+
+/* Move every ephemeral services from the src service map to the dst service
+ * map. It is possible that a service can't be register to the dst map which
+ * won't stop the process of moving them all but will trigger a log warn. */
+static void
+move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst)
+{
+ hs_service_t **iter, **next;
+
+ tor_assert(src);
+ tor_assert(dst);
- /* Calculate the cell signature */
+ /* Iterate over the map to find ephemeral service and move them to the other
+ * map. We loop using this method to have a safe removal process. */
+ for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) {
+ hs_service_t *s = *iter;
+ if (!s->config.is_ephemeral) {
+ /* Yeah, we are in a very manual loop :). */
+ next = HT_NEXT(hs_service_ht, src, iter);
+ continue;
+ }
+ /* Remove service from map and then register to it to the other map.
+ * Reminder that "*iter" and "s" are the same thing. */
+ next = HT_NEXT_RMV(hs_service_ht, src, iter);
+ if (register_service(dst, s) < 0) {
+ log_warn(LD_BUG, "Ephemeral service key is already being used. "
+ "Skipping.");
+ }
+ }
+}
+
+/* Return a const string of the directory path escaped. If this is an
+ * ephemeral service, it returns "[EPHEMERAL]". This can only be called from
+ * the main thread because escaped() uses a static variable. */
+static const char *
+service_escaped_dir(const hs_service_t *s)
+{
+ return (s->config.is_ephemeral) ? "[EPHEMERAL]" :
+ escaped(s->config.directory_path);
+}
+
+/* Register services that are in the staging list. Once this function returns,
+ * the global service map will be set with the right content and all non
+ * surviving services will be cleaned up. */
+static void
+register_all_services(void)
+{
+ struct hs_service_ht *new_service_map;
+
+ tor_assert(hs_service_staging_list);
+
+ /* We'll save us some allocation and computing time. */
+ if (smartlist_len(hs_service_staging_list) == 0) {
+ return;
+ }
+
+ /* Allocate a new map that will replace the current one. */
+ new_service_map = tor_malloc_zero(sizeof(*new_service_map));
+ HT_INIT(hs_service_ht, new_service_map);
+
+ /* First step is to transfer all ephemeral services from the current global
+ * map to the new one we are constructing. We do not prune ephemeral
+ * services as the only way to kill them is by deleting it from the control
+ * port or stopping the tor daemon. */
+ 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);
+ if (s) {
+ /* Pass ownership of intro points from s (the current service) to snew
+ * (the newly configured one). */
+ move_intro_points(s, snew);
+ /* Remove the service from the global map because after this, we need to
+ * go over the remaining service in that map that aren't surviving the
+ * reload to close their circuits. */
+ remove_service(hs_service_map, s);
+ }
+ /* Great, this service is now ready to be added to our new map. */
+ if (BUG(register_service(new_service_map, snew) < 0)) {
+ /* This should never happen because prior to registration, we validate
+ * every service against the entire set. Not being able to register a
+ * service means we failed to validate correctly. In that case, don't
+ * break tor and ignore the service but tell user. */
+ log_warn(LD_BUG, "Unable to register service with directory %s",
+ service_escaped_dir(snew));
+ SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew);
+ hs_service_free(snew);
+ }
+ } SMARTLIST_FOREACH_END(snew);
+
+ /* Close any circuits associated with the non surviving services. Every
+ * service in the current global map are roaming. */
+ 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. */
+ smartlist_clear(hs_service_staging_list);
+ service_free_all();
+ hs_service_map = new_service_map;
+}
+
+/* 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
+write_address_to_file(const hs_service_t *service, const char *fname_)
+{
+ int ret = -1;
+ char *fname = NULL;
+ 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_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, addr_buf, 0) < 0) {
+ log_warn(LD_REND, "Could not write onion address to hostname file %s",
+ escaped(fname));
+ goto end;
+ }
+
+#ifndef _WIN32
+ if (service->config.dir_group_readable) {
+ /* Mode to 0640. */
+ if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) {
+ log_warn(LD_FS, "Unable to make onion service hostname file %s "
+ "group-readable.", escaped(fname));
+ }
+ }
+#endif /* _WIN32 */
+
+ /* Success. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ tor_free(addr_buf);
+ return ret;
+}
+
+/* Load and/or generate private keys for the given service. On success, the
+ * hostname file will be written to disk along with the master private key iff
+ * the service is not configured for offline keys. Return 0 on success else -1
+ * on failure. */
+static int
+load_service_keys(hs_service_t *service)
+{
+ int ret = -1;
+ char *fname = NULL;
+ ed25519_keypair_t *kp;
+ const hs_service_config_t *config;
+
+ tor_assert(service);
+
+ config = &service->config;
+
+ /* Create and fix permission on service directory. We are about to write
+ * files to that directory so make sure it exists and has the right
+ * permissions. We do this here because at this stage we know that Tor is
+ * actually running and the service we have has been validated. */
+ if (BUG(hs_check_service_private_dir(get_options()->User,
+ config->directory_path,
+ config->dir_group_readable, 1) < 0)) {
+ goto end;
+ }
+
+ /* Try to load the keys from file or generate it if not found. */
+ fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix);
+ /* Don't ask for key creation, we want to know if we were able to load it or
+ * we had to generate it. Better logging! */
+ kp = ed_key_init_from_file(fname, 0, LOG_INFO, NULL, 0, 0, 0, NULL);
+ if (!kp) {
+ log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname);
+ /* We'll now try to generate the keys and for it we want the strongest
+ * randomness for it. The keypair will be written in different files. */
+ uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG |
+ INIT_ED_KEY_SPLIT;
+ kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0,
+ NULL);
+ if (!kp) {
+ log_warn(LD_REND, "Unable to generate keys and save in %s.", fname);
+ goto end;
+ }
+ }
+
+ /* Copy loaded or generated keys to service object. */
+ ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey);
+ memcpy(&service->keys.identity_sk, &kp->seckey,
+ sizeof(service->keys.identity_sk));
+ /* This does a proper memory wipe. */
+ ed25519_keypair_free(kp);
+
+ /* Build onion address from the newly loaded keys. */
+ tor_assert(service->config.version <= UINT8_MAX);
+ hs_build_address(&service->keys.identity_pk,
+ (uint8_t) service->config.version,
+ service->onion_address);
+
+ /* Write onion address to hostname file. */
+ if (write_address_to_file(service, fname_hostname) < 0) {
+ goto end;
+ }
+
+ /* Succes. */
+ ret = 0;
+ end:
+ tor_free(fname);
+ 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));
+ 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_);
+ digestmap_free(desc->intro_points.failed_id, tor_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();
+ 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,
+ 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. */
{
- /* 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;
+ 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;
- tor_assert(encoded_len > ED25519_SIG_LEN);
+ done:
+ return ret;
+}
- 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;
+/* 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_clear_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;
+}
+
+/* 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;
- /* 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);
+ 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);
- /* We are done! Return the cell! */
- return cell;
+ 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) {
+ 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;
+ }
+
+ /* 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. */
+ 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:
- trn_cell_establish_intro_free(cell);
+ 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;
+ }
+
+ /* 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) {
+ 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 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) {
+ num_needed_ip += get_intro_point_num_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) {
+ 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. */
+ setup_intro_point_exclude_list(desc, exclude_nodes);
+
+ 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);
+ }
+ /* 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:
+ /* 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;
+ }
+ /* 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;
+ }
+ }
+}
+
+/* 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 = 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
+ * 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 ||
+ 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);
+ 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 && !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);
+ }
+ continue;
+ }
+ } DIGEST256MAP_FOREACH_END;
+ } FOR_EACH_DESCRIPTOR_END;
+}
+
+/** We just entered overlap period and we need to rotate our <b>service</b>
+ * descriptors */
+static void
+rotate_service_descriptors(hs_service_t *service)
+{
+ if (service->desc_current) {
+ /* Close all IP circuits for the descriptor. */
+ close_intro_circuits(&service->desc_current->intro_points);
+ /* We don't need this one anymore, we won't serve any clients coming with
+ * this service descriptor. */
+ service_descriptor_free(service->desc_current);
+ }
+ /* The next one become the current one and emptying the next will trigger
+ * a descriptor creation for it. */
+ service->desc_current = service->desc_next;
+ service->desc_next = NULL;
+}
+
+/* Rotate descriptors for each service if needed. If we are just entering
+ * the overlap period, rotate them that is point the previous descriptor to
+ * the current and cleanup the previous one. A non existing current
+ * descriptor will trigger a descriptor build for the next time period. */
+STATIC void
+rotate_all_descriptors(time_t now)
+{
+ /* 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)) {
+ 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)
+{
+ /* 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);
+
+ /* 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
+ * 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
+ * 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)
+{
+ /* For v2 services, this step happens in the upload event. */
+
+ /* Run v3+ events. */
+ /* 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);
+}
+
+/* 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)
+{
+ 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 (hs_circ_service_get_intro_circ(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) < 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);
+
+/* 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
+ * 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 + 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
+ * 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 or able to build
+ * internal circuits as required by services. */
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN ||
+ !have_completed_a_circuit()) {
+ return;
+ }
+
+ /* Run v2 check. */
+ if (rend_num_services() > 0) {
+ rend_consider_services_intro_points(now);
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(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);
+ /* Once the circuits have opened, we'll make sure to update the
+ * descriptor intro point list and cleanup any extraneous. */
+ }
+ } 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/<version>/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_upload_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;
+}
+
+/** Return a newly-allocated string for our state file which contains revision
+ * counter information for <b>desc</b>. The format is:
+ *
+ * HidServRevCounter <blinded_pubkey> <rev_counter>
+ */
+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: <blinded key> <rev counter> */
+ 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 <b>state_line</b> for the revision counter of the service
+ * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the
+ * line is relevant to this service, and return the cached revision
+ * counter. Else set <b>service_found_out</b> 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 <b>blinded_pubkey</b>. 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 <b>hs_desc</b> 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 <b>hs_desc</b> 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 <b>hs_desc</b>, 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
+ * 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);
+ }
+
+ /* Update the revision counter of this descriptor */
+ increment_descriptor_revision_counter(desc->desc);
+
+ 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. */
+ 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
+ * 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
+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 (rend_num_services() > 0) {
+ rend_consider_services_upload(now);
+ rend_consider_descriptor_republication();
+ }
+
+ /* Run v3+ check. */
+ FOR_EACH_SERVICE_BEGIN(service) {
+ 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;
+}
+
+/* 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)
+{
+ hs_service_t *service = NULL;
+ hs_service_intro_point_t *ip = NULL;
+ hs_service_descriptor_t *desc = NULL;
+
+ tor_assert(circ);
+
+ /* 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);
+
+ 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;
+ }
+ if (ip == NULL) {
+ 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. */
+ 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), END_CIRC_REASON_NOSUCHSERVICE);
+ done:
+ 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);
+ tor_assert(circ->cpath);
+ /* 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);
+
+ /* 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;
+}
+
+/* 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,
+ 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);
+
+ /* 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. */
+ 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;
+ }
+ 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 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;
+ }
+
+ /* 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;
+}
+
+/* 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)
+{
+ 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);
+
+ /* 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;
+ }
+
+ return 0;
+ err:
+ 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_upload_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);
+}
+
+/* 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 */
+/* ========== */
+
+/* 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
+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.
+ *
+ * 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. */
+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. */
+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_upload_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
+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;
+ }
+
+ 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;
+}
+
+/* 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. */
+ 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;
+ }
+ 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
+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:
+ if (circ->hs_ident) {
+ service_intro_circ_has_opened(circ);
+ } else {
+ 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);
+ }
+ 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
+hs_service_load_all_keys(void)
+{
+ /* Load v2 service keys if we have v2. */
+ if (rend_num_services() != 0) {
+ if (rend_service_load_all_keys(NULL) < 0) {
+ goto err;
+ }
+ }
+
+ /* Load or/and generate them for v3+. */
+ SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) {
+ /* Ignore ephemeral service, they already have their keys set. */
+ if (service->config.is_ephemeral) {
+ continue;
+ }
+ log_info(LD_REND, "Loading v3 onion service keys from %s",
+ service_escaped_dir(service));
+ if (load_service_keys(service) < 0) {
+ goto err;
+ }
+ /* XXX: Load/Generate client authorization keys. (#20700) */
+ } SMARTLIST_FOREACH_END(service);
+
+ /* Final step, the staging list contains service in a quiescent state that
+ * is ready to be used. Register them to the global map. Once this is over,
+ * the staging list will be cleaned up. */
+ register_all_services();
+
+ /* All keys have been loaded successfully. */
+ return 0;
+ err:
+ return -1;
+}
+
+/* Put all service object in the given service list. After this, the caller
+ * looses ownership of every elements in the list and responsible to free the
+ * list pointer. */
+void
+hs_service_stage_services(const smartlist_t *service_list)
+{
+ tor_assert(service_list);
+ /* This list is freed at registration time but this function can be called
+ * multiple time. */
+ if (hs_service_staging_list == NULL) {
+ hs_service_staging_list = smartlist_new();
+ }
+ /* Add all service object to our staging list. Caller is responsible for
+ * freeing the service_list. */
+ smartlist_add_all(hs_service_staging_list, service_list);
+}
+
+/* Allocate and initilize a service object. The service configuration will
+ * contain the default values. Return the newly allocated object pointer. This
+ * function can't fail. */
+hs_service_t *
+hs_service_new(const or_options_t *options)
+{
+ hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t));
+ /* Set default configuration value. */
+ 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;
+}
+
+/* Free the given <b>service</b> object and all its content. This function
+ * also takes care of wiping service keys from memory. It is safe to pass a
+ * NULL pointer. */
+void
+hs_service_free(hs_service_t *service)
+{
+ if (service == NULL) {
+ return;
+ }
+
+ /* 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);
+
+ /* 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));
+
+ 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)
+{
+ /* Should never be called twice. */
+ tor_assert(!hs_service_map);
+ tor_assert(!hs_service_staging_list);
+
+ /* v2 specific. */
+ rend_service_init();
+
+ hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht));
+ HT_INIT(hs_service_ht, hs_service_map);
+
+ hs_service_staging_list = smartlist_new();
+}
+
+/* Release all global storage of the hidden service subsystem. */
+void
+hs_service_free_all(void)
+{
+ rend_service_free_all();
+ service_free_all();
+}
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global service map size. Only used by unit test. */
+STATIC unsigned int
+get_hs_service_map_size(void)
+{
+ return HT_SIZE(hs_service_map);
+}
+
+/* Return the staging list size. Only used by unit test. */
+STATIC int
+get_hs_service_staging_list_size(void)
+{
+ return smartlist_len(hs_service_staging_list);
+}
+
+STATIC hs_service_ht *
+get_hs_service_map(void)
+{
+ return hs_service_map;
+}
+
+STATIC hs_service_t *
+get_first_service(void)
+{
+ hs_service_t **obj = HT_START(hs_service_ht, hs_service_map);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return *obj;
+}
+
+#endif /* TOR_UNIT_TESTS */
+
diff --git a/src/or/hs_service.h b/src/or/hs_service.h
index 3302592762..8d613d23ed 100644
--- a/src/or/hs_service.h
+++ b/src/or/hs_service.h
@@ -3,25 +3,344 @@
/**
* \file hs_service.h
- * \brief Header file for hs_service.c.
+ * \brief Header file containing service data for the HS subsytem.
**/
#ifndef TOR_HS_SERVICE_H
#define TOR_HS_SERVICE_H
-#include "or.h"
+#include "crypto_curve25519.h"
+#include "crypto_ed25519.h"
+#include "replaycache.h"
+
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "hs_ident.h"
+#include "hs_intropoint.h"
+
+/* Trunnel */
#include "hs/cell_establish_intro.h"
-/* 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. */
+/* When loading and configuring a service, this is the default version it will
+ * be configured for as it is possible that no HiddenServiceVersion is
+ * 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. */
+ 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;
+
+ /* 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;
+
+ /* Maximum number of INTRODUCE2 cell this intro point should accept. */
+ uint64_t introduce2_max;
+
+ /* The time at which this intro point should expire and stop being used. */
+ time_t time_to_expire;
+
+ /* The amount of circuit creation we've made to this intro point. This is
+ * incremented every time we do a circuit relaunch on this intro point which
+ * is triggered when the circuit dies but the node is still in the
+ * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */
+ uint32_t circuit_retries;
+
+ /* Set if this intro point has an established circuit. */
+ unsigned int circuit_established : 1;
+
+ /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the
+ * circuit associated with this intro point has received. This is used to
+ * prevent replay attacks. */
+ replaycache_t *replay_cache;
+} hs_service_intro_point_t;
+
+/* Object handling introduction points of a service. */
+typedef struct hs_service_intropoints_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t retry_period_started;
+
+ /* Number of circuit we've launched during a single retry period. */
+ unsigned int num_circuits_launched;
+
+ /* 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. */
+typedef struct hs_service_descriptor_t {
+ /* Decoded descriptor. This object is used for encoding when the service
+ * publishes the descriptor. */
+ hs_descriptor_t *desc;
+
+ /* Descriptor signing keypair. */
+ ed25519_keypair_t signing_kp;
+
+ /* Blinded keypair derived from the master identity public key. */
+ ed25519_keypair_t blinded_kp;
+
+ /* When is the next time when we should upload the descriptor. */
+ time_t next_upload_time;
+
+ /* Introduction points assign to this descriptor which contains
+ * 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;
+
+ /* 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 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;
+
+/* Service key material. */
+typedef struct hs_service_keys_t {
+ /* Master identify public key. */
+ ed25519_public_key_t identity_pk;
+ /* Master identity private key. */
+ ed25519_secret_key_t identity_sk;
+ /* True iff the key is kept offline which means the identity_sk MUST not be
+ * used in that case. */
+ unsigned int is_identify_key_offline : 1;
+} hs_service_keys_t;
+
+/* Service configuration. The following are set from the torrc options either
+ * set by the configuration file or by the control port. Nothing else should
+ * change those values. */
+typedef struct hs_service_config_t {
+ /* Protocol version of the service. Specified by HiddenServiceVersion
+ * option. */
+ uint32_t version;
+
+ /* List of rend_service_port_config_t */
+ smartlist_t *ports;
+
+ /* Path on the filesystem where the service persistent data is stored. NULL
+ * if the service is ephemeral. Specified by HiddenServiceDir option. */
+ char *directory_path;
+
+ /* The maximum number of simultaneous streams per rendezvous circuit that
+ * are allowed to be created. No limit if 0. Specified by
+ * HiddenServiceMaxStreams option. */
+ uint64_t max_streams_per_rdv_circuit;
+
+ /* If true, we close circuits that exceed the max_streams_per_rdv_circuit
+ * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */
+ unsigned int max_streams_close_circuit : 1;
+
+ /* How many introduction points this service has. Specified by
+ * HiddenServiceNumIntroductionPoints option. */
+ unsigned int num_intro_points;
+
+ /* True iff we allow request made on unknown ports. Specified by
+ * HiddenServiceAllowUnknownPorts option. */
+ unsigned int allow_unknown_ports : 1;
+
+ /* If true, this service is a Single Onion Service. Specified by
+ * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */
+ unsigned int is_single_onion : 1;
+
+ /* If true, allow group read permissions on the directory_path. Specified by
+ * HiddenServiceDirGroupReadable option. */
+ unsigned int dir_group_readable : 1;
+
+ /* Is this service ephemeral? */
+ unsigned int is_ephemeral : 1;
+} hs_service_config_t;
+
+/* Service state. */
+typedef struct hs_service_state_t {
+ /* The time at which we've started our retry period to build circuits. We
+ * don't want to stress circuit creation so we can only retry for a certain
+ * time and then after we stop and wait. */
+ time_t intro_circ_retry_started_time;
+
+ /* Number of circuit we've launched during a single retry period. This
+ * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */
+ unsigned int num_intro_circ_launched;
+
+ /* 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. */
+typedef struct hs_service_t {
+ /* Onion address base32 encoded and NUL terminated. We keep it for logging
+ * purposes so we don't have to build it everytime. */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ /* Hashtable node: use to look up the service by its master public identity
+ * key in the service global map. */
+ HT_ENTRY(hs_service_t) hs_service_node;
+
+ /* Service state which contains various flags and counters. */
+ hs_service_state_t state;
+
+ /* Key material of the service. */
+ hs_service_keys_t keys;
+
+ /* Configuration of the service. */
+ hs_service_config_t config;
+
+ /* Current descriptor. */
+ hs_service_descriptor_t *desc_current;
+ /* Next descriptor that we need for the overlap period for which we have to
+ * keep two sets of opened introduction point circuits. */
+ hs_service_descriptor_t *desc_next;
+
+ /* XXX: Credential (client auth.) #20700. */
+
+} hs_service_t;
+
+/* For the service global hash map, we define a specific type for it which
+ * will make it safe to use and specific to some controlled parameters such as
+ * the hashing function and how to compare services. */
+typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht;
+
+/* API */
+
+/* Global initializer and cleanup function. */
+void hs_service_init(void);
+void hs_service_free_all(void);
+
+/* Service new/free functions. */
+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,
+ 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);
+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);
+
+void hs_service_intro_circ_has_closed(origin_circuit_t *circ);
+
+#ifdef HS_SERVICE_PRIVATE
+
+#ifdef TOR_UNIT_TESTS
+
+/* Useful getters for unit tests. */
+STATIC unsigned int get_hs_service_map_size(void);
+STATIC int get_hs_service_staging_list_size(void);
+STATIC hs_service_ht *get_hs_service_map(void);
+STATIC hs_service_t *get_first_service(void);
+
+/* Service accessors. */
+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);
+
+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,
+ 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 */
-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);
+#endif /* HS_SERVICE_PRIVATE */
#endif /* TOR_HS_SERVICE_H */
diff --git a/src/or/include.am b/src/or/include.am
index 1ef5afa013..8db5be095a 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -50,16 +50,21 @@ LIBTOR_A_SOURCES = \
src/or/dnsserv.c \
src/or/fp_pair.c \
src/or/geoip.c \
- src/or/hs_intropoint.c \
- src/or/hs_circuitmap.c \
- src/or/hs_ntor.c \
- src/or/hs_service.c \
src/or/entrynodes.c \
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 \
src/or/hs_common.c \
+ src/or/hs_config.c \
src/or/hs_descriptor.c \
+ src/or/hs_ident.c \
+ src/or/hs_intropoint.c \
+ src/or/hs_ntor.c \
+ src/or/hs_service.c \
src/or/keypin.c \
src/or/main.c \
src/or/microdesc.c \
@@ -180,12 +185,17 @@ 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_descriptor.h \
- src/or/hs_intropoint.h \
- src/or/hs_circuitmap.h \
- src/or/hs_ntor.h \
- src/or/hs_service.h \
+ src/or/hs_ident.h \
+ src/or/hs_intropoint.h \
+ src/or/hs_ntor.h \
+ src/or/hs_service.h \
src/or/keypin.h \
src/or/main.h \
src/or/microdesc.h \
diff --git a/src/or/main.c b/src/or/main.c
index cb24fd18c8..86fdb93282 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.
@@ -1713,7 +1709,7 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options)
* networkstatus_get_reasonably_live_consensus(), but that value is way
* way too high. Arma: is the bridge issue there resolved yet? -NM */
#define NS_EXPIRY_SLOP (24*60*60)
- if (ns && ns->valid_until < now+NS_EXPIRY_SLOP &&
+ if (ns && ns->valid_until < (now - NS_EXPIRY_SLOP) &&
router_have_minimum_dir_info()) {
router_dir_info_changed();
}
@@ -2060,6 +2056,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options)
/**
* Periodic callback: write the heartbeat message in the logs.
+ *
+ * If writing the heartbeat message to the logs fails for some reason, retry
+ * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds.
*/
static int
heartbeat_callback(time_t now, const or_options_t *options)
@@ -2071,14 +2070,20 @@ heartbeat_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
- /* Write the heartbeat message */
+ /* Skip the first one. */
if (first) {
- first = 0; /* Skip the first one. */
- } else {
- log_heartbeat(now);
+ first = 0;
+ return options->HeartbeatPeriod;
}
- return options->HeartbeatPeriod;
+ /* Write the heartbeat message */
+ if (log_heartbeat(now) == 0) {
+ return options->HeartbeatPeriod;
+ } else {
+ /* If we couldn't write the heartbeat log message, try again in the minimum
+ * interval of time. */
+ return MIN_HEARTBEAT_PERIOD;
+ }
}
#define CDM_CLEAN_CALLBACK_INTERVAL 600
@@ -2092,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. */
@@ -2355,7 +2382,7 @@ do_hup(void)
tor_free(msg);
}
}
- if (authdir_mode_handles_descs(options, -1)) {
+ if (authdir_mode(options)) {
/* reload the approved-routers file */
if (dirserv_load_fingerprint_file() < 0) {
/* warnings are logged from dirserv_load_fingerprint_file() directly */
@@ -2499,9 +2526,6 @@ do_main_loop(void)
}
}
- /* Initialize relay-side HS circuitmap */
- hs_circuitmap_init();
-
/* set up once-a-second callback. */
if (! second_timer) {
struct timeval one_second;
@@ -2877,7 +2901,6 @@ dumpstats(int severity)
rep_hist_dump_stats(now,severity);
rend_service_dump_stats(severity);
- dump_pk_ops(severity);
dump_distinct_digest_count(severity);
}
@@ -3014,9 +3037,10 @@ tor_init(int argc, char *argv[])
rep_hist_init();
/* Initialize the service cache. */
rend_cache_init();
- hs_cache_init();
addressmap_init(); /* Init the client dns cache. Do it always, since it's
* cheap. */
+ /* Initialize the HS subsystem. */
+ hs_init();
{
/* We search for the "quiet" option first, since it decides whether we
@@ -3216,10 +3240,8 @@ tor_free_all(int postfork)
networkstatus_free_all();
addressmap_free_all();
dirserv_free_all();
- rend_service_free_all();
rend_cache_free_all();
rend_service_authorization_free_all();
- hs_cache_free_all();
rep_hist_free_all();
dns_free_all();
clear_pending_onions();
@@ -3232,7 +3254,6 @@ tor_free_all(int postfork)
connection_edge_free_all();
scheduler_free_all();
nodelist_free_all();
- hs_circuitmap_free_all();
microdesc_free_all();
routerparse_free_all();
ext_orport_free_all();
@@ -3241,6 +3262,7 @@ tor_free_all(int postfork)
protover_free_all();
bridges_free_all();
consdiffmgr_free_all();
+ hs_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();
@@ -3478,7 +3500,7 @@ sandbox_init_filter(void)
if (options->BridgeAuthoritativeDir)
OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp");
- if (authdir_mode_handles_descs(options, -1))
+ if (authdir_mode(options))
OPEN_DATADIR("approved-routers");
if (options->ServerDNSResolvConfFile)
@@ -3550,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);
@@ -3559,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 */
@@ -3754,6 +3777,11 @@ tor_main(int argc, char *argv[])
case CMD_KEYGEN:
result = load_ed_keys(get_options(), time(NULL)) < 0;
break;
+ case CMD_KEY_EXPIRATION:
+ init_keys();
+ result = log_cert_expiration();
+ result = 0;
+ break;
case CMD_LIST_FINGERPRINT:
result = do_list_fingerprint();
break;
diff --git a/src/or/microdesc.c b/src/or/microdesc.c
index a4e6b409c4..18a6fbded7 100644
--- a/src/or/microdesc.c
+++ b/src/or/microdesc.c
@@ -876,7 +876,7 @@ update_microdesc_downloads(time_t now)
smartlist_free(missing);
}
-/** For every microdescriptor listed in the current microdecriptor consensus,
+/** For every microdescriptor listed in the current microdescriptor consensus,
* update its last_listed field to be at least as recent as the publication
* time of the current microdescriptor consensus.
*/
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 25d79139b9..69bff55cff 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -76,7 +76,7 @@ static strmap_t *unnamed_server_map = NULL;
* status. */
STATIC networkstatus_t *current_ns_consensus = NULL;
-/** Most recently received and validated v3 "microdec"-flavored consensus
+/** Most recently received and validated v3 "microdesc"-flavored consensus
* network status. */
STATIC networkstatus_t *current_md_consensus = NULL;
@@ -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 <b>ns</b>, return true iff 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 <b>consensus</b> is valid or expired recently enough that
* we can still use it.
*
@@ -1783,7 +1790,7 @@ networkstatus_set_current_consensus(const char *consensus,
if (from_cache && !was_waiting_for_certs) {
/* We previously stored this; check _now_ to make sure that version-kills
- * really work. This happens even before we check signatures: we did so
+ * really work. This happens even before we check signatures: we did so
* before when we stored this to disk. This does mean an attacker who can
* write to the datadir can make us not start: such an attacker could
* already harm us by replacing our guards, which would be worse. */
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 3ac5c3e302..0fcaea626d 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"
@@ -54,6 +55,7 @@
#include "rendservice.h"
#include "router.h"
#include "routerlist.h"
+#include "routerparse.h"
#include "routerset.h"
#include "torcert.h"
@@ -164,12 +166,78 @@ 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 <b>node</b> for the consensus <b>ns</b>, 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 = 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;
+
+ 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 "
+ "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 (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, 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, 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, ns);
+ }
+
+ /* 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);
+ } else {
+ memset(node->hsdir_index->next, 0, sizeof(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 +284,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 +322,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 +365,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 +495,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);
}
@@ -630,11 +716,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
if (! node->name_lookup_warned) {
base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN);
log_warn(LD_CONFIG,
- "You specified a server \"%s\" by name, but the directory "
- "authorities do not have any key registered for this "
- "nickname -- so it could be used by any server, not just "
- "the one you meant. "
- "To make sure you get the same server in the future, refer "
+ "You specified a relay \"%s\" by name, but nicknames can be "
+ "used by any relay, not just the one you meant. "
+ "To make sure you get the same relay in the future, refer "
"to it by key, as \"$%s\".", nickname, fp);
node->name_lookup_warned = 1;
}
@@ -707,6 +791,57 @@ node_supports_ed25519_link_authentication(const node_t *node)
return 0;
}
+/** Return true iff <b>node</b> supports the hidden service directory version
+ * 3 protocol (proposal 224). */
+int
+node_supports_v3_hsdir(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_v3_hsdir;
+ }
+ if (node->ri) {
+ 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) {
+ 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);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
+/** Return true iff <b>node</b> supports ed25519 authentication as an hidden
+ * service introduction point.*/
+int
+node_supports_ed25519_hs_intro(const node_t *node)
+{
+ tor_assert(node);
+
+ if (node->rs) {
+ return node->rs->supports_ed25519_hs_intro;
+ }
+ if (node->ri) {
+ if (node->ri->protocol_list == NULL) {
+ return 0;
+ }
+ return protocol_list_supports_protocol(node->ri->protocol_list,
+ PRT_HSINTRO, PROTOVER_HS_INTRO_V3);
+ }
+ tor_assert_nonfatal_unreached_once();
+ return 0;
+}
+
/** Return the RSA ID key's SHA1 digest for the provided node. */
const uint8_t *
node_get_rsa_id_digest(const node_t *node)
diff --git a/src/or/nodelist.h b/src/or/nodelist.h
index 95ae778a5b..405b79d820 100644
--- a/src/or/nodelist.h
+++ b/src/or/nodelist.h
@@ -58,6 +58,8 @@ const ed25519_public_key_t *node_get_ed25519_id(const node_t *node);
int node_ed25519_id_matches(const node_t *node,
const ed25519_public_key_t *id);
int node_supports_ed25519_link_authentication(const node_t *node);
+int node_supports_v3_hsdir(const node_t *node);
+int node_supports_ed25519_hs_intro(const node_t *node);
const uint8_t *node_get_rsa_id_digest(const node_t *node);
int node_has_ipv6_addr(const node_t *node);
diff --git a/src/or/ntmain.c b/src/or/ntmain.c
index d0d5276c48..7a85ba9708 100644
--- a/src/or/ntmain.c
+++ b/src/or/ntmain.c
@@ -329,9 +329,10 @@ nt_service_main(void)
case CMD_VERIFY_CONFIG:
case CMD_DUMP_CONFIG:
case CMD_KEYGEN:
+ case CMD_KEY_EXPIRATION:
log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, "
- "--hash-password, --keygen, --dump-config, or --verify-config) "
- "in NT service.");
+ "--hash-password, --keygen, --dump-config, --verify-config, "
+ "or --key-expiration) in NT service.");
break;
case CMD_RUN_UNITTESTS:
default:
diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c
index 294fc0df6d..c71fa236ed 100644
--- a/src/or/onion_tap.c
+++ b/src/or/onion_tap.c
@@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key,
if (crypto_dh_get_public(dh, challenge, dhbytes))
goto err;
- note_crypto_pk_op(ENC_ONIONSKIN);
-
/* set meeting point, meeting cookie, etc here. Leave zero for now. */
- if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out,
+ if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out,
TAP_ONIONSKIN_CHALLENGE_LEN,
challenge, DH_KEY_LEN,
PK_PKCS1_OAEP_PADDING, 1)<0)
@@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake(
k = i==0?private_key:prev_private_key;
if (!k)
break;
- note_crypto_pk_op(DEC_ONIONSKIN);
- len = crypto_pk_private_hybrid_decrypt(k, challenge,
+ len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge,
TAP_ONIONSKIN_CHALLENGE_LEN,
onion_skin,
TAP_ONIONSKIN_CHALLENGE_LEN,
diff --git a/src/or/or.h b/src/or/or.h
index 77207bc031..ff11c72790 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 connection to a hidden service directory: upload a v3 descriptor. */
+#define DIR_PURPOSE_UPLOAD_HSDESC 20
+/** A connection to a hidden service directory: fetch a v3 descriptor. */
+#define DIR_PURPOSE_FETCH_HSDESC 21
+#define DIR_PURPOSE_MAX_ 21
/** True iff <b>p</b> 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. */
@@ -846,6 +851,13 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
return DOWNCAST(rend_data_v2_t, d);
}
+/* Stub because we can't include hs_ident.h. */
+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
* simultaneous attempts to connect to the same rendezvous point. */
@@ -1633,6 +1645,11 @@ typedef struct edge_connection_t {
* an exit)? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for edge connections. Used by the HS
+ * client-side code to identify client SOCKS connections and by the
+ * service-side code to match HS circuits with their streams. */
+ struct hs_ident_edge_conn_t *hs_ident;
+
uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit
* connection. Exit connections only. */
uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell
@@ -1783,6 +1800,11 @@ typedef struct dir_connection_t {
/** What rendezvous service are we querying for? */
rend_data_t *rend_data;
+ /* Hidden service connection identifier for dir connections: Used by HS
+ client-side code to fetch HS descriptors, and by the service-side code to
+ upload descriptors. */
+ struct hs_ident_dir_conn_t *hs_ident;
+
/** If this is a one-hop connection, tracks the state of the directory guard
* for this connection (if any). */
struct circuit_guard_state_t *guard_state;
@@ -2058,7 +2080,9 @@ typedef struct download_status_t {
* or after each failure? */
download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the
* deterministic schedule, or random
- * exponential backoffs? */
+ * exponential backoffs?
+ * Increment on failure schedules
+ * always use exponential backoff. */
uint8_t last_backoff_position; /**< number of attempts/failures, depending
* on increment_on, when we last recalculated
* the delay. Only updated if backoff
@@ -2473,6 +2497,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
@@ -3186,6 +3214,10 @@ typedef struct origin_circuit_t {
/** Holds all rendezvous data on either client or service side. */
rend_data_t *rend_data;
+ /** Holds hidden service identifier on either client or service side. This
+ * is for both introduction and rendezvous circuit. */
+ struct hs_ident_circuit_t *hs_ident;
+
/** Holds the data that the entry guard system uses to track the
* status of the guard this circuit is using, and thereby to determine
* whether this circuit can be used. */
@@ -3567,7 +3599,8 @@ typedef struct {
enum {
CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD,
CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG,
- CMD_KEYGEN
+ CMD_KEYGEN,
+ CMD_KEY_EXPIRATION,
} command;
char *command_arg; /**< Argument for command-line option. */
@@ -4594,6 +4627,9 @@ typedef struct {
config_line_t *TransportProxies;
+ /** Cached revision counters for active hidden services on this host */
+ 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
* bandwidth usage. The "Interval" fields hold the granularity, in seconds,
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 <b>s</b>. 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 b9f1613457..5e5f9f4db6 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,
@@ -315,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 */
diff --git a/src/or/policies.c b/src/or/policies.c
index 3d49a6110c..4c24bfbc32 100644
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@ -2731,7 +2731,7 @@ parse_short_policy(const char *summary)
}
{
- size_t size = STRUCT_OFFSET(short_policy_t, entries) +
+ size_t size = offsetof(short_policy_t, entries) +
sizeof(short_policy_entry_t)*(n_entries);
result = tor_malloc_zero(size);
diff --git a/src/or/protover.h b/src/or/protover.h
index 22667bed79..2066aeec72 100644
--- a/src/or/protover.h
+++ b/src/or/protover.h
@@ -17,6 +17,11 @@
/* This is a guess. */
#define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha"
+/** The protover version number that signifies HSDir support for HSv3 */
+#define PROTOVER_HSDIR_V3 2
+/** The protover version number that signifies HSv3 intro point support */
+#define PROTOVER_HS_INTRO_V3 4
+
/** List of recognized subprotocols. */
typedef enum protocol_type_t {
PRT_LINK,
diff --git a/src/or/relay.c b/src/or/relay.c
index 0ff53ed5e9..18ccc65b80 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -184,18 +184,12 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell)
/** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b>
* (in place).
*
- * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt.
- *
- * Returns 0.
+ * Note that we use the same operation for encrypting and for decrypting.
*/
-static int
-relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in,
- int encrypt_mode)
+static void
+relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in)
{
- (void)encrypt_mode;
crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE);
-
- return 0;
}
/**
@@ -449,8 +443,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
do { /* Remember: cpath is in forward order, that is, first hop first. */
tor_assert(thishop);
- if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0)
- return -1;
+ /* decrypt one layer */
+ relay_crypt_one_payload(thishop->b_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -467,19 +461,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction,
log_fn(LOG_PROTOCOL_WARN, LD_OR,
"Incoming cell at client not recognized. Closing.");
return -1;
- } else { /* we're in the middle. Just one crypt. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto,
- cell->payload, 1) < 0)
- return -1;
-// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not "
-// "the client.");
+ } else {
+ /* We're in the middle. Encrypt one layer. */
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload);
}
} else /* cell_direction == CELL_DIRECTION_OUT */ {
- /* we're in the middle. Just one crypt. */
+ /* We're in the middle. Decrypt one layer. */
- if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto,
- cell->payload, 0) < 0)
- return -1;
+ relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload);
relay_header_unpack(&rh, cell->payload);
if (rh.recognized == 0) {
@@ -525,11 +514,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
/* moving from farthest to nearest hop */
do {
tor_assert(thishop);
- /* XXXX RD This is a bug, right? */
- log_debug(LD_OR,"crypting a layer of the relay cell.");
- if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) {
- return -1;
- }
+ log_debug(LD_OR,"encrypting a layer of the relay cell.");
+ relay_crypt_one_payload(thishop->f_crypto, cell->payload);
thishop = thishop->prev;
} while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev);
@@ -546,8 +532,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ,
or_circ = TO_OR_CIRCUIT(circ);
chan = or_circ->p_chan;
relay_set_digest(or_circ->p_digest, cell);
- if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0)
- return -1;
+ /* encrypt one layer */
+ relay_crypt_one_payload(or_circ->p_crypto, cell->payload);
}
++stats_n_relay_cells_relayed;
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index 9bc2d6289d..e47e1ef639 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -17,6 +17,7 @@
#include "connection_edge.h"
#include "directory.h"
#include "hs_common.h"
+#include "hs_circuit.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -285,10 +286,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
goto perm_err;
}
- note_crypto_pk_op(REND_CLIENT);
- /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
+ /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg,
* to avoid buffer overflows? */
- r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
+ r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN,
sizeof(payload)-DIGEST_LEN,
tmp,
(int)(dh_offset+DH_KEY_LEN),
@@ -1150,9 +1150,6 @@ int
rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
size_t request_len)
{
- crypt_path_t *hop;
- char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
-
if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY &&
circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)
|| !circ->build_state->pending_final_cpath) {
@@ -1170,55 +1167,13 @@ rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request,
log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service.");
- /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh
- * handshake...*/
- tor_assert(circ->build_state);
- tor_assert(circ->build_state->pending_final_cpath);
- hop = circ->build_state->pending_final_cpath;
- tor_assert(hop->rend_dh_handshake_state);
- if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN,
- hop->rend_dh_handshake_state, (char*)request,
- DH_KEY_LEN,
- keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
- log_warn(LD_GENERAL, "Couldn't complete DH handshake.");
+ if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) {
+ log_warn(LD_GENERAL, "Failed to setup circ");
goto err;
}
- /* ... and set up cpath. */
- if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0)
- goto err;
-
- /* Check whether the digest is right... */
- if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) {
- log_warn(LD_PROTOCOL, "Incorrect digest of key material.");
- goto err;
- }
-
- crypto_dh_free(hop->rend_dh_handshake_state);
- hop->rend_dh_handshake_state = NULL;
-
- /* All is well. Extend the circuit. */
- circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED);
- hop->state = CPATH_STATE_OPEN;
- /* set the windows to default. these are the windows
- * that the client thinks the service has.
- */
- hop->package_window = circuit_initial_package_window();
- hop->deliver_window = CIRCWINDOW_START;
-
- /* Now that this circuit has finished connecting to its destination,
- * make sure circuit_get_open_circ_or_launch is willing to return it
- * so we can actually use it. */
- circ->hs_circ_has_timed_out = 0;
-
- onion_append_to_cpath(&circ->cpath, hop);
- circ->build_state->pending_final_cpath = NULL; /* prevent double-free */
-
- circuit_try_attaching_streams(circ);
-
- memwipe(keys, 0, sizeof(keys));
return 0;
+
err:
- memwipe(keys, 0, sizeof(keys));
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
return -1;
}
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index e1236bdd0f..8b555a3164 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -8,6 +8,8 @@
* introducers, services, clients, and rendezvous points.
**/
+#define RENDCOMMON_PRIVATE
+
#include "or.h"
#include "circuitbuild.h"
#include "config.h"
@@ -395,7 +397,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out,
/** Attempt to parse the given <b>desc_str</b> and return true if this
* succeeds, false otherwise. */
-static int
+STATIC int
rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc)
{
rend_service_descriptor_t *test_parsed = NULL;
@@ -775,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)
@@ -791,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)
diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h
index 94c2480d86..292f9277e8 100644
--- a/src/or/rendcommon.h
+++ b/src/or/rendcommon.h
@@ -63,5 +63,12 @@ int rend_non_anonymous_mode_enabled(const or_options_t *options);
void assert_circ_anonymity_ok(origin_circuit_t *circ,
const or_options_t *options);
+#ifdef RENDCOMMON_PRIVATE
+
+STATIC int
+rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc);
+
+#endif
+
#endif
diff --git a/src/or/rendmid.c b/src/or/rendmid.c
index 23c3deddaa..66d2f93113 100644
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@ -71,7 +71,6 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
goto err;
}
/* Rest of body: signature of previous data */
- note_crypto_pk_op(REND_MID);
if (crypto_pk_public_checksig_digest(pk,
(char*)request, 2+asn1len+DIGEST_LEN,
(char*)(request+2+DIGEST_LEN+asn1len),
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index f3b78c4663..a205b00c6b 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -18,6 +18,7 @@
#include "control.h"
#include "directory.h"
#include "hs_common.h"
+#include "hs_config.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -82,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. */
@@ -163,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;
@@ -231,18 +216,41 @@ rend_service_free(rend_service_t *service)
tor_free(service);
}
-/** Release all the storage held in rend_service_list.
- */
+/* Release all the storage held in rend_service_staging_list. */
+void
+rend_service_free_staging_list(void)
+{
+ if (rend_service_staging_list) {
+ SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_staging_list);
+ rend_service_staging_list = NULL;
+ }
+}
+
+/** Release all the storage held in both rend_service_list and
+ * rend_service_staging_list. */
void
rend_service_free_all(void)
{
- if (!rend_service_list)
- return;
+ if (rend_service_list) {
+ SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
+ rend_service_free(ptr));
+ smartlist_free(rend_service_list);
+ rend_service_list = NULL;
+ }
+ rend_service_free_staging_list();
+}
+
+/* Initialize the subsystem. */
+void
+rend_service_init(void)
+{
+ tor_assert(!rend_service_list);
+ tor_assert(!rend_service_staging_list);
- SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr,
- rend_service_free(ptr));
- smartlist_free(rend_service_list);
- rend_service_list = NULL;
+ rend_service_list = smartlist_new();
+ rend_service_staging_list = smartlist_new();
}
/* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there
@@ -252,8 +260,6 @@ static int
rend_validate_service(const smartlist_t *service_list,
const rend_service_t *service)
{
- int dupe = 0;
-
tor_assert(service_list);
tor_assert(service);
@@ -286,34 +292,6 @@ rend_validate_service(const smartlist_t *service_list,
goto invalid;
}
- /* XXX This duplicate check has two problems:
- *
- * a) It's O(n^2), but the same comment from the bottom of
- * rend_config_services() should apply.
- *
- * b) We only compare directory paths as strings, so we can't
- * detect two distinct paths that specify the same directory
- * (which can arise from symlinks, case-insensitivity, bind
- * mounts, etc.).
- *
- * It also can't detect that two separate Tor instances are trying
- * to use the same HiddenServiceDir; for that, we would need a
- * lock file. But this is enough to detect a simple mistake that
- * at least one person has actually made.
- */
- if (!rend_service_is_ephemeral(service)) {
- /* Skip dupe for ephemeral services. */
- SMARTLIST_FOREACH(service_list, rend_service_t *, ptr,
- dupe = dupe ||
- !strcmp(ptr->directory, service->directory));
- if (dupe) {
- log_warn(LD_REND, "Another hidden service is already configured for "
- "directory %s.",
- rend_service_escaped_dir(service));
- goto invalid;
- }
- }
-
/* Valid. */
return 0;
invalid:
@@ -335,6 +313,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
/* We must have a service list, even if it's a temporary one, so we can
* check for duplicate services */
if (BUG(!s_list)) {
+ rend_service_free(service);
return -1;
}
@@ -496,41 +475,6 @@ rend_service_port_config_free(rend_service_port_config_t *p)
tor_free(p);
}
-/* Check the directory for <b>service</b>, and add the service to
- * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL.
- * Only add the service to the list if <b>validate_only</b> is false.
- * If <b>validate_only</b> is true, free the service.
- * If <b>service</b> is NULL, ignore it, and return 0.
- * Returns 0 on success, and -1 on failure.
- * Takes ownership of <b>service</b>, either freeing it, or adding it to the
- * global service list.
- */
-STATIC int
-rend_service_check_dir_and_add(smartlist_t *service_list,
- const or_options_t *options,
- rend_service_t *service,
- int validate_only)
-{
- if (!service) {
- /* It is ok for a service to be NULL, this means there are no services */
- return 0;
- }
-
- if (rend_service_check_private_dir(options, service, !validate_only)
- < 0) {
- rend_service_free(service);
- return -1;
- }
-
- smartlist_t *s_list = rend_get_service_list_mutable(service_list);
- /* We must have a service list, even if it's a temporary one, so we can
- * check for duplicate services */
- if (BUG(!s_list)) {
- return -1;
- }
- return rend_add_service(s_list, service);
-}
-
/* Helper: Actual implementation of the pruning on reload which we've
* decoupled in order to make the unit test workeable without ugly hacks.
* Furthermore, this function does NOT free any memory but will nullify the
@@ -657,19 +601,54 @@ rend_service_prune_list(void)
}
}
-/** Set up rend_service_list, based on the values of HiddenServiceDir and
- * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on
- * failure. (If <b>validate_only</b> is set, parse, warn and return as
- * normal, but don't actually change the configured services.)
- */
+/* Copy all the relevant data that the hs_service object contains over to the
+ * rend_service_t object. The reason to do so is because when configuring a
+ * service, we go through a generic handler that creates an hs_service_t
+ * object which so we have to copy the parsed values to a rend service object
+ * which is version 2 specific. */
+static void
+service_config_shadow_copy(rend_service_t *service,
+ hs_service_config_t *config)
+{
+ tor_assert(service);
+ tor_assert(config);
+
+ service->directory = tor_strdup(config->directory_path);
+ service->dir_group_readable = config->dir_group_readable;
+ service->allow_unknown_ports = config->allow_unknown_ports;
+ /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535)
+ * if the code flow is right so this cast is safe. But just in case, we'll
+ * check it. */
+ service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit;
+ if (BUG(config->max_streams_per_rdv_circuit >
+ HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) {
+ service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT;
+ }
+ service->max_streams_close_circuit = config->max_streams_close_circuit;
+ service->n_intro_points_wanted = config->num_intro_points;
+ /* Switching ownership of the ports to the rend service object. */
+ smartlist_add_all(service->ports, config->ports);
+ smartlist_free(config->ports);
+ config->ports = NULL;
+}
+
+/* Parse the hidden service configuration starting at <b>line_</b> using the
+ * already configured generic service configuration in <b>config</b>. This
+ * function will translate the config object to a rend_service_t and add it to
+ * the temporary list if valid. If <b>validate_only</b> is set, parse, warn
+ * and return as normal but don't actually add the service to the list. */
int
-rend_config_services(const or_options_t *options, int validate_only)
+rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config)
{
- config_line_t *line;
+ const config_line_t *line;
rend_service_t *service = NULL;
- rend_service_port_config_t *portcfg;
- int ok = 0;
- int rv = -1;
+
+ /* line_ can be NULL which would mean that the service configuration only
+ * have one line that is the directory directive. */
+ tor_assert(options);
+ tor_assert(config);
/* Use the staging service list so that we can check then do the pruning
* process using the main list at the end. */
@@ -677,100 +656,23 @@ rend_config_services(const or_options_t *options, int validate_only)
rend_service_staging_list = smartlist_new();
}
- for (line = options->RendConfigLines; line; line = line->next) {
+ /* Initialize service. */
+ service = tor_malloc_zero(sizeof(rend_service_t));
+ service->intro_period_started = time(NULL);
+ service->ports = smartlist_new();
+ /* From the hs_service object which has been used to load the generic
+ * options, we'll copy over the useful data to the rend_service_t object. */
+ service_config_shadow_copy(service, config);
+
+ for (line = line_; line; line = line->next) {
if (!strcasecmp(line->key, "HiddenServiceDir")) {
- if (service) {
- /* Validate and register the service we just finished parsing this
- * code registers every service except the last one parsed, which is
- * validated and registered below the loop. */
- if (rend_validate_service(rend_service_staging_list, service) < 0) {
- goto free_and_return;
- }
- if (rend_service_check_dir_and_add(rend_service_staging_list, options,
- service, validate_only) < 0) {
- /* The above frees the service on error so nullify the pointer. */
- service = NULL;
- goto free_and_return;
- }
- }
- service = tor_malloc_zero(sizeof(rend_service_t));
- service->directory = tor_strdup(line->value);
- service->ports = smartlist_new();
- service->intro_period_started = time(NULL);
- service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
- continue;
- }
- if (!service) {
- log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
- line->key);
- goto free_and_return;
+ /* We just hit the next hidden service, stop right now. */
+ break;
}
- if (!strcasecmp(line->key, "HiddenServicePort")) {
- char *err_msg = NULL;
- portcfg = rend_service_parse_port_config(line->value, " ", &err_msg);
- if (!portcfg) {
- if (err_msg)
- log_warn(LD_CONFIG, "%s", err_msg);
- tor_free(err_msg);
- goto free_and_return;
- }
- tor_assert(!err_msg);
- smartlist_add(service->ports, portcfg);
- } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) {
- service->allow_unknown_ports = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceAllowUnknownPorts=%d for %s",
- (int)service->allow_unknown_ports,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key,
- "HiddenServiceDirGroupReadable")) {
- service->dir_group_readable = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceDirGroupReadable should be 0 or 1, not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceDirGroupReadable=%d for %s",
- service->dir_group_readable,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) {
- service->max_streams_per_circuit = (int)tor_parse_long(line->value,
- 10, 0, 65535, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreams should be between 0 and %d, not %s",
- 65535, line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreams=%d for %s",
- service->max_streams_per_circuit,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) {
- service->max_streams_close_circuit = (int)tor_parse_long(line->value,
- 10, 0, 1, &ok, NULL);
- if (!ok) {
- log_warn(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, "
- "not %s",
- line->value);
- goto free_and_return;
- }
- log_info(LD_CONFIG,
- "HiddenServiceMaxStreamsCloseCircuit=%d for %s",
- (int)service->max_streams_close_circuit,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ /* Number of introduction points. */
+ if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
+ int ok = 0;
+ /* Those are specific defaults for version 2. */
service->n_intro_points_wanted =
(unsigned int) tor_parse_long(line->value, 10,
0, NUM_INTRO_POINTS_MAX, &ok, NULL);
@@ -779,12 +681,13 @@ rend_config_services(const or_options_t *options, int validate_only)
"HiddenServiceNumIntroductionPoints "
"should be between %d and %d, not %s",
0, NUM_INTRO_POINTS_MAX, line->value);
- goto free_and_return;
+ goto err;
}
log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s",
- service->n_intro_points_wanted,
- rend_service_escaped_dir(service));
- } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
+ service->n_intro_points_wanted, escaped(service->directory));
+ continue;
+ }
+ if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
/* Parse auth type and comma-separated list of client names and add a
* rend_authorized_client_t for each client to the service's list
* of authorized clients. */
@@ -794,7 +697,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (service->auth_type != REND_NO_AUTH) {
log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient "
"lines for a single service.");
- goto free_and_return;
+ goto err;
}
type_names_split = smartlist_new();
smartlist_split_string(type_names_split, line->value, " ", 0, 2);
@@ -802,7 +705,8 @@ rend_config_services(const or_options_t *options, int validate_only)
log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This "
"should have been prevented when parsing the "
"configuration.");
- goto free_and_return;
+ smartlist_free(type_names_split);
+ goto err;
}
authname = smartlist_get(type_names_split, 0);
if (!strcasecmp(authname, "basic")) {
@@ -816,7 +720,7 @@ rend_config_services(const or_options_t *options, int validate_only)
(char *) smartlist_get(type_names_split, 0));
SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp));
smartlist_free(type_names_split);
- goto free_and_return;
+ goto err;
}
service->clients = smartlist_new();
if (smartlist_len(type_names_split) < 2) {
@@ -853,7 +757,7 @@ rend_config_services(const or_options_t *options, int validate_only)
client_name, REND_CLIENTNAME_MAX_LEN);
SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp));
smartlist_free(clients);
- goto free_and_return;
+ goto err;
}
client = tor_malloc_zero(sizeof(rend_authorized_client_t));
client->client_name = tor_strdup(client_name);
@@ -875,56 +779,29 @@ rend_config_services(const or_options_t *options, int validate_only)
smartlist_len(service->clients),
service->auth_type == REND_BASIC_AUTH ? 512 : 16,
service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth");
- goto free_and_return;
- }
- } else {
- tor_assert(!strcasecmp(line->key, "HiddenServiceVersion"));
- if (strcmp(line->value, "2")) {
- log_warn(LD_CONFIG,
- "The only supported HiddenServiceVersion is 2.");
- goto free_and_return;
+ goto err;
}
+ continue;
}
}
- /* Validate the last service that we just parsed. */
- if (service &&
- rend_validate_service(rend_service_staging_list, service) < 0) {
- goto free_and_return;
- }
- /* register the final service after we have finished parsing all services
- * this code only registers the last service, other services are registered
- * within the loop. It is ok for this service to be NULL, it is ignored. */
- if (rend_service_check_dir_and_add(rend_service_staging_list, options,
- service, validate_only) < 0) {
- /* Service object is freed on error so nullify pointer. */
- service = NULL;
- goto free_and_return;
+ /* Validate the service just parsed. */
+ if (rend_validate_service(rend_service_staging_list, service) < 0) {
+ /* Service is in the staging list so don't try to free it. */
+ goto err;
}
- /* The service is in the staging list so nullify pointer to avoid double
- * free of this object in case of error because we lost ownership of it at
- * this point. */
- service = NULL;
- /* Free the newly added services if validating */
- if (validate_only) {
- rv = 0;
- goto free_and_return;
+ /* Add it to the temporary list which we will use to prune our current
+ * list if any after configuring all services. */
+ if (rend_add_service(rend_service_staging_list, service) < 0) {
+ /* The object has been freed on error already. */
+ service = NULL;
+ goto err;
}
- /* This could be a reload of configuration so try to prune the main list
- * using the staging one. And we know we are not in validate mode here.
- * After this, the main and staging list will point to the right place and
- * be in a quiescent usable state. */
- rend_service_prune_list();
-
return 0;
- free_and_return:
+ err:
rend_service_free(service);
- SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr,
- rend_service_free(ptr));
- smartlist_free(rend_service_staging_list);
- rend_service_staging_list = NULL;
- return rv;
+ return -1;
}
/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using
@@ -1170,15 +1047,8 @@ rend_service_update_descriptor(rend_service_t *service)
static char *
rend_service_path(const rend_service_t *service, const char *file_name)
{
- char *file_path = NULL;
-
tor_assert(service->directory);
-
- /* Can never fail: asserts rather than leaving file_path NULL. */
- tor_asprintf(&file_path, "%s%s%s",
- service->directory, PATH_SEPARATOR, file_name);
-
- return file_path;
+ return hs_path_from_filename(service->directory, file_name);
}
/* Allocate and return a string containing the path to the single onion
@@ -1548,9 +1418,9 @@ rend_service_load_keys(rend_service_t *s)
char *fname = NULL;
char buf[128];
- /* Make sure the directory was created and single onion poisoning was
- * checked before calling this function */
- if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0))
+ /* Create the directory if needed which will also poison it in case of
+ * single onion service. */
+ if (rend_service_check_private_dir(get_options(), s, 1) < 0)
goto err;
/* Load key */
@@ -1808,24 +1678,6 @@ rend_service_get_by_service_id(const char *id)
return NULL;
}
-/** Return 1 if any virtual port in <b>service</b> 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 <b>descriptor_cookie</b> of
* length <b>cookie_len</b> for <b>service</b>. Return 1 for success
* and 0 for failure. */
@@ -2143,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);
@@ -2195,7 +2047,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
cpath->rend_dh_handshake_state = dh;
dh = NULL;
- if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
+ if (circuit_init_cpath_crypto(cpath,
+ keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN,
+ 1, 0)<0)
goto err;
memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN);
@@ -2843,10 +2697,8 @@ rend_service_decrypt_intro(
}
/* Decrypt the encrypted part */
-
- note_crypto_pk_op(REND_SERVER);
result =
- crypto_pk_private_hybrid_decrypt(
+ crypto_pk_obsolete_private_hybrid_decrypt(
key, (char *)buf, sizeof(buf),
(const char *)(intro->ciphertext), intro->ciphertext_len,
PK_PKCS1_OAEP_PADDING, 1);
@@ -3040,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);
@@ -3230,10 +3059,11 @@ count_intro_point_circuits(const rend_service_t *service)
crypto material. On success, fill <b>cell_body_out</b> 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;
@@ -3258,7 +3088,6 @@ encode_establish_intro_cell_legacy(char *cell_body_out,
if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9))
goto err;
len += 20;
- note_crypto_pk_op(REND_SERVER);
r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len,
cell_body_out_len - len,
cell_body_out, len);
@@ -3371,7 +3200,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) {
@@ -4098,10 +3927,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(
@@ -4118,7 +3946,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;
@@ -4396,60 +4223,6 @@ rend_service_dump_stats(int severity)
}
}
-#ifdef HAVE_SYS_UN_H
-
-/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t,
- * add the given <b>p</b>, 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 <b>conn</b> set it to use the given port <b>p</b> 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 <b>conn</b>, 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
@@ -4462,9 +4235,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);
@@ -4500,41 +4270,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;
}
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index 1583a6010b..ed1044f04a 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -13,11 +13,9 @@
#define TOR_RENDSERVICE_H
#include "or.h"
+#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
/* This can be used for both INTRODUCE1 and INTRODUCE2 */
@@ -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 */
@@ -119,20 +119,12 @@ typedef struct rend_service_t {
STATIC void rend_service_free(rend_service_t *service);
STATIC char *rend_service_sos_poison_path(const rend_service_t *service);
-STATIC int rend_service_check_dir_and_add(smartlist_t *service_list,
- const or_options_t *options,
- rend_service_t *service,
- int validate_only);
STATIC int rend_service_verify_single_onion_poison(
const rend_service_t *s,
const or_options_t *options);
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);
@@ -143,13 +135,16 @@ STATIC void rend_service_prune_list_impl_(void);
#endif /* RENDSERVICE_PRIVATE */
-int num_rend_services(void);
-int rend_config_services(const or_options_t *options, int validate_only);
+int rend_num_services(void);
+int rend_config_service(const config_line_t *line_,
+ const or_options_t *options,
+ hs_service_config_t *config);
void rend_service_prune_list(void);
+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);
@@ -172,6 +167,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);
@@ -179,6 +178,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn,
origin_circuit_t *circ);
void rend_service_dump_stats(int severity);
void rend_service_free_all(void);
+void rend_service_init(void);
rend_service_port_config_t *rend_service_parse_port_config(const char *string,
const char *sep,
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 72a5cc5a9b..e65b93fa76 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -2064,105 +2064,6 @@ rep_hist_circbuilding_dormant(time_t now)
return 1;
}
-/** Structure to track how many times we've done each public key operation. */
-static struct {
- /** How many directory objects have we signed? */
- unsigned long n_signed_dir_objs;
- /** How many routerdescs have we signed? */
- unsigned long n_signed_routerdescs;
- /** How many directory objects have we verified? */
- unsigned long n_verified_dir_objs;
- /** How many routerdescs have we verified */
- unsigned long n_verified_routerdescs;
- /** How many onionskins have we encrypted to build circuits? */
- unsigned long n_onionskins_encrypted;
- /** How many onionskins have we decrypted to do circuit build requests? */
- unsigned long n_onionskins_decrypted;
- /** How many times have we done the TLS handshake as a client? */
- unsigned long n_tls_client_handshakes;
- /** How many times have we done the TLS handshake as a server? */
- unsigned long n_tls_server_handshakes;
- /** How many PK operations have we done as a hidden service client? */
- unsigned long n_rend_client_ops;
- /** How many PK operations have we done as a hidden service midpoint? */
- unsigned long n_rend_mid_ops;
- /** How many PK operations have we done as a hidden service provider? */
- unsigned long n_rend_server_ops;
-} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0};
-
-/** Increment the count of the number of times we've done <b>operation</b>. */
-void
-note_crypto_pk_op(pk_op_t operation)
-{
- switch (operation)
- {
- case SIGN_DIR:
- pk_op_counts.n_signed_dir_objs++;
- break;
- case SIGN_RTR:
- pk_op_counts.n_signed_routerdescs++;
- break;
- case VERIFY_DIR:
- pk_op_counts.n_verified_dir_objs++;
- break;
- case VERIFY_RTR:
- pk_op_counts.n_verified_routerdescs++;
- break;
- case ENC_ONIONSKIN:
- pk_op_counts.n_onionskins_encrypted++;
- break;
- case DEC_ONIONSKIN:
- pk_op_counts.n_onionskins_decrypted++;
- break;
- case TLS_HANDSHAKE_C:
- pk_op_counts.n_tls_client_handshakes++;
- break;
- case TLS_HANDSHAKE_S:
- pk_op_counts.n_tls_server_handshakes++;
- break;
- case REND_CLIENT:
- pk_op_counts.n_rend_client_ops++;
- break;
- case REND_MID:
- pk_op_counts.n_rend_mid_ops++;
- break;
- case REND_SERVER:
- pk_op_counts.n_rend_server_ops++;
- break;
- default:
- log_warn(LD_BUG, "Unknown pk operation %d", operation);
- }
-}
-
-/** Log the number of times we've done each public/private-key operation. */
-void
-dump_pk_ops(int severity)
-{
- tor_log(severity, LD_HIST,
- "PK operations: %lu directory objects signed, "
- "%lu directory objects verified, "
- "%lu routerdescs signed, "
- "%lu routerdescs verified, "
- "%lu onionskins encrypted, "
- "%lu onionskins decrypted, "
- "%lu client-side TLS handshakes, "
- "%lu server-side TLS handshakes, "
- "%lu rendezvous client operations, "
- "%lu rendezvous middle operations, "
- "%lu rendezvous server operations.",
- pk_op_counts.n_signed_dir_objs,
- pk_op_counts.n_verified_dir_objs,
- pk_op_counts.n_signed_routerdescs,
- pk_op_counts.n_verified_routerdescs,
- pk_op_counts.n_onionskins_encrypted,
- pk_op_counts.n_onionskins_decrypted,
- pk_op_counts.n_tls_client_handshakes,
- pk_op_counts.n_tls_server_handshakes,
- pk_op_counts.n_rend_client_ops,
- pk_op_counts.n_rend_mid_ops,
- pk_op_counts.n_rend_server_ops);
-}
-
/*** Exit port statistics ***/
/* Some constants */
diff --git a/src/or/rephist.h b/src/or/rephist.h
index 2b1c2e7ec7..8f6d46616d 100644
--- a/src/or/rephist.h
+++ b/src/or/rephist.h
@@ -62,9 +62,6 @@ int any_predicted_circuits(time_t now);
int rep_hist_circbuilding_dormant(time_t now);
int predicted_ports_prediction_time_remaining(time_t now);
-void note_crypto_pk_op(pk_op_t operation);
-void dump_pk_ops(int severity);
-
void rep_hist_exit_stats_init(time_t now);
void rep_hist_reset_exit_stats(time_t now);
void rep_hist_exit_stats_term(void);
diff --git a/src/or/router.c b/src/or/router.c
index 2187a76b48..7fad572657 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -1065,7 +1065,7 @@ init_keys(void)
/* 4. Build our router descriptor. */
/* Must be called after keys are initialized. */
mydesc = router_get_my_descriptor();
- if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) {
+ if (authdir_mode_v3(options)) {
const char *m = NULL;
routerinfo_t *ri;
/* We need to add our own fingerprint so it gets recognized. */
@@ -1596,32 +1596,19 @@ authdir_mode_v3(const or_options_t *options)
{
return authdir_mode(options) && options->V3AuthoritativeDir != 0;
}
-/** Return true iff we are a v3 directory authority. */
-int
-authdir_mode_any_main(const or_options_t *options)
-{
- return options->V3AuthoritativeDir;
-}
-/** Return true if we believe ourselves to be any kind of
- * authoritative directory beyond just a hidserv authority. */
-int
-authdir_mode_any_nonhidserv(const or_options_t *options)
-{
- return options->BridgeAuthoritativeDir ||
- authdir_mode_any_main(options);
-}
/** Return true iff we are an authoritative directory server that is
* authoritative about receiving and serving descriptors of type
- * <b>purpose</b> on its dirport. Use -1 for "any purpose". */
+ * <b>purpose</b> on its dirport.
+ */
int
authdir_mode_handles_descs(const or_options_t *options, int purpose)
{
- if (purpose < 0)
- return authdir_mode_any_nonhidserv(options);
+ if (BUG(purpose < 0)) /* Deprecated. */
+ return authdir_mode(options);
else if (purpose == ROUTER_PURPOSE_GENERAL)
- return authdir_mode_any_main(options);
+ return authdir_mode_v3(options);
else if (purpose == ROUTER_PURPOSE_BRIDGE)
- return (options->BridgeAuthoritativeDir);
+ return authdir_mode_bridge(options);
else
return 0;
}
@@ -1633,7 +1620,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
{
if (authdir_mode_bridge(options))
return 0;
- return authdir_mode_any_nonhidserv(options);
+ return authdir_mode(options);
}
/** Return true iff we are an authoritative directory server that
* tests reachability of the descriptors it learns about.
@@ -1641,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options)
int
authdir_mode_tests_reachability(const or_options_t *options)
{
- return authdir_mode_handles_descs(options, -1);
+ return authdir_mode(options);
}
/** Return true iff we believe ourselves to be a bridge authoritative
* directory server.
@@ -1869,7 +1856,7 @@ static const char *desc_gen_reason = NULL;
* now. */
static time_t desc_clean_since = 0;
/** Why did we mark the descriptor dirty? */
-static const char *desc_dirty_reason = NULL;
+static const char *desc_dirty_reason = "Tor just started";
/** Boolean: do we need to regenerate the above? */
static int desc_needs_upload = 0;
@@ -3022,7 +3009,6 @@ router_dump_router_to_string(routerinfo_t *router,
crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
- note_crypto_pk_op(SIGN_RTR);
{
char *sig;
if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) {
diff --git a/src/or/router.h b/src/or/router.h
index 9c5def5218..97f331713a 100644
--- a/src/or/router.h
+++ b/src/or/router.h
@@ -54,8 +54,6 @@ int net_is_disabled(void);
int authdir_mode(const or_options_t *options);
int authdir_mode_v3(const or_options_t *options);
-int authdir_mode_any_main(const or_options_t *options);
-int authdir_mode_any_nonhidserv(const or_options_t *options);
int authdir_mode_handles_descs(const or_options_t *options, int purpose);
int authdir_mode_publishes_statuses(const or_options_t *options);
int authdir_mode_tests_reachability(const or_options_t *options);
diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c
index 71889d2721..2f20758b5b 100644
--- a/src/or/routerkeys.c
+++ b/src/or/routerkeys.c
@@ -1136,6 +1136,108 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key)
#undef MAKECERT
#endif
+/**
+ * Print the ISO8601-formated <b>expiration</b> for a certificate with
+ * some <b>description</b> to stdout.
+ *
+ * For example, for a signing certificate, this might print out:
+ * signing-cert-expiry: 2017-07-25 08:30:15 UTC
+ */
+static void
+print_cert_expiration(const char *expiration,
+ const char *description)
+{
+ fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration);
+}
+
+/**
+ * Log when a certificate, <b>cert</b>, with some <b>description</b> and
+ * stored in a file named <b>fname</b>, is going to expire.
+ */
+static void
+log_ed_cert_expiration(const tor_cert_t *cert,
+ const char *description,
+ const char *fname) {
+ char expiration[ISO_TIME_LEN+1];
+
+ if (BUG(!cert)) { /* If the specified key hasn't been loaded */
+ log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.",
+ description);
+ } else {
+ format_local_iso_time(expiration, cert->valid_until);
+ log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.",
+ description, fname, expiration);
+ print_cert_expiration(expiration, description);
+ }
+}
+
+/**
+ * Log when our master signing key certificate expires. Used when tor is given
+ * the --key-expiration command-line option.
+ *
+ * Returns 0 on success and 1 on failure.
+ */
+static int
+log_master_signing_key_cert_expiration(const or_options_t *options)
+{
+ const tor_cert_t *signing_key;
+ char *fn = NULL;
+ int failed = 0;
+ time_t now = approx_time();
+
+ fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert");
+
+ /* Try to grab our cached copy of the key. */
+ signing_key = get_master_signing_key_cert();
+
+ tor_assert(server_identity_key_is_set());
+
+ /* Load our keys from disk, if necessary. */
+ if (!signing_key) {
+ failed = load_ed_keys(options, now) < 0;
+ signing_key = get_master_signing_key_cert();
+ }
+
+ /* If we do have a signing key, log the expiration time. */
+ if (signing_key) {
+ log_ed_cert_expiration(signing_key, "signing", fn);
+ } else {
+ log_warn(LD_OR, "Could not load signing key certificate from %s, so " \
+ "we couldn't learn anything about certificate expiration.", fn);
+ }
+
+ tor_free(fn);
+
+ return failed;
+}
+
+/**
+ * Log when a key certificate expires. Used when tor is given the
+ * --key-expiration command-line option.
+ *
+ * If an command argument is given, which should specify the type of
+ * key to get expiry information about (currently supported arguments
+ * are "sign"), get info about that type of certificate. Otherwise,
+ * print info about the supported arguments.
+ *
+ * Returns 0 on success and -1 on failure.
+ */
+int
+log_cert_expiration(void)
+{
+ const or_options_t *options = get_options();
+ const char *arg = options->command_arg;
+
+ if (!strcmp(arg, "sign")) {
+ return log_master_signing_key_cert_expiration(options);
+ } else {
+ fprintf(stderr, "No valid argument to --key-expiration found!\n");
+ fprintf(stderr, "Currently recognised arguments are: 'sign'\n");
+
+ return -1;
+ }
+}
+
const ed25519_public_key_t *
get_master_identity_key(void)
{
diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h
index c10cf32a71..0cf13e7600 100644
--- a/src/or/routerkeys.h
+++ b/src/or/routerkeys.h
@@ -63,6 +63,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert,
const ed25519_public_key_t *master_id_pkey,
const uint8_t *rsa_id_digest));
+int log_cert_expiration(void);
int load_ed_keys(const or_options_t *options, time_t now);
int should_make_new_ed_keys(const or_options_t *options, const time_t now);
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 0e45f63f70..49caa875fe 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -5033,7 +5033,7 @@ launch_descriptor_downloads(int purpose,
}
}
- if (!authdir_mode_any_nonhidserv(options)) {
+ if (!authdir_mode(options)) {
/* If we wind up going to the authorities, we want to only open one
* connection to each authority at a time, so that we don't overload
* them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH
@@ -5055,8 +5055,9 @@ launch_descriptor_downloads(int purpose,
if (n_per_request > max_dl_per_req)
n_per_request = max_dl_per_req;
- if (n_per_request < MIN_DL_PER_REQUEST)
- n_per_request = MIN_DL_PER_REQUEST;
+ if (n_per_request < MIN_DL_PER_REQUEST) {
+ n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable);
+ }
if (n_downloadable > n_per_request)
req_plural = rtr_plural = "s";
@@ -5164,7 +5165,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
smartlist_add(downloadable, rs->descriptor_digest);
} SMARTLIST_FOREACH_END(rsp);
- if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)
+ if (!authdir_mode_v3(options)
&& smartlist_len(no_longer_old)) {
routerlist_t *rl = router_get_routerlist();
log_info(LD_DIR, "%d router descriptors listed in consensus are "
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index 0f6113ccfc..3449e6f6b5 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -1996,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end,
}
tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
#ifdef COUNT_DISTINCT_DIGESTS
if (!verified_digests)
verified_digests = digestmap_new();
@@ -2231,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
}
if (key) {
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(digest, DIGEST_LEN, tok, key, 0,
"extra-info") < 0)
goto err;
@@ -2708,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,6 +2719,12 @@ 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. */
+ 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]);
}
@@ -2856,7 +2861,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b)
int
networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
{
- int64_t weight_scale;
int64_t G=0, M=0, E=0, D=0, T=0;
double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed;
double Gtotal=0, Mtotal=0, Etotal=0;
@@ -2864,7 +2868,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method)
int valid = 1;
(void) consensus_method;
- weight_scale = networkstatus_get_weight_scale_param(ns);
+ const int64_t weight_scale = networkstatus_get_weight_scale_param(ns);
+ tor_assert(weight_scale >= 1);
Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1);
Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1);
Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1);
@@ -3360,8 +3365,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens)
voter_identity = "consensus";
}
- /* We extract both and on error, everything is stopped because it means
- * the votes is malformed for the shared random value(s). */
+ /* We extract both, and on error everything is stopped because it means
+ * the vote is malformed for the shared random value(s). */
if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) {
log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s",
voter_identity);
@@ -5288,7 +5293,6 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
}
/* Parse and verify signature. */
tok = find_by_keyword(tokens, R_SIGNATURE);
- note_crypto_pk_op(VERIFY_RTR);
if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0,
"v2 rendezvous service descriptor") < 0)
goto err;
diff --git a/src/or/scheduler.c b/src/or/scheduler.c
index fac545fba7..0d31c7d58c 100644
--- a/src/or/scheduler.c
+++ b/src/or/scheduler.c
@@ -282,7 +282,7 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan))
*/
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE;
log_debug(LD_SCHED,
@@ -324,7 +324,7 @@ scheduler_channel_has_waiting_cells,(channel_t *chan))
chan->scheduler_state = SCHED_CHAN_PENDING;
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
log_debug(LD_SCHED,
"Channel " U64_FORMAT " at %p went from waiting_for_cells "
@@ -400,7 +400,7 @@ scheduler_release_channel,(channel_t *chan))
if (chan->scheduler_state == SCHED_CHAN_PENDING) {
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
@@ -430,7 +430,7 @@ scheduler_run, (void))
/* Pop off a channel */
chan = smartlist_pqueue_pop(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx));
+ offsetof(channel_t, sched_heap_idx));
tor_assert(chan);
/* Figure out how many cells we can write */
@@ -531,7 +531,7 @@ scheduler_run, (void))
readd_chan->scheduler_state = SCHED_CHAN_PENDING;
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
readd_chan);
} SMARTLIST_FOREACH_END(readd_chan);
smartlist_free(to_readd);
@@ -581,7 +581,7 @@ scheduler_channel_wants_writes(channel_t *chan)
*/
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
chan->scheduler_state = SCHED_CHAN_PENDING;
log_debug(LD_SCHED,
@@ -624,11 +624,11 @@ scheduler_touch_channel(channel_t *chan)
/* Remove and re-add it */
smartlist_pqueue_remove(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
smartlist_pqueue_add(channels_pending,
scheduler_compare_channels,
- STRUCT_OFFSET(channel_t, sched_heap_idx),
+ offsetof(channel_t, sched_heap_idx),
chan);
}
/* else no-op, since it isn't in the queue */
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
index 25ca0611cd..e4ee64139a 100644
--- a/src/or/shared_random.c
+++ b/src/or/shared_random.c
@@ -1390,6 +1390,52 @@ 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(const networkstatus_t *ns)
+{
+ 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;
+}
+
+/* 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(const networkstatus_t *ns)
+{
+ 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;
+}
+
#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..76d5b95422 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(const networkstatus_t *ns);
+const sr_srv_t *sr_get_previous(const networkstatus_t *ns);
+
#ifdef SHARED_RANDOM_PRIVATE
/* Encode */
diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c
index 89d2e8d7f6..d7ae05e895 100644
--- a/src/or/shared_random_state.c
+++ b/src/or/shared_random_state.c
@@ -42,7 +42,7 @@ static const char dstate_cur_srv_key[] = "SharedRandCurrentValue";
/* These next two are duplicates or near-duplicates from config.c */
#define VAR(name, conftype, member, initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \
initvalue }
/* As VAR, but the option name and member name are the same. */
#define V(member, conftype, initvalue) \
@@ -77,14 +77,14 @@ static config_var_t state_vars[] = {
* lets us preserve options from versions of Tor newer than us. */
static config_var_t state_extra_var = {
"__extra", CONFIG_TYPE_LINELIST,
- STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL
+ offsetof(sr_disk_state_t, ExtraLines), NULL
};
/* Configuration format of sr_disk_state_t. */
static const config_format_t state_format = {
sizeof(sr_disk_state_t),
SR_DISK_STATE_MAGIC,
- STRUCT_OFFSET(sr_disk_state_t, magic_),
+ offsetof(sr_disk_state_t, magic_),
NULL,
NULL,
state_vars,
@@ -133,7 +133,7 @@ get_voting_interval(void)
/* Given the time <b>now</b>, 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();
@@ -156,6 +156,42 @@ 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 (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 <b>now</b>.
* 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..837fa75392 100644
--- a/src/or/shared_random_state.h
+++ b/src/or/shared_random_state.h
@@ -121,11 +121,16 @@ 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);
+unsigned int sr_state_get_phase_duration(void);
+unsigned int sr_state_get_protocol_run_duration(void);
+
#ifdef SHARED_RANDOM_STATE_PRIVATE
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/or/statefile.c b/src/or/statefile.c
index d0606b3012..18111771da 100644
--- a/src/or/statefile.c
+++ b/src/or/statefile.c
@@ -55,7 +55,7 @@ static config_abbrev_t state_abbrevs_[] = {
/*XXXX these next two are duplicates or near-duplicates from config.c */
#define VAR(name,conftype,member,initvalue) \
- { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \
initvalue }
/** As VAR, but the option name and member name are the same. */
#define V(member,conftype,initvalue) \
@@ -85,6 +85,8 @@ static config_var_t state_vars_[] = {
VAR("TransportProxy", LINELIST_S, TransportProxies, NULL),
V(TransportProxies, LINELIST_V, NULL),
+ V(HidServRevCounter, LINELIST, NULL),
+
V(BWHistoryReadEnds, ISOTIME, NULL),
V(BWHistoryReadInterval, UINT, "900"),
V(BWHistoryReadValues, CSV, ""),
@@ -131,14 +133,14 @@ static int or_state_validate_cb(void *old_options, void *options,
/** "Extra" variable in the state that receives lines we can't parse. This
* lets us preserve options from versions of Tor newer than us. */
static config_var_t state_extra_var = {
- "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL
+ "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL
};
/** Configuration format for or_state_t. */
static const config_format_t state_format = {
sizeof(or_state_t),
OR_STATE_MAGIC,
- STRUCT_OFFSET(or_state_t, magic_),
+ offsetof(or_state_t, magic_),
state_abbrevs_,
NULL,
state_vars_,
diff --git a/src/or/torcert.c b/src/or/torcert.c
index 658e620ca5..69b157446a 100644
--- a/src/or/torcert.c
+++ b/src/or/torcert.c
@@ -393,7 +393,7 @@ rsa_ed25519_crosscert_check(const uint8_t *crosscert,
}
const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc);
- const uint64_t expiration_time = expiration_date * 3600;
+ const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600;
if (reject_if_expired_before < 0 ||
expiration_time < (uint64_t)reject_if_expired_before) {
diff --git a/src/test/ed25519_exts_ref.py b/src/test/ed25519_exts_ref.py
index af5010415e..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))
@@ -69,6 +67,11 @@ def signatureWithESK(m,h,pk):
def newSK():
return os.urandom(32)
+def random_scalar(entropy_f): # 0..L-1 inclusive
+ # reduce the bias to a safe level by generating 256 extra bits
+ oversized = int(binascii.hexlify(entropy_f(32+32)), 16)
+ return oversized % ell
+
# ------------------------------------------------------------
MSG = "This is extremely silly. But it is also incredibly serious business!"
@@ -126,6 +129,31 @@ class SelfTest(unittest.TestCase):
self._testSignatures(besk, bpk)
+ def testIdentity(self):
+ # Base point:
+ # B is the unique point (x, 4/5) \in E for which x is positive
+ By = 4 * inv(5)
+ Bx = xrecover(By)
+ B = [Bx % q,By % q]
+
+ # Get identity E by doing: E = l*B, where l is the group order
+ identity = scalarmult(B, ell)
+
+ # Get identity E by doing: E = l*A, where A is a random point
+ sk = newSK()
+ pk = decodepoint(publickey(sk))
+ identity2 = scalarmult(pk, ell)
+
+ # Check that identities match
+ assert(identity == identity2)
+ # Check that identity is the point (0,1)
+ assert(identity == [0L,1L])
+
+ # Check identity element: a*E = E, where a is a random scalar
+ scalar = random_scalar(os.urandom)
+ result = scalarmult(identity, scalar)
+ assert(result == identity == identity2)
+
# ------------------------------------------------------------
# From pprint.pprint([ binascii.b2a_hex(os.urandom(32)) for _ in xrange(8) ])
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",
};
/**
diff --git a/src/test/hs_build_address.py b/src/test/hs_build_address.py
new file mode 100644
index 0000000000..7be9c8b85a
--- /dev/null
+++ b/src/test/hs_build_address.py
@@ -0,0 +1,37 @@
+import sys
+import hashlib
+import struct
+import base64
+
+# Python 3.6+, the SHA3 is available in hashlib natively. Else this requires
+# the pysha3 package (pip install pysha3).
+if sys.version_info < (3, 6):
+ import sha3
+
+# Test vector to make sure the right sha3 version will be used. pysha3 < 1.0
+# used the old Keccak implementation. During the finalization of SHA3, NIST
+# changed the delimiter suffix from 0x01 to 0x06. The Keccak sponge function
+# stayed the same. pysha3 1.0 provides the previous Keccak hash, too.
+TEST_VALUE = "e167f68d6563d75bb25f3aa49c29ef612d41352dc00606de7cbd630bb2665f51"
+if TEST_VALUE != sha3.sha3_256(b"Hello World").hexdigest():
+ print("pysha3 version is < 1.0. Please install from:")
+ print("https://github.com/tiran/pysha3https://github.com/tiran/pysha3")
+ sys.exit(1)
+
+# Checksum is built like so:
+# CHECKSUM = SHA3(".onion checksum" || PUBKEY || VERSION)
+PREFIX = ".onion checksum".encode()
+# 32 bytes ed25519 pubkey.
+PUBKEY = ("\x42" * 32).encode()
+# Version 3 is proposal224
+VERSION = 3
+
+data = struct.pack('15s32sb', PREFIX, PUBKEY, VERSION)
+checksum = hashlib.sha3_256(data).digest()
+
+# Onion address is built like so:
+# onion_address = base32(PUBKEY || CHECKSUM || VERSION) + ".onion"
+address = struct.pack('!32s2sb', PUBKEY, checksum, VERSION)
+onion_addr = base64.b32encode(address).decode().lower()
+
+print("%s" % (onion_addr))
diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c
index 3f0d6a9413..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 *
@@ -15,8 +16,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));
@@ -94,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));
@@ -105,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));
@@ -119,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();
@@ -142,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/include.am b/src/test/include.am
index 29ba1ce7c9..7a9ecca907 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -34,6 +34,7 @@ endif
TESTS += src/test/test src/test/test-slow src/test/test-memwipe \
src/test/test_workqueue \
src/test/test_keygen.sh \
+ src/test/test_key_expiration.sh \
src/test/test-timers \
$(TESTSCRIPTS)
@@ -114,7 +115,12 @@ 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 \
src/test/test_hs_service.c \
+ src/test/test_hs_client.c \
src/test/test_hs_intropoint.c \
src/test/test_handles.c \
src/test/test_hs_cache.c \
@@ -270,6 +276,7 @@ noinst_HEADERS+= \
src/test/test.h \
src/test/test_helpers.h \
src/test/test_dir_common.h \
+ src/test/test_connection.h \
src/test/test_descriptors.inc \
src/test/example_extrainfo.inc \
src/test/failing_routerdescs.inc \
@@ -322,6 +329,7 @@ EXTRA_DIST += \
src/test/slownacl_curve25519.py \
src/test/zero_length_keys.sh \
src/test/test_keygen.sh \
+ src/test/test_key_expiration.sh \
src/test/test_zero_length_keys.sh \
src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.sh \
src/test/test-network.sh \
diff --git a/src/test/rend_test_helpers.c b/src/test/rend_test_helpers.c
index f7880046fb..095bfecf21 100644
--- a/src/test/rend_test_helpers.c
+++ b/src/test/rend_test_helpers.c
@@ -71,3 +71,19 @@ create_descriptor(rend_service_descriptor_t **generated, char **service_id,
crypto_pk_free(pk2);
}
+rend_data_t *
+mock_rend_data(const char *onion_address)
+{
+ rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
+ rend_data_t *rend_query = &v2_data->base_;
+ rend_query->version = 2;
+
+ strlcpy(v2_data->onion_address, onion_address,
+ sizeof(v2_data->onion_address));
+ v2_data->auth_type = REND_NO_AUTH;
+ rend_query->hsdirs_fp = smartlist_new();
+ smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
+ DIGEST_LEN));
+ return rend_query;
+}
+
diff --git a/src/test/rend_test_helpers.h b/src/test/rend_test_helpers.h
index 486adba436..6f0ef114de 100644
--- a/src/test/rend_test_helpers.h
+++ b/src/test/rend_test_helpers.h
@@ -10,6 +10,7 @@ void generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc,
char **service_id, int intro_points);
void create_descriptor(rend_service_descriptor_t **generated,
char **service_id, int intro_points);
+rend_data_t *mock_rend_data(const char *onion_address);
#endif
diff --git a/src/test/test.c b/src/test/test.c
index 68f5f90fd7..994a97ab00 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -142,7 +142,8 @@ test_bad_onion_handshake(void *arg)
/* Server: Case 1: the encrypted data is degenerate. */
memset(junk_buf, 0, sizeof(junk_buf));
- crypto_pk_public_hybrid_encrypt(pk, junk_buf2, TAP_ONIONSKIN_CHALLENGE_LEN,
+ crypto_pk_obsolete_public_hybrid_encrypt(pk,
+ junk_buf2, TAP_ONIONSKIN_CHALLENGE_LEN,
junk_buf, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1);
tt_int_op(-1, OP_EQ,
onion_skin_TAP_server_handshake(junk_buf2, pk, NULL,
@@ -1213,8 +1214,13 @@ struct testgroup_t testgroups[] = {
{ "extorport/", extorport_tests },
{ "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 },
{ "hs_service/", hs_service_tests },
+ { "hs_client/", hs_client_tests },
{ "hs_intropoint/", hs_intropoint_tests },
{ "introduce/", introduce_tests },
{ "keypin/", keypin_tests },
diff --git a/src/test/test.h b/src/test/test.h
index 6abaf39e6f..448d253fb5 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -207,8 +207,13 @@ 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_common_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[];
extern struct testcase_t introduce_tests[];
extern struct testcase_t keypin_tests[];
diff --git a/src/test/test_buffers.c b/src/test/test_buffers.c
index 07114a8571..3989a45480 100644
--- a/src/test/test_buffers.c
+++ b/src/test/test_buffers.c
@@ -838,10 +838,37 @@ test_buffers_find_contentlen(void *arg)
;
}
+static void
+test_buffer_peek_startswith(void *arg)
+{
+ (void)arg;
+ buf_t *buf;
+ buf = buf_new();
+ tt_ptr_op(buf, OP_NE, NULL);
+
+ tt_assert(peek_buf_startswith(buf, ""));
+ tt_assert(! peek_buf_startswith(buf, "X"));
+
+ write_to_buf("Tor", 3, buf);
+
+ tt_assert(peek_buf_startswith(buf, ""));
+ tt_assert(peek_buf_startswith(buf, "T"));
+ tt_assert(peek_buf_startswith(buf, "To"));
+ tt_assert(peek_buf_startswith(buf, "Tor"));
+ tt_assert(! peek_buf_startswith(buf, "Top"));
+ tt_assert(! peek_buf_startswith(buf, "For"));
+ tt_assert(! peek_buf_startswith(buf, "Tork"));
+ tt_assert(! peek_buf_startswith(buf, "Torpor"));
+
+ done:
+ buf_free(buf);
+}
+
struct testcase_t buffer_tests[] = {
{ "basic", test_buffers_basic, TT_FORK, NULL, NULL },
{ "copy", test_buffer_copy, TT_FORK, NULL, NULL },
{ "pullup", test_buffer_pullup, TT_FORK, NULL, NULL },
+ { "startswith", test_buffer_peek_startswith, 0, NULL, NULL },
{ "ext_or_cmd", test_buffer_ext_or_cmd, TT_FORK, NULL, NULL },
{ "allocation_tracking", test_buffer_allocation_tracking, TT_FORK,
NULL, NULL },
diff --git a/src/test/test_channel.c b/src/test/test_channel.c
index f5999b8e67..347aca7ecb 100644
--- a/src/test/test_channel.c
+++ b/src/test/test_channel.c
@@ -811,7 +811,7 @@ test_channel_incoming(void *arg)
tt_assert(ch->registered);
/* Open it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN);
/* Receive a fixed cell */
@@ -899,7 +899,7 @@ test_channel_lifecycle(void *arg)
tt_int_op(old_count, ==, test_cells_written);
/* Move it to OPEN and flush */
- channel_change_state(ch1, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch1);
/* Queue should drain */
tt_int_op(old_count + 1, ==, test_cells_written);
@@ -925,13 +925,13 @@ test_channel_lifecycle(void *arg)
tt_int_op(test_releases_count, ==, init_releases_count);
/* Move ch2 to OPEN */
- channel_change_state(ch2, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch2);
tt_int_op(test_doesnt_want_writes_count, ==,
init_doesnt_want_writes_count + 1);
tt_int_op(test_releases_count, ==, init_releases_count);
/* Move ch1 back to OPEN */
- channel_change_state(ch1, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch1);
tt_int_op(test_doesnt_want_writes_count, ==,
init_doesnt_want_writes_count + 1);
tt_int_op(test_releases_count, ==, init_releases_count);
@@ -1018,7 +1018,7 @@ test_channel_lifecycle_2(void *arg)
tt_assert(ch->registered);
/* Finish opening it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
/* Error exit from lower layer */
chan_test_error(ch);
@@ -1037,7 +1037,7 @@ test_channel_lifecycle_2(void *arg)
tt_assert(ch->registered);
/* Finish opening it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN);
/* Go to maintenance state */
@@ -1066,7 +1066,7 @@ test_channel_lifecycle_2(void *arg)
tt_assert(ch->registered);
/* Finish opening it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN);
/* Go to maintenance state */
@@ -1092,7 +1092,7 @@ test_channel_lifecycle_2(void *arg)
tt_assert(ch->registered);
/* Finish opening it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN);
/* Go to maintenance state */
@@ -1322,7 +1322,7 @@ test_channel_queue_impossible(void *arg)
* gets thrown away properly.
*/
test_chan_accept_cells = 1;
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_assert(test_cells_written == old_count);
tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0);
@@ -1350,7 +1350,7 @@ test_channel_queue_impossible(void *arg)
/* Let it drain and check that the bad entry is discarded */
test_chan_accept_cells = 1;
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_assert(test_cells_written == old_count);
tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0);
@@ -1378,7 +1378,7 @@ test_channel_queue_impossible(void *arg)
/* Let it drain and check that the bad entry is discarded */
test_chan_accept_cells = 1;
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_assert(test_cells_written == old_count);
tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0);
@@ -1406,7 +1406,7 @@ test_channel_queue_impossible(void *arg)
/* Let it drain and check that the bad entry is discarded */
test_chan_accept_cells = 1;
tor_capture_bugs_(1);
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_assert(test_cells_written == old_count);
tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0);
@@ -1463,7 +1463,7 @@ test_channel_queue_incoming(void *arg)
tt_assert(ch->registered);
/* Open it */
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN);
/* Assert that the incoming queue is empty */
@@ -1603,7 +1603,7 @@ test_channel_queue_size(void *arg)
/* Go to open */
old_count = test_cells_written;
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
/*
* It should try to write, but we aren't accepting cells right now, so
@@ -1706,7 +1706,7 @@ test_channel_write(void *arg)
* gets drained from the queue.
*/
test_chan_accept_cells = 1;
- channel_change_state(ch, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch);
tt_assert(test_cells_written == old_count + 1);
/*
diff --git a/src/test/test_config.c b/src/test/test_config.c
index 40d562a6f8..6b875a0a04 100644
--- a/src/test/test_config.c
+++ b/src/test/test_config.c
@@ -4816,6 +4816,7 @@ test_config_include_limit(void *data)
(void)data;
config_line_t *result = NULL;
+ char *torrc_path = NULL;
char *dir = tor_strdup(get_fname("test_include_limit"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -4825,8 +4826,7 @@ test_config_include_limit(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char torrc_path[PATH_MAX+1];
- tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
+ tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s",
torrc_path);
@@ -4837,6 +4837,7 @@ test_config_include_limit(void *data)
done:
config_free_lines(result);
+ tor_free(torrc_path);
tor_free(dir);
}
@@ -4847,6 +4848,7 @@ test_config_include_does_not_exist(void *data)
config_line_t *result = NULL;
char *dir = tor_strdup(get_fname("test_include_does_not_exist"));
+ char *missing_path = NULL;
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
@@ -4855,9 +4857,7 @@ test_config_include_does_not_exist(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char missing_path[PATH_MAX+1];
- tor_snprintf(missing_path, sizeof(missing_path), "%s"PATH_SEPARATOR"missing",
- dir);
+ tor_asprintf(&missing_path, "%s"PATH_SEPARATOR"missing", dir);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s",
missing_path);
@@ -4868,6 +4868,7 @@ test_config_include_does_not_exist(void *data)
done:
config_free_lines(result);
tor_free(dir);
+ tor_free(missing_path);
}
static void
@@ -4877,6 +4878,7 @@ test_config_include_error_in_included_file(void *data)
config_line_t *result = NULL;
char *dir = tor_strdup(get_fname("test_error_in_included_file"));
+ char *invalid_path = NULL;
tt_ptr_op(dir, OP_NE, NULL);
#ifdef _WIN32
@@ -4885,9 +4887,7 @@ test_config_include_error_in_included_file(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char invalid_path[PATH_MAX+1];
- tor_snprintf(invalid_path, sizeof(invalid_path), "%s"PATH_SEPARATOR"invalid",
- dir);
+ tor_asprintf(&invalid_path, "%s"PATH_SEPARATOR"invalid", dir);
tt_int_op(write_str_to_file(invalid_path, "unclosed \"", 0), OP_EQ, 0);
char torrc_contents[1000];
@@ -4900,6 +4900,7 @@ test_config_include_error_in_included_file(void *data)
done:
config_free_lines(result);
tor_free(dir);
+ tor_free(invalid_path);
}
static void
@@ -4908,6 +4909,8 @@ test_config_include_empty_file_folder(void *data)
(void)data;
config_line_t *result = NULL;
+ char *folder_path = NULL;
+ char *file_path = NULL;
char *dir = tor_strdup(get_fname("test_include_empty_file_folder"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -4917,17 +4920,13 @@ test_config_include_empty_file_folder(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char folder_path[PATH_MAX+1];
- tor_snprintf(folder_path, sizeof(folder_path), "%s"PATH_SEPARATOR"empty_dir",
- dir);
+ tor_asprintf(&folder_path, "%s"PATH_SEPARATOR"empty_dir", dir);
#ifdef _WIN32
tt_int_op(mkdir(folder_path), OP_EQ, 0);
#else
tt_int_op(mkdir(folder_path, 0700), OP_EQ, 0);
#endif
- char file_path[PATH_MAX+1];
- tor_snprintf(file_path, sizeof(file_path), "%s"PATH_SEPARATOR"empty_file",
- dir);
+ tor_asprintf(&file_path, "%s"PATH_SEPARATOR"empty_file", dir);
tt_int_op(write_str_to_file(file_path, "", 0), OP_EQ, 0);
char torrc_contents[1000];
@@ -4944,6 +4943,8 @@ test_config_include_empty_file_folder(void *data)
done:
config_free_lines(result);
+ tor_free(folder_path);
+ tor_free(file_path);
tor_free(dir);
}
@@ -4953,6 +4954,7 @@ test_config_include_recursion_before_after(void *data)
(void)data;
config_line_t *result = NULL;
+ char *torrc_path = NULL;
char *dir = tor_strdup(get_fname("test_include_recursion_before_after"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -4962,8 +4964,7 @@ test_config_include_recursion_before_after(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char torrc_path[PATH_MAX+1];
- tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
+ tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir);
char file_contents[1000];
const int limit = MAX_INCLUDE_RECURSION_LEVEL;
@@ -4982,9 +4983,10 @@ test_config_include_recursion_before_after(void *data)
}
if (i > 1) {
- char file_path[PATH_MAX+1];
- tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i);
+ char *file_path = NULL;
+ tor_asprintf(&file_path, "%s%d", torrc_path, i);
tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0);
+ tor_free(file_path);
}
}
@@ -5008,6 +5010,7 @@ test_config_include_recursion_before_after(void *data)
done:
config_free_lines(result);
tor_free(dir);
+ tor_free(torrc_path);
}
static void
@@ -5016,6 +5019,7 @@ test_config_include_recursion_after_only(void *data)
(void)data;
config_line_t *result = NULL;
+ char *torrc_path = NULL;
char *dir = tor_strdup(get_fname("test_include_recursion_after_only"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -5025,8 +5029,7 @@ test_config_include_recursion_after_only(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char torrc_path[PATH_MAX+1];
- tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir);
+ tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir);
char file_contents[1000];
const int limit = MAX_INCLUDE_RECURSION_LEVEL;
@@ -5045,9 +5048,10 @@ test_config_include_recursion_after_only(void *data)
}
if (i > 1) {
- char file_path[PATH_MAX+1];
- tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i);
+ char *file_path = NULL;
+ tor_asprintf(&file_path, "%s%d", torrc_path, i);
tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0);
+ tor_free(file_path);
}
}
@@ -5071,6 +5075,7 @@ test_config_include_recursion_after_only(void *data)
done:
config_free_lines(result);
tor_free(dir);
+ tor_free(torrc_path);
}
static void
@@ -5079,6 +5084,9 @@ test_config_include_folder_order(void *data)
(void)data;
config_line_t *result = NULL;
+ char *torrcd = NULL;
+ char *path = NULL;
+ char *path2 = NULL;
char *dir = tor_strdup(get_fname("test_include_folder_order"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -5088,8 +5096,7 @@ test_config_include_folder_order(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char torrcd[PATH_MAX+1];
- tor_snprintf(torrcd, sizeof(torrcd), "%s"PATH_SEPARATOR"%s", dir, "torrc.d");
+ tor_asprintf(&torrcd, "%s"PATH_SEPARATOR"%s", dir, "torrc.d");
#ifdef _WIN32
tt_int_op(mkdir(torrcd), OP_EQ, 0);
@@ -5098,9 +5105,7 @@ test_config_include_folder_order(void *data)
#endif
// test that files in subfolders are ignored
- char path[PATH_MAX+1];
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd,
- "subfolder");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "subfolder");
#ifdef _WIN32
tt_int_op(mkdir(path), OP_EQ, 0);
@@ -5108,27 +5113,31 @@ test_config_include_folder_order(void *data)
tt_int_op(mkdir(path, 0700), OP_EQ, 0);
#endif
- char path2[PATH_MAX+1];
- tor_snprintf(path2, sizeof(path2), "%s"PATH_SEPARATOR"%s", path,
- "01_ignore");
+ tor_asprintf(&path2, "%s"PATH_SEPARATOR"%s", path, "01_ignore");
tt_int_op(write_str_to_file(path2, "ShouldNotSee 1\n", 0), OP_EQ, 0);
+ tor_free(path);
// test that files starting with . are ignored
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, ".dot");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, ".dot");
tt_int_op(write_str_to_file(path, "ShouldNotSee 2\n", 0), OP_EQ, 0);
+ tor_free(path);
// test file order
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "01_1st");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "01_1st");
tt_int_op(write_str_to_file(path, "Test 1\n", 0), OP_EQ, 0);
+ tor_free(path);
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd");
tt_int_op(write_str_to_file(path, "Test 2\n", 0), OP_EQ, 0);
+ tor_free(path);
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd");
tt_int_op(write_str_to_file(path, "Test 3\n", 0), OP_EQ, 0);
+ tor_free(path);
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th");
tt_int_op(write_str_to_file(path, "Test 4\n", 0), OP_EQ, 0);
+ tor_free(path);
char torrc_contents[1000];
tor_snprintf(torrc_contents, sizeof(torrc_contents),
@@ -5154,6 +5163,9 @@ test_config_include_folder_order(void *data)
done:
config_free_lines(result);
+ tor_free(torrcd);
+ tor_free(path);
+ tor_free(path2);
tor_free(dir);
}
@@ -5285,6 +5297,7 @@ test_config_include_flag_torrc_only(void *data)
(void)data;
char *errmsg = NULL;
+ char *path = NULL;
char *dir = tor_strdup(get_fname("test_include_flag_torrc_only"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -5294,8 +5307,7 @@ test_config_include_flag_torrc_only(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char path[PATH_MAX+1];
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", dir, "dummy");
tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0);
char conf_empty[1000];
@@ -5315,6 +5327,7 @@ test_config_include_flag_torrc_only(void *data)
done:
tor_free(errmsg);
+ tor_free(path);
tor_free(dir);
}
@@ -5324,6 +5337,7 @@ test_config_include_flag_defaults_only(void *data)
(void)data;
char *errmsg = NULL;
+ char *path = NULL;
char *dir = tor_strdup(get_fname("test_include_flag_defaults_only"));
tt_ptr_op(dir, OP_NE, NULL);
@@ -5333,8 +5347,7 @@ test_config_include_flag_defaults_only(void *data)
tt_int_op(mkdir(dir, 0700), OP_EQ, 0);
#endif
- char path[PATH_MAX+1];
- tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy");
+ tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", dir, "dummy");
tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0);
char conf_empty[1000];
@@ -5354,6 +5367,7 @@ test_config_include_flag_defaults_only(void *data)
done:
tor_free(errmsg);
+ tor_free(path);
tor_free(dir);
}
diff --git a/src/test/test_connection.c b/src/test/test_connection.c
index 7e5193b203..f2529026f9 100644
--- a/src/test/test_connection.c
+++ b/src/test/test_connection.c
@@ -17,9 +17,8 @@
#include "rendcache.h"
#include "directory.h"
-static void test_conn_lookup_addr_helper(const char *address,
- int family,
- tor_addr_t *addr);
+#include "test_connection.h"
+#include "test_helpers.h"
static void * test_conn_get_basic_setup(const struct testcase_t *tc);
static int test_conn_get_basic_teardown(const struct testcase_t *tc,
@@ -62,48 +61,7 @@ static int test_conn_get_rsrc_teardown(const struct testcase_t *tc,
#define TEST_CONN_UNATTACHED_STATE (AP_CONN_STATE_CIRCUIT_WAIT)
#define TEST_CONN_ATTACHED_STATE (AP_CONN_STATE_CONNECT_WAIT)
-#define TEST_CONN_FD_INIT 50
-static int mock_connection_connect_sockaddr_called = 0;
-static int fake_socket_number = TEST_CONN_FD_INIT;
-
-static int
-mock_connection_connect_sockaddr(connection_t *conn,
- const struct sockaddr *sa,
- socklen_t sa_len,
- const struct sockaddr *bindaddr,
- socklen_t bindaddr_len,
- int *socket_error)
-{
- (void)sa_len;
- (void)bindaddr;
- (void)bindaddr_len;
-
- tor_assert(conn);
- tor_assert(sa);
- tor_assert(socket_error);
-
- mock_connection_connect_sockaddr_called++;
-
- conn->s = fake_socket_number++;
- tt_assert(SOCKET_OK(conn->s));
- /* We really should call tor_libevent_initialize() here. Because we don't,
- * we are relying on other parts of the code not checking if the_event_base
- * (and therefore event->ev_base) is NULL. */
- tt_assert(connection_add_connecting(conn) == 0);
-
- done:
- /* Fake "connected" status */
- return 1;
-}
-
-static int
-fake_close_socket(evutil_socket_t sock)
-{
- (void)sock;
- return 0;
-}
-
-static void
+void
test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
{
int rv = 0;
@@ -122,51 +80,6 @@ test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr)
tor_addr_make_null(addr, TEST_CONN_FAMILY);
}
-static connection_t *
-test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
-{
- connection_t *conn = NULL;
- tor_addr_t addr;
- int socket_err = 0;
- int in_progress = 0;
-
- MOCK(connection_connect_sockaddr,
- mock_connection_connect_sockaddr);
- MOCK(tor_close_socket, fake_close_socket);
-
- init_connection_lists();
-
- conn = connection_new(type, TEST_CONN_FAMILY);
- tt_assert(conn);
-
- test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
- tt_assert(!tor_addr_is_null(&addr));
-
- tor_addr_copy_tight(&conn->addr, &addr);
- conn->port = TEST_CONN_PORT;
- mock_connection_connect_sockaddr_called = 0;
- in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
- TEST_CONN_PORT, &socket_err);
- tt_assert(mock_connection_connect_sockaddr_called == 1);
- tt_assert(!socket_err);
- tt_assert(in_progress == 0 || in_progress == 1);
-
- /* fake some of the attributes so the connection looks OK */
- conn->state = state;
- conn->purpose = purpose;
- assert_connection_ok(conn, time(NULL));
-
- UNMOCK(connection_connect_sockaddr);
- UNMOCK(tor_close_socket);
- return conn;
-
- /* On failure */
- done:
- UNMOCK(connection_connect_sockaddr);
- UNMOCK(tor_close_socket);
- return NULL;
-}
-
static void *
test_conn_get_basic_setup(const struct testcase_t *tc)
{
diff --git a/src/test/test_connection.h b/src/test/test_connection.h
new file mode 100644
index 0000000000..392783b53b
--- /dev/null
+++ b/src/test/test_connection.h
@@ -0,0 +1,13 @@
+/* Copyright (c) 2014-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/** Some constants used by test_connection and helpers */
+#define TEST_CONN_FAMILY (AF_INET)
+#define TEST_CONN_ADDRESS "127.0.0.1"
+#define TEST_CONN_PORT (12345)
+#define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345"
+#define TEST_CONN_FD_INIT 50
+
+void test_conn_lookup_addr_helper(const char *address,
+ int family, tor_addr_t *addr);
+
diff --git a/src/test/test_containers.c b/src/test/test_containers.c
index 54484a2a91..0005dcb02c 100644
--- a/src/test/test_containers.c
+++ b/src/test/test_containers.c
@@ -681,7 +681,7 @@ test_container_pqueue(void *arg)
{
smartlist_t *sl = smartlist_new();
int (*cmp)(const void *, const void*);
- const int offset = STRUCT_OFFSET(pq_entry_t, idx);
+ const int offset = offsetof(pq_entry_t, idx);
#define ENTRY(s) pq_entry_t s = { #s, -1 }
ENTRY(cows);
ENTRY(zebras);
diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c
index ec9d4e2709..11200b4e98 100644
--- a/src/test/test_crypto.c
+++ b/src/test/test_crypto.c
@@ -1252,10 +1252,10 @@ test_crypto_pk(void *arg)
for (i = 85; i < 140; ++i) {
memset(data2,0,1024);
memset(data3,0,1024);
- len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2),
+ len = crypto_pk_obsolete_public_hybrid_encrypt(pk1,data2,sizeof(data2),
data1,i,PK_PKCS1_OAEP_PADDING,0);
tt_int_op(len, OP_GE, 0);
- len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3),
+ len = crypto_pk_obsolete_private_hybrid_decrypt(pk1,data3,sizeof(data3),
data2,len,PK_PKCS1_OAEP_PADDING,1);
tt_int_op(len,OP_EQ, i);
tt_mem_op(data1,OP_EQ, data3,i);
@@ -2170,6 +2170,9 @@ test_crypto_ed25519_simple(void *arg)
tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub1, &sec1));
tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub2, &sec1));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, 0);
+ tt_int_op(ed25519_validate_pubkey(&pub2), OP_EQ, 0);
+
tt_mem_op(pub1.pubkey, OP_EQ, pub2.pubkey, sizeof(pub1.pubkey));
tt_assert(ed25519_pubkey_eq(&pub1, &pub2));
tt_assert(ed25519_pubkey_eq(&pub1, &pub1));
@@ -2541,6 +2544,39 @@ test_crypto_ed25519_blinding(void *arg)
;
}
+/** Test that our blinding functions will fail if we pass them bad pubkeys */
+static void
+test_crypto_ed25519_blinding_fail(void *arg)
+{
+ int retval;
+ uint8_t param[32] = {2};
+ ed25519_public_key_t pub;
+ ed25519_public_key_t pub_blinded;
+
+ (void)arg;
+
+ /* This point is not on the curve: the blind routines should fail */
+ const char badkey[] =
+ "e19c65de75c68cf3b7643ea732ba9eb1a3d20d6d57ba223c2ece1df66feb5af0";
+ retval = base16_decode((char*)pub.pubkey, sizeof(pub.pubkey),
+ badkey, strlen(badkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub.pubkey));
+ retval = ed25519_public_blind(&pub_blinded, &pub, param);
+ tt_int_op(retval, OP_EQ, -1);
+
+ /* This point is legit: blind routines should be happy */
+ const char goodkey[] =
+ "4ba2e44760dff4c559ef3c38768c1c14a8a54740c782c8d70803e9d6e3ad8794";
+ retval = base16_decode((char*)pub.pubkey, sizeof(pub.pubkey),
+ goodkey, strlen(goodkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub.pubkey));
+ retval = ed25519_public_blind(&pub_blinded, &pub, param);
+ tt_int_op(retval, OP_EQ, 0);
+
+ done:
+ ;
+}
+
static void
test_crypto_ed25519_testvectors(void *arg)
{
@@ -2832,6 +2868,67 @@ crypto_rand_check_failure_mode_predict(void)
#undef FAILURE_MODE_BUFFER_SIZE
+/** Test that our ed25519 validation function rejects evil public keys and
+ * accepts good ones. */
+static void
+test_crypto_ed25519_validation(void *arg)
+{
+ (void) arg;
+
+ int retval;
+ ed25519_public_key_t pub1;
+
+ /* See https://lists.torproject.org/pipermail/tor-dev/2017-April/012230.html
+ for a list of points with torsion components in ed25519. */
+
+ { /* Point with torsion component (order 8l) */
+ const char badkey[] =
+ "300ef2e64e588e1df55b48e4da0416ffb64cc85d5b00af6463d5cc6c2b1c185e";
+ retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey),
+ badkey, strlen(badkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1);
+ }
+
+ { /* Point with torsion component (order 4l) */
+ const char badkey[] =
+ "f43e3a046db8749164c6e69b193f1e942c7452e7d888736f40b98093d814d5e7";
+ retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey),
+ badkey, strlen(badkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1);
+ }
+
+ { /* Point with torsion component (order 2l) */
+ const char badkey[] =
+ "c9fff3af0471c28e33e98c2043e44f779d0427b1e37c521a6bddc011ed1869af";
+ retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey),
+ badkey, strlen(badkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1);
+ }
+
+ { /* This point is not even on the curve */
+ const char badkey[] =
+ "e19c65de75c68cf3b7643ea732ba9eb1a3d20d6d57ba223c2ece1df66feb5af0";
+ retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey),
+ badkey, strlen(badkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1);
+ }
+
+ { /* This one is a good key */
+ const char goodkey[] =
+ "4ba2e44760dff4c559ef3c38768c1c14a8a54740c782c8d70803e9d6e3ad8794";
+ retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey),
+ goodkey, strlen(goodkey));
+ tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey));
+ tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, 0);
+ }
+
+ done: ;
+}
+
static void
test_crypto_failure_modes(void *arg)
{
@@ -2917,7 +3014,9 @@ struct testcase_t crypto_tests[] = {
ED25519_TEST(encode, 0),
ED25519_TEST(convert, 0),
ED25519_TEST(blinding, 0),
+ ED25519_TEST(blinding_fail, 0),
ED25519_TEST(testvectors, 0),
+ ED25519_TEST(validation, 0),
{ "ed25519_storage", test_crypto_ed25519_storage, 0, NULL, NULL },
{ "siphash", test_crypto_siphash, 0, NULL, NULL },
{ "failure_modes", test_crypto_failure_modes, TT_FORK, NULL, NULL },
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index a9d9cba7df..e31917056e 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -1785,7 +1785,7 @@ test_dir_param_voting_lookup(void *arg)
dirvote_get_intermediate_param_value(lst, "moomin", -100));
tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1);
tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ,
- "!(n_found > 1)");
+ "n_found == 0");
tor_end_capture_bugs_();
/* There is no 'fred=', so that is treated as not existing. */
tt_int_op(-100, OP_EQ,
@@ -4109,40 +4109,69 @@ test_dir_download_status_schedule(void *arg)
}
static void
-test_dir_download_status_random_backoff(void *arg)
+download_status_random_backoff_helper(int min_delay, int max_delay)
{
download_status_t dls_random =
{ 0, 0, 0, DL_SCHED_GENERIC, DL_WANT_AUTHORITY,
DL_SCHED_INCREMENT_FAILURE, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 };
int increment = -1;
- int old_increment;
+ int old_increment = -1;
time_t current_time = time(NULL);
- const int min_delay = 0;
- const int max_delay = 1000000;
-
- (void)arg;
+ const int exponent = DIR_DEFAULT_RANDOM_MULTIPLIER + 1;
/* Check the random backoff cases */
- old_increment = 0;
do {
increment = download_status_schedule_get_delay(&dls_random,
NULL,
min_delay, max_delay,
current_time);
+
+ log_debug(LD_DIR, "Min: %d, Max: %d, Inc: %d, Old Inc: %d",
+ min_delay, max_delay, increment, old_increment);
+
+ /* Regression test for 20534 and friends
+ * increment must always increase after the first */
+ if (dls_random.last_backoff_position > 0 && max_delay > 0) {
+ /* Always increment the exponential backoff */
+ tt_int_op(increment, OP_GE, 1);
+ }
+
/* Test */
tt_int_op(increment, OP_GE, min_delay);
tt_int_op(increment, OP_LE, max_delay);
- tt_int_op(increment, OP_GE, old_increment);
- /* We at most quadruple, and maybe add one */
- tt_int_op(increment, OP_LE, 4 * old_increment + 1);
+ if (dls_random.last_backoff_position == 0) {
+ /* regression tests for 17750
+ * Always use the minimum delay for the first increment */
+ tt_int_op(increment, OP_EQ, min_delay);
+ } else {
+ /* It's times like these I'd love a good saturating arithmetic
+ * implementation */
+ int min_inc = INT_MAX;
+ if (old_increment <= INT_MAX - 1) {
+ min_inc = old_increment + 1;
+ }
+
+ int max_inc = INT_MAX;
+ if (old_increment <= (INT_MAX - 1)/exponent) {
+ max_inc = (exponent * old_increment) + 1;
+ }
+
+ /* Regression test for 20534 and friends:
+ * increment must always increase after the first */
+ tt_int_op(increment, OP_GE, min_inc);
+ /* We at most quadruple, and always add one */
+ tt_int_op(increment, OP_LE, max_inc);
+ }
/* Advance */
- current_time += increment;
++(dls_random.n_download_attempts);
++(dls_random.n_download_failures);
/* Try another maybe */
old_increment = increment;
+ if (increment >= max_delay)
+ current_time += increment;
+
} while (increment < max_delay);
done:
@@ -4150,6 +4179,27 @@ test_dir_download_status_random_backoff(void *arg)
}
static void
+test_dir_download_status_random_backoff(void *arg)
+{
+ (void)arg;
+
+ /* Do a standard test */
+ download_status_random_backoff_helper(0, 1000000);
+ /* Regression test for 20534 and friends:
+ * try tighter bounds */
+ download_status_random_backoff_helper(0, 100);
+ /* regression tests for 17750: initial delay */
+ download_status_random_backoff_helper(10, 1000);
+ download_status_random_backoff_helper(20, 30);
+
+ /* Pathological cases */
+ download_status_random_backoff_helper(0, 0);
+ download_status_random_backoff_helper(1, 1);
+ download_status_random_backoff_helper(0, INT_MAX);
+ download_status_random_backoff_helper(INT_MAX/2, INT_MAX);
+}
+
+static void
test_dir_download_status_increment(void *arg)
{
(void)arg;
@@ -4161,32 +4211,97 @@ test_dir_download_status_increment(void *arg)
DL_WANT_ANY_DIRSERVER,
DL_SCHED_INCREMENT_ATTEMPT,
DL_SCHED_DETERMINISTIC, 0, 0 };
+ download_status_t dls_exp = { 0, 0, 0, DL_SCHED_GENERIC,
+ DL_WANT_ANY_DIRSERVER,
+ DL_SCHED_INCREMENT_ATTEMPT,
+ DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 };
+ int no_delay = 0;
int delay0 = -1;
int delay1 = -1;
int delay2 = -1;
smartlist_t *schedule = smartlist_new();
+ smartlist_t *schedule_no_initial_delay = smartlist_new();
or_options_t test_options;
time_t next_at = TIME_MAX;
time_t current_time = time(NULL);
- /* Provide some values for the schedule */
+ /* Provide some values for the schedules */
delay0 = 10;
delay1 = 99;
delay2 = 20;
- /* Make the schedule */
+ /* Make the schedules */
smartlist_add(schedule, (void *)&delay0);
smartlist_add(schedule, (void *)&delay1);
smartlist_add(schedule, (void *)&delay2);
+ smartlist_add(schedule_no_initial_delay, (void *)&no_delay);
+ smartlist_add(schedule_no_initial_delay, (void *)&delay1);
+ smartlist_add(schedule_no_initial_delay, (void *)&delay2);
+
/* Put it in the options */
mock_options = &test_options;
reset_options(mock_options, &mock_get_options_calls);
- mock_options->TestingClientDownloadSchedule = schedule;
mock_options->TestingBridgeDownloadSchedule = schedule;
+ mock_options->TestingClientDownloadSchedule = schedule;
MOCK(get_options, mock_get_options);
+ /* Check that the initial value of the schedule is the first value used,
+ * whether or not it was reset before being used */
+
+ /* regression test for 17750: no initial delay */
+ mock_options->TestingClientDownloadSchedule = schedule_no_initial_delay;
+ mock_get_options_calls = 0;
+ /* we really want to test that it's equal to time(NULL) + delay0, but that's
+ * an unrealiable test, because time(NULL) might change. */
+ tt_assert(download_status_get_next_attempt_at(&dls_failure)
+ >= current_time + no_delay);
+ tt_assert(download_status_get_next_attempt_at(&dls_failure)
+ != TIME_MAX);
+ tt_assert(download_status_get_n_failures(&dls_failure) == 0);
+ tt_assert(download_status_get_n_attempts(&dls_failure) == 0);
+ tt_assert(mock_get_options_calls >= 1);
+
+ /* regression test for 17750: initial delay */
+ mock_options->TestingClientDownloadSchedule = schedule;
+ mock_get_options_calls = 0;
+ /* we really want to test that it's equal to time(NULL) + delay0, but that's
+ * an unrealiable test, because time(NULL) might change. */
+ tt_assert(download_status_get_next_attempt_at(&dls_failure)
+ >= current_time + delay0);
+ tt_assert(download_status_get_next_attempt_at(&dls_failure)
+ != TIME_MAX);
+ tt_assert(download_status_get_n_failures(&dls_failure) == 0);
+ tt_assert(download_status_get_n_attempts(&dls_failure) == 0);
+ tt_assert(mock_get_options_calls >= 1);
+
+ /* regression test for 17750: exponential, no initial delay */
+ mock_options->TestingClientDownloadSchedule = schedule_no_initial_delay;
+ mock_get_options_calls = 0;
+ /* we really want to test that it's equal to time(NULL) + delay0, but that's
+ * an unrealiable test, because time(NULL) might change. */
+ tt_assert(download_status_get_next_attempt_at(&dls_exp)
+ >= current_time + no_delay);
+ tt_assert(download_status_get_next_attempt_at(&dls_exp)
+ != TIME_MAX);
+ tt_assert(download_status_get_n_failures(&dls_exp) == 0);
+ tt_assert(download_status_get_n_attempts(&dls_exp) == 0);
+ tt_assert(mock_get_options_calls >= 1);
+
+ /* regression test for 17750: exponential, initial delay */
+ mock_options->TestingClientDownloadSchedule = schedule;
+ mock_get_options_calls = 0;
+ /* we really want to test that it's equal to time(NULL) + delay0, but that's
+ * an unrealiable test, because time(NULL) might change. */
+ tt_assert(download_status_get_next_attempt_at(&dls_exp)
+ >= current_time + delay0);
+ tt_assert(download_status_get_next_attempt_at(&dls_exp)
+ != TIME_MAX);
+ tt_assert(download_status_get_n_failures(&dls_exp) == 0);
+ tt_assert(download_status_get_n_attempts(&dls_exp) == 0);
+ tt_assert(mock_get_options_calls >= 1);
+
/* Check that a failure reset works */
mock_get_options_calls = 0;
download_status_reset(&dls_failure);
@@ -4313,7 +4428,7 @@ test_dir_download_status_increment(void *arg)
tt_assert(next_at == TIME_MAX);
tt_assert(download_status_get_n_failures(&dls_attempt) == 1);
tt_assert(download_status_get_n_attempts(&dls_attempt) == 0);
- tt_assert(mock_get_options_calls == 0);
+ tt_assert(mock_get_options_calls >= 1);
/* Check that an attempt reset works */
mock_get_options_calls = 0;
@@ -4440,6 +4555,7 @@ test_dir_download_status_increment(void *arg)
done:
/* the pointers in schedule are allocated on the stack */
smartlist_free(schedule);
+ smartlist_free(schedule_no_initial_delay);
UNMOCK(get_options);
mock_options = NULL;
mock_get_options_calls = 0;
diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c
index 9fada5a675..e885d27815 100644
--- a/src/test/test_helpers.c
+++ b/src/test/test_helpers.c
@@ -7,16 +7,25 @@
*/
#define ROUTERLIST_PRIVATE
+#define CONFIG_PRIVATE
+#define CONNECTION_PRIVATE
+#define MAIN_PRIVATE
+
#include "orconfig.h"
#include "or.h"
+#include "buffers.h"
+#include "config.h"
+#include "confparse.h"
+#include "connection.h"
+#include "main.h"
+#include "nodelist.h"
#include "relay.h"
#include "routerlist.h"
-#include "nodelist.h"
-#include "buffers.h"
#include "test.h"
#include "test_helpers.h"
+#include "test_connection.h"
#ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS
DISABLE_GCC_WARNING(overlength-strings)
@@ -143,3 +152,128 @@ mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
return tor_addr_lookup__real(name, family, out);
}
+/*********** Helper funcs for making new connections/streams *****************/
+
+/* Helper for test_conn_get_connection() */
+static int
+fake_close_socket(evutil_socket_t sock)
+{
+ (void)sock;
+ return 0;
+}
+
+static int mock_connection_connect_sockaddr_called = 0;
+static int fake_socket_number = TEST_CONN_FD_INIT;
+
+/* Helper for test_conn_get_connection() */
+static int
+mock_connection_connect_sockaddr(connection_t *conn,
+ const struct sockaddr *sa,
+ socklen_t sa_len,
+ const struct sockaddr *bindaddr,
+ socklen_t bindaddr_len,
+ int *socket_error)
+{
+ (void)sa_len;
+ (void)bindaddr;
+ (void)bindaddr_len;
+
+ tor_assert(conn);
+ tor_assert(sa);
+ tor_assert(socket_error);
+
+ mock_connection_connect_sockaddr_called++;
+
+ conn->s = fake_socket_number++;
+ tt_assert(SOCKET_OK(conn->s));
+ /* We really should call tor_libevent_initialize() here. Because we don't,
+ * we are relying on other parts of the code not checking if the_event_base
+ * (and therefore event->ev_base) is NULL. */
+ tt_assert(connection_add_connecting(conn) == 0);
+
+ done:
+ /* Fake "connected" status */
+ return 1;
+}
+
+/** Create and return a new connection/stream */
+connection_t *
+test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose)
+{
+ connection_t *conn = NULL;
+ tor_addr_t addr;
+ int socket_err = 0;
+ int in_progress = 0;
+
+ MOCK(connection_connect_sockaddr,
+ mock_connection_connect_sockaddr);
+ MOCK(tor_close_socket, fake_close_socket);
+
+ init_connection_lists();
+
+ conn = connection_new(type, TEST_CONN_FAMILY);
+ tt_assert(conn);
+
+ test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr);
+ tt_assert(!tor_addr_is_null(&addr));
+
+ tor_addr_copy_tight(&conn->addr, &addr);
+ conn->port = TEST_CONN_PORT;
+ mock_connection_connect_sockaddr_called = 0;
+ in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr,
+ TEST_CONN_PORT, &socket_err);
+ tt_assert(mock_connection_connect_sockaddr_called == 1);
+ tt_assert(!socket_err);
+ tt_assert(in_progress == 0 || in_progress == 1);
+
+ /* fake some of the attributes so the connection looks OK */
+ conn->state = state;
+ conn->purpose = purpose;
+ assert_connection_ok(conn, time(NULL));
+
+ UNMOCK(connection_connect_sockaddr);
+ UNMOCK(tor_close_socket);
+ return conn;
+
+ /* On failure */
+ done:
+ UNMOCK(connection_connect_sockaddr);
+ UNMOCK(tor_close_socket);
+ return NULL;
+}
+
+/* Helper function to parse a set of torrc options in a text format and return
+ * a newly allocated or_options_t object containing the configuration. On
+ * error, NULL is returned indicating that the conf couldn't be parsed
+ * properly. */
+or_options_t *
+helper_parse_options(const char *conf)
+{
+ int ret = 0;
+ char *msg = NULL;
+ or_options_t *opt = NULL;
+ config_line_t *line = NULL;
+
+ /* Kind of pointless to call this with a NULL value. */
+ tt_assert(conf);
+
+ opt = options_new();
+ tt_assert(opt);
+ ret = config_get_lines(conf, &line, 1);
+ if (ret != 0) {
+ goto done;
+ }
+ ret = config_assign(&options_format, opt, line, 0, &msg);
+ if (ret != 0) {
+ goto done;
+ }
+
+ done:
+ config_free_lines(line);
+ if (ret != 0) {
+ or_options_free(opt);
+ opt = NULL;
+ }
+ return opt;
+}
+
diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h
index 4621631cc1..847104a40a 100644
--- a/src/test/test_helpers.h
+++ b/src/test/test_helpers.h
@@ -1,9 +1,11 @@
-/* Copyright (c) 2014-2017, The Tor Project, Inc. */
+/* Copyright (c) 2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#ifndef TOR_TEST_HELPERS_H
#define TOR_TEST_HELPERS_H
+#include "or.h"
+
const char *get_yesterday_date_str(void);
circuit_t * dummy_origin_circuit_new(int num_cells);
@@ -20,7 +22,11 @@ void connection_write_to_buf_mock(const char *string, size_t len,
int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
uint16_t family, tor_addr_t *out);
+connection_t *test_conn_get_connection(uint8_t state,
+ uint8_t type, uint8_t purpose);
+or_options_t *helper_parse_options(const char *conf);
+
extern const char TEST_DESCRIPTORS[];
-#endif
+#endif /* TOR_TEST_HELPERS_H */
diff --git a/src/test/test_hs.c b/src/test/test_hs.c
index 5aae6c5b97..48acd91808 100644
--- a/src/test/test_hs.c
+++ b/src/test/test_hs.c
@@ -8,7 +8,9 @@
#define CONTROL_PRIVATE
#define CIRCUITBUILD_PRIVATE
+#define RENDCOMMON_PRIVATE
#define RENDSERVICE_PRIVATE
+#define HS_SERVICE_PRIVATE
#include "or.h"
#include "test.h"
@@ -32,8 +34,9 @@
#define STR_HSDIR_NONE_EXIST_LONGNAME \
"$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
-/* DuckDuckGo descriptor as an example. */
-static const char *hs_desc_content = "\
+/* DuckDuckGo descriptor as an example. This one has extra "\r" at the end so
+ * the control port is happy. */
+static const char *hs_desc_content_control = "\
rendezvous-service-descriptor g5ojobzupf275beh5ra72uyhb3dkpxwg\r\n\
version 2\r\n\
permanent-key\r\n\
@@ -94,6 +97,68 @@ PcftsZf2ztN0sbNCtPgDL3d0PqvxY3iHTQAI8EbaGq/IAJUZ8U4y963dD5+Bn6JQ\r\n\
myE3ctmh0vy5+QxSiRjmQBkuEpCyks7LvWvHYrhnmcg=\r\n\
-----END SIGNATURE-----";
+/* DuckDuckGo descriptor as an example. */
+static const char *hs_desc_content = "\
+rendezvous-service-descriptor g5ojobzupf275beh5ra72uyhb3dkpxwg\n\
+version 2\n\
+permanent-key\n\
+-----BEGIN RSA PUBLIC KEY-----\n\
+MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE\n\
+aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg\n\
+I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=\n\
+-----END RSA PUBLIC KEY-----\n\
+secret-id-part anmjoxxwiupreyajjt5yasimfmwcnxlf\n\
+publication-time 2015-03-11 19:00:00\n\
+protocol-versions 2,3\n\
+introduction-points\n\
+-----BEGIN MESSAGE-----\n\
+aW50cm9kdWN0aW9uLXBvaW50IDd1bnd4cmg2dG5kNGh6eWt1Z3EzaGZzdHduc2ll\n\
+cmhyCmlwLWFkZHJlc3MgMTg4LjEzOC4xMjEuMTE4Cm9uaW9uLXBvcnQgOTAwMQpv\n\
+bmlvbi1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dC\n\
+QUxGRVVyeVpDbk9ROEhURmV5cDVjMTRObWVqL1BhekFLTTBxRENTNElKUWh0Y3g1\n\
+NXpRSFdOVWIKQ2hHZ0JqR1RjV3ZGRnA0N3FkdGF6WUZhVXE2c0lQKzVqeWZ5b0Q4\n\
+UmJ1bzBwQmFWclJjMmNhYUptWWM0RDh6Vgpuby9sZnhzOVVaQnZ1cWY4eHIrMDB2\n\
+S0JJNmFSMlA2OE1WeDhrMExqcUpUU2RKOE9idm9yQWdNQkFBRT0KLS0tLS1FTkQg\n\
+UlNBIFBVQkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQ\n\
+VUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTnJHb0ozeTlHNXQzN2F2ekI1cTlwN1hG\n\
+VUplRUVYMUNOaExnWmJXWGJhVk5OcXpoZFhyL0xTUQppM1Z6dW5OaUs3cndUVnE2\n\
+K2QyZ1lRckhMMmIvMXBBY3ZKWjJiNSs0bTRRc0NibFpjRENXTktRbHJnRWN5WXRJ\n\
+CkdscXJTbFFEaXA0ZnNrUFMvNDVkWTI0QmJsQ3NGU1k3RzVLVkxJck4zZFpGbmJr\n\
+NEZIS1hBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJv\n\
+ZHVjdGlvbi1wb2ludCBiNGM3enlxNXNheGZzN2prNXFibG1wN3I1b3pwdHRvagpp\n\
+cC1hZGRyZXNzIDEwOS4xNjkuNDUuMjI2Cm9uaW9uLXBvcnQgOTAwMQpvbmlvbi1r\n\
+ZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQU8xSXpw\n\
+WFFUTUY3RXZUb1NEUXpzVnZiRVFRQUQrcGZ6NzczMVRXZzVaUEJZY1EyUkRaeVp4\n\
+OEQKNUVQSU1FeUE1RE83cGd0ak5LaXJvYXJGMC8yempjMkRXTUlSaXZyU29YUWVZ\n\
+ZXlMM1pzKzFIajJhMDlCdkYxZAp6MEswblRFdVhoNVR5V3lyMHdsbGI1SFBnTlI0\n\
+MS9oYkprZzkwZitPVCtIeGhKL1duUml2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBV\n\
+QkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMg\n\
+S0VZLS0tLS0KTUlHSkFvR0JBSzNWZEJ2ajFtQllLL3JrcHNwcm9Ub0llNUtHVmth\n\
+QkxvMW1tK1I2YUVJek1VZFE1SjkwNGtyRwpCd3k5NC8rV0lGNFpGYXh5Z2phejl1\n\
+N2pKY1k3ZGJhd1pFeG1hYXFCRlRwL2h2ZG9rcHQ4a1ByRVk4OTJPRHJ1CmJORUox\n\
+N1FPSmVMTVZZZk5Kcjl4TWZCQ3JQai8zOGh2RUdrbWVRNmRVWElvbVFNaUJGOVRB\n\
+Z01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlv\n\
+bi1wb2ludCBhdjVtcWl0Y2Q3cjJkandsYmN0c2Jlc2R3eGt0ZWtvegppcC1hZGRy\n\
+ZXNzIDE0NC43Ni44LjczCm9uaW9uLXBvcnQgNDQzCm9uaW9uLWtleQotLS0tLUJF\n\
+R0lOIFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTzVweVZzQmpZQmNmMXBE\n\
+dklHUlpmWXUzQ05nNldka0ZLMGlvdTBXTGZtejZRVDN0NWhzd3cyVwpjejlHMXhx\n\
+MmN0Nkd6VWkrNnVkTDlITTRVOUdHTi9BbW8wRG9GV1hKWHpBQkFXd2YyMVdsd1lW\n\
+eFJQMHRydi9WCkN6UDkzcHc5OG5vSmdGUGRUZ05iMjdKYmVUZENLVFBrTEtscXFt\n\
+b3NveUN2RitRa25vUS9BZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0t\n\
+LS0tCnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpN\n\
+SUdKQW9HQkFMVjNKSmtWN3lTNU9jc1lHMHNFYzFQOTVRclFRR3ZzbGJ6Wi9zRGxl\n\
+RlpKYXFSOUYvYjRUVERNClNGcFMxcU1GbldkZDgxVmRGMEdYRmN2WVpLamRJdHU2\n\
+SndBaTRJeEhxeXZtdTRKdUxrcXNaTEFLaXRLVkx4eGsKeERlMjlDNzRWMmJrOTRJ\n\
+MEgybTNKS2tzTHVwc3VxWWRVUmhOVXN0SElKZmgyZmNIalF0bEFnTUJBQUU9Ci0t\n\
+LS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KCg==\n\
+-----END MESSAGE-----\n\
+signature\n\
+-----BEGIN SIGNATURE-----\n\
+d4OuCE5OLAOnRB6cQN6WyMEmg/BHem144Vec+eYgeWoKwx3MxXFplUjFxgnMlmwN\n\
+PcftsZf2ztN0sbNCtPgDL3d0PqvxY3iHTQAI8EbaGq/IAJUZ8U4y963dD5+Bn6JQ\n\
+myE3ctmh0vy5+QxSiRjmQBkuEpCyks7LvWvHYrhnmcg=\n\
+-----END SIGNATURE-----";
+
/* Helper global variable for hidden service descriptor event test.
* It's used as a pointer to dynamically created message buffer in
* send_control_event_string_replacement function, which mocks
@@ -125,6 +190,30 @@ node_describe_longname_by_id_replacement(const char *id_digest)
}
}
+/** Test that we can parse a hardcoded v2 HS desc. */
+static void
+test_hs_parse_static_v2_desc(void *arg)
+{
+ int ret;
+ rend_encoded_v2_service_descriptor_t desc;
+
+ (void) arg;
+
+ /* Test an obviously not parseable string */
+ desc.desc_str = tor_strdup("ceci n'est pas un HS descriptor");
+ ret = rend_desc_v2_is_parsable(&desc);
+ tor_free(desc.desc_str);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* Test an actual descriptor */
+ desc.desc_str = tor_strdup(hs_desc_content);
+ ret = rend_desc_v2_is_parsable(&desc);
+ tor_free(desc.desc_str);
+ tt_int_op(ret, OP_EQ, 1);
+
+ done: ;
+}
+
/** Make sure each hidden service descriptor async event generation
*
* function generates the message in expected format.
@@ -235,10 +324,10 @@ test_hs_desc_event(void *arg)
/* test valid content. */
control_event_hs_descriptor_content(rend_query.onion_address,
STR_HS_CONTENT_DESC_ID, HSDIR_EXIST_ID,
- hs_desc_content);
+ hs_desc_content_control);
tor_asprintf(&exp_msg, "650+HS_DESC_CONTENT " STR_HS_ADDR " "\
STR_HS_CONTENT_DESC_ID " " STR_HSDIR_EXIST_LONGNAME\
- "\r\n%s\r\n.\r\n650 OK\r\n", hs_desc_content);
+ "\r\n%s\r\n.\r\n650 OK\r\n", hs_desc_content_control);
tt_assert(received_msg);
tt_str_op(received_msg, OP_EQ, exp_msg);
@@ -574,17 +663,8 @@ test_single_onion_poisoning(void *arg)
char *poison_path = NULL;
char *err_msg = NULL;
- /* No services, no service to verify, no problem! */
- mock_options->HiddenServiceSingleHopMode = 0;
- mock_options->HiddenServiceNonAnonymousMode = 0;
- ret = rend_config_services(mock_options, 1);
- tt_assert(ret == 0);
-
- /* Either way, no problem. */
mock_options->HiddenServiceSingleHopMode = 1;
mock_options->HiddenServiceNonAnonymousMode = 1;
- ret = rend_config_services(mock_options, 1);
- tt_assert(ret == 0);
/* Create the data directory, and, if the correct bit in arg is set,
* create a directory for that service.
@@ -638,8 +718,10 @@ test_single_onion_poisoning(void *arg)
tt_assert(ret == 0);
/* Add the first service */
- ret = rend_service_check_dir_and_add(services, mock_options, service_1, 0);
- tt_assert(ret == 0);
+ ret = hs_check_service_private_dir(mock_options->User, service_1->directory,
+ service_1->dir_group_readable, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ smartlist_add(services, service_1);
/* But don't add the second service yet. */
/* Service directories, but no previous keys, no problem! */
@@ -717,8 +799,10 @@ test_single_onion_poisoning(void *arg)
tt_assert(ret == 0);
/* Now add the second service: it has no key and no poison file */
- ret = rend_service_check_dir_and_add(services, mock_options, service_2, 0);
- tt_assert(ret == 0);
+ ret = hs_check_service_private_dir(mock_options->User, service_2->directory,
+ service_2->dir_group_readable, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ smartlist_add(services, service_2);
/* A new service, and an existing poisoned service. Not ok. */
mock_options->HiddenServiceSingleHopMode = 0;
@@ -942,6 +1026,8 @@ test_prune_services_on_reload(void *arg)
struct testcase_t hs_tests[] = {
{ "hs_rend_data", test_hs_rend_data, TT_FORK,
NULL, NULL },
+ { "hs_parse_static_v2_desc", test_hs_parse_static_v2_desc, TT_FORK,
+ NULL, NULL },
{ "hs_desc_event", test_hs_desc_event, TT_FORK,
NULL, NULL },
{ "pick_tor2web_rendezvous_node", test_pick_tor2web_rendezvous_node, TT_FORK,
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_cell.c b/src/test/test_hs_cell.c
new file mode 100644
index 0000000000..1b3c788a67
--- /dev/null
+++ b/src/test/test_hs_cell.c
@@ -0,0 +1,130 @@
+/* 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_cell.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 ret;
+ char circ_nonce[DIGEST_LEN] = {0};
+ uint8_t buf[RELAY_PAYLOAD_SIZE];
+ trn_cell_establish_intro_t *cell_in = NULL;
+
+ crypto_rand(circ_nonce, sizeof(circ_nonce));
+
+ /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
+ attempt to parse it. */
+ {
+ /* 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);
+ }
+
+ /* 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 */
+ {
+ ret = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf));
+ tt_u64_op(ret, OP_GT, 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_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;
+ ssize_t cell_len = 0;
+ trn_cell_establish_intro_t *cell = NULL;
+ char circ_nonce[DIGEST_LEN] = {0};
+ hs_service_intro_point_t *ip = NULL;
+
+ MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
+
+ 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 = 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_i64_op(cell_len, OP_EQ, -1);
+
+ 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_client.c b/src/test/test_hs_client.c
new file mode 100644
index 0000000000..6281825841
--- /dev/null
+++ b/src/test/test_hs_client.c
@@ -0,0 +1,286 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_client.c
+ * \brief Test prop224 HS client functionality.
+ */
+
+#define CRYPTO_PRIVATE
+#define MAIN_PRIVATE
+#define TOR_CHANNEL_INTERNAL_
+#define CIRCUITBUILD_PRIVATE
+#define CIRCUITLIST_PRIVATE
+#define CONNECTION_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+#include "rend_test_helpers.h"
+
+#include "config.h"
+#include "crypto.h"
+#include "channeltls.h"
+
+#include "hs_circuit.h"
+#include "hs_ident.h"
+#include "circuitlist.h"
+#include "circuitbuild.h"
+#include "connection.h"
+#include "connection_edge.h"
+
+static int
+mock_connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
+{
+ (void) ap_conn;
+ return 0;
+}
+
+/* Test helper function: Setup a circuit and a stream with the same hidden
+ * service destination, and put them in <b>circ_out</b> and
+ * <b>conn_out</b>. Make the stream wait for circuits to be established to the
+ * hidden service. */
+static int
+helper_get_circ_and_stream_for_test(origin_circuit_t **circ_out,
+ connection_t **conn_out,
+ int is_legacy)
+{
+ int retval;
+ channel_tls_t *n_chan=NULL;
+ rend_data_t *conn_rend_data = NULL;
+ origin_circuit_t *or_circ = NULL;
+ connection_t *conn = NULL;
+ ed25519_public_key_t service_pk;
+
+ /* Make a dummy connection stream and make it wait for our circuit */
+ conn = test_conn_get_connection(AP_CONN_STATE_CIRCUIT_WAIT,
+ CONN_TYPE_AP /* ??? */,
+ 0);
+ if (is_legacy) {
+ /* Legacy: Setup rend_data of stream */
+ char service_id[REND_SERVICE_ID_LEN_BASE32+1] = {0};
+ TO_EDGE_CONN(conn)->rend_data = mock_rend_data(service_id);
+ conn_rend_data = TO_EDGE_CONN(conn)->rend_data;
+ } else {
+ /* prop224: Setup hs conn identifier on the stream */
+ ed25519_secret_key_t sk;
+ tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
+ tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
+
+ /* Setup hs_conn_identifier of stream */
+ TO_EDGE_CONN(conn)->hs_ident = hs_ident_edge_conn_new(&service_pk);
+ }
+
+ /* Make it wait for circuit */
+ connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn));
+
+ /* This is needed to silence a BUG warning from
+ connection_edge_update_circuit_isolation() */
+ TO_ENTRY_CONN(conn)->original_dest_address =
+ tor_strdup(TO_ENTRY_CONN(conn)->socks_request->address);
+
+ /****************************************************/
+
+ /* Now make dummy circuit */
+ or_circ = origin_circuit_new();
+
+ or_circ->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
+
+ or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
+ or_circ->build_state->is_internal = 1;
+
+ if (is_legacy) {
+ /* Legacy: Setup rend data and final cpath */
+ or_circ->build_state->pending_final_cpath =
+ tor_malloc_zero(sizeof(crypt_path_t));
+ or_circ->build_state->pending_final_cpath->magic = CRYPT_PATH_MAGIC;
+ or_circ->build_state->pending_final_cpath->rend_dh_handshake_state =
+ crypto_dh_new(DH_TYPE_REND);
+ tt_assert(
+ or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
+ retval = crypto_dh_generate_public(
+ or_circ->build_state->pending_final_cpath->rend_dh_handshake_state);
+ tt_int_op(retval, ==, 0);
+ or_circ->rend_data = rend_data_dup(conn_rend_data);
+ } else {
+ /* prop224: Setup hs ident on the circuit */
+ or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
+ HS_IDENT_CIRCUIT_RENDEZVOUS);
+ }
+
+ TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
+
+ /* fake n_chan */
+ n_chan = tor_malloc_zero(sizeof(channel_tls_t));
+ n_chan->base_.global_identifier = 1;
+ or_circ->base_.n_chan = &(n_chan->base_);
+
+ *circ_out = or_circ;
+ *conn_out = conn;
+
+ return 0;
+
+ done:
+ /* something failed */
+ return -1;
+}
+
+/* Test: Ensure that setting up legacy e2e rendezvous circuits works
+ * correctly. */
+static void
+test_e2e_rend_circuit_setup_legacy(void *arg)
+{
+ ssize_t retval;
+ origin_circuit_t *or_circ = NULL;
+ connection_t *conn = NULL;
+
+ (void) arg;
+
+ /** In this test we create a v2 legacy HS stream and a circuit with the same
+ * hidden service destination. We make the stream wait for circuits to be
+ * established to the hidden service, and then we complete the circuit using
+ * the hs_circuit_setup_e2e_rend_circ_legacy_client() function. We then
+ * check that the end-to-end cpath was setup correctly and that the stream
+ * was attached to the circuit as expected. */
+
+ MOCK(connection_ap_handshake_send_begin,
+ mock_connection_ap_handshake_send_begin);
+
+ /* Setup */
+ retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 1);
+ tt_int_op(retval, OP_EQ, 0);
+ tt_assert(or_circ);
+ tt_assert(conn);
+
+ /* Check number of hops */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, ==, 0);
+
+ /* Check that our stream is not attached on any circuits */
+ tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
+
+ /********************************************** */
+
+ /* Make a good RENDEZVOUS1 cell body because it needs to pass key exchange
+ * digest verification... */
+ uint8_t rend_cell_body[DH_KEY_LEN+DIGEST_LEN] = {2};
+ {
+ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN];
+ crypto_dh_t *dh_state =
+ or_circ->build_state->pending_final_cpath->rend_dh_handshake_state;
+ /* compute and overwrite digest of cell body with the right value */
+ retval = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh_state,
+ (char*)rend_cell_body, DH_KEY_LEN,
+ keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN);
+ tt_int_op(retval, OP_GT, 0);
+ memcpy(rend_cell_body+DH_KEY_LEN, keys, DIGEST_LEN);
+ }
+
+ /* Setup the circuit */
+ retval = hs_circuit_setup_e2e_rend_circ_legacy_client(or_circ,
+ rend_cell_body);
+ tt_int_op(retval, OP_EQ, 0);
+
+ /**********************************************/
+
+ /* See that a hop was added to the circuit's cpath */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, OP_EQ, 1);
+
+ /* Check the digest algo */
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+ OP_EQ, DIGEST_SHA1);
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+ OP_EQ, DIGEST_SHA1);
+ tt_assert(or_circ->cpath->f_crypto);
+ tt_assert(or_circ->cpath->b_crypto);
+
+ /* Ensure that circ purpose was changed */
+ tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
+
+ /* Test that stream got attached */
+ tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
+
+ done:
+ connection_free_(conn);
+ tor_free(TO_CIRCUIT(or_circ)->n_chan);
+ circuit_free(TO_CIRCUIT(or_circ));
+}
+
+/* Test: Ensure that setting up v3 rendezvous circuits works correctly. */
+static void
+test_e2e_rend_circuit_setup(void *arg)
+{
+ uint8_t ntor_key_seed[DIGEST256_LEN] = {0};
+ origin_circuit_t *or_circ;
+ int retval;
+ connection_t *conn = NULL;
+
+ (void) arg;
+
+ /** In this test we create a prop224 v3 HS stream and a circuit with the same
+ * hidden service destination. We make the stream wait for circuits to be
+ * established to the hidden service, and then we complete the circuit using
+ * the hs_circuit_setup_e2e_rend_circ() function. We then check that the
+ * end-to-end cpath was setup correctly and that the stream was attached to
+ * the circuit as expected. */
+
+ MOCK(connection_ap_handshake_send_begin,
+ mock_connection_ap_handshake_send_begin);
+
+ /* Setup */
+ retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 0);
+ tt_int_op(retval, OP_EQ, 0);
+ tt_assert(or_circ);
+ tt_assert(conn);
+
+ /* Check number of hops: There should be no hops yet to this circ */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, ==, 0);
+ tt_assert(!or_circ->cpath);
+
+ /* Check that our stream is not attached on any circuits */
+ tt_assert(!TO_EDGE_CONN(conn)->on_circuit);
+
+ /**********************************************/
+
+ /* Setup the circuit */
+ retval = hs_circuit_setup_e2e_rend_circ(or_circ,
+ ntor_key_seed, sizeof(ntor_key_seed),
+ 0);
+ tt_int_op(retval, OP_EQ, 0);
+
+ /**********************************************/
+
+ /* See that a hop was added to the circuit's cpath */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, OP_EQ, 1);
+
+ /* Check that the crypt path has prop224 algorithm parameters */
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+ OP_EQ, DIGEST_SHA3_256);
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+ OP_EQ, DIGEST_SHA3_256);
+ tt_assert(or_circ->cpath->f_crypto);
+ tt_assert(or_circ->cpath->b_crypto);
+
+ /* Ensure that circ purpose was changed */
+ tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED);
+
+ /* Test that stream got attached */
+ tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ));
+
+ done:
+ connection_free_(conn);
+ tor_free(TO_CIRCUIT(or_circ)->n_chan);
+ circuit_free(TO_CIRCUIT(or_circ));
+}
+
+struct testcase_t hs_client_tests[] = {
+ { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy,
+ TT_FORK, NULL, NULL },
+ { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup,
+ TT_FORK, NULL, NULL },
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c
new file mode 100644
index 0000000000..b8b7ab00d2
--- /dev/null
+++ b/src/test/test_hs_common.c
@@ -0,0 +1,507 @@
+/* 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
+#define HS_SERVICE_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+#include "hs_test_helpers.h"
+
+#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
+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 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)
+{
+ 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 =
+ "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);
+
+ 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 */
+static void
+test_time_period(void *arg)
+{
+ (void) arg;
+ uint64_t tn;
+ int retval;
+ 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",
+ &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);
+
+ { /* 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);
+ 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);
+
+ { /* 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_u64_op(hs_get_time_period_num(next_tp_start), OP_EQ,
+ hs_get_next_time_period_num(fake_time));
+ }
+
+ done:
+ ;
+}
+
+/** 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)
+{
+ (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)
+{
+ (void) arg;
+ int retval;
+ time_t now = time(NULL);
+ networkstatus_t *dummy_consensus = NULL;
+
+ /* 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 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);
+
+ /* Now increase the valid_after so that it goes to 11:00:00 UTC. Overlap
+ period is still active. */
+ dummy_consensus->valid_after += 3600*11;
+ 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);
+}
+
+/* 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);
+}
+
+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);
+}
+
+/** 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 },
+ { "validate_address", test_validate_address, TT_FORK,
+ 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 },
+ { "desc_overlap_period_testnet", test_desc_overlap_period_testnet, TT_FORK,
+ NULL, NULL },
+ { "desc_responsible_hsdirs", test_responsible_hsdirs, TT_FORK,
+ NULL, NULL },
+ { "disaster_srv", test_disaster_srv, TT_FORK, NULL, NULL },
+
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_config.c b/src/test/test_hs_config.c
new file mode 100644
index 0000000000..a76be301d3
--- /dev/null
+++ b/src/test/test_hs_config.c
@@ -0,0 +1,487 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_config.c
+ * \brief Test hidden service configuration functionality.
+ */
+
+#define CONFIG_PRIVATE
+#define HS_SERVICE_PRIVATE
+
+#include "test.h"
+#include "test_helpers.h"
+#include "log_test_helpers.h"
+
+#include "config.h"
+#include "hs_common.h"
+#include "hs_config.h"
+#include "hs_service.h"
+#include "rendservice.h"
+
+static int
+helper_config_service(const char *conf, int validate_only)
+{
+ int ret = 0;
+ or_options_t *options = NULL;
+ tt_assert(conf);
+ options = helper_parse_options(conf);
+ tt_assert(options);
+ ret = hs_config_service_all(options, validate_only);
+ done:
+ or_options_free(options);
+ return ret;
+}
+
+static void
+test_invalid_service(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* Try with a missing port configuration. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 1\n"; /* Wrong not supported version. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceVersion must be between 2 and 3");
+ teardown_capture_of_logs();
+ }
+
+ /* Bad value of HiddenServiceAllowUnknownPorts. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServiceAllowUnknownPorts 2\n"; /* Should be 0 or 1. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceAllowUnknownPorts must be "
+ "between 0 and 1, not 2");
+ teardown_capture_of_logs();
+ }
+
+ /* Bad value of HiddenServiceDirGroupReadable */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServiceDirGroupReadable 2\n"; /* Should be 0 or 1. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceDirGroupReadable must be "
+ "between 0 and 1, not 2");
+ teardown_capture_of_logs();
+ }
+
+ /* Bad value of HiddenServiceMaxStreamsCloseCircuit */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServiceMaxStreamsCloseCircuit 2\n"; /* Should be 0 or 1. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceMaxStreamsCloseCircuit must "
+ "be between 0 and 1, not 2");
+ teardown_capture_of_logs();
+ }
+
+ /* Too much max streams. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceMaxStreams 65536\n"; /* One too many. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceMaxStreams must be between "
+ "0 and 65535, not 65536");
+ teardown_capture_of_logs();
+ }
+
+ /* Duplicate directory directive. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 81\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("Another hidden service is already "
+ "configured for directory");
+ teardown_capture_of_logs();
+ }
+
+ /* Bad port. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 65536\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("Missing or invalid port");
+ teardown_capture_of_logs();
+ }
+
+ /* Out of order directives. */
+ {
+ const char *conf =
+ "HiddenServiceVersion 2\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServicePort 80\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceVersion with no preceding "
+ "HiddenServiceDir directive");
+ teardown_capture_of_logs();
+ }
+
+ done:
+ ;
+}
+
+static void
+test_valid_service(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* Mix of v2 and v3. Still valid. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 81\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 82\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_invalid_service_v2(void *arg)
+{
+ int validate_only = 1, ret;
+
+ (void) arg;
+
+ /* Try with a missing port configuration. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("with no ports configured.");
+ teardown_capture_of_logs();
+ }
+
+ /* Too many introduction points. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceNumIntroductionPoints 11\n"; /* One too many. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceNumIntroductionPoints should "
+ "be between 0 and 10, not 11");
+ teardown_capture_of_logs();
+ }
+
+ /* Too little introduction points. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceNumIntroductionPoints -1\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceNumIntroductionPoints should "
+ "be between 0 and 10, not -1");
+ teardown_capture_of_logs();
+ }
+
+ /* Bad authorized client type. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceAuthorizeClient blah alice,bob\n"; /* blah is no good. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceAuthorizeClient contains "
+ "unrecognized auth-type");
+ teardown_capture_of_logs();
+ }
+
+ done:
+ ;
+}
+
+static void
+test_valid_service_v2(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* Valid complex configuration. Basic client authorization. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServicePort 22 localhost:22\n"
+#ifdef HAVE_SYS_UN_H
+ "HiddenServicePort 42 unix:/path/to/socket\n"
+#endif
+ "HiddenServiceAuthorizeClient basic alice,bob,eve\n"
+ "HiddenServiceAllowUnknownPorts 1\n"
+ "HiddenServiceMaxStreams 42\n"
+ "HiddenServiceMaxStreamsCloseCircuit 0\n"
+ "HiddenServiceDirGroupReadable 1\n"
+ "HiddenServiceNumIntroductionPoints 7\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Valid complex configuration. Stealth client authorization. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 65535\n"
+ "HiddenServicePort 22 1.1.1.1:22\n"
+#ifdef HAVE_SYS_UN_H
+ "HiddenServicePort 9000 unix:/path/to/socket\n"
+#endif
+ "HiddenServiceAuthorizeClient stealth charlie,romeo\n"
+ "HiddenServiceAllowUnknownPorts 0\n"
+ "HiddenServiceMaxStreams 42\n"
+ "HiddenServiceMaxStreamsCloseCircuit 0\n"
+ "HiddenServiceDirGroupReadable 1\n"
+ "HiddenServiceNumIntroductionPoints 8\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_invalid_service_v3(void *arg)
+{
+ int validate_only = 1, ret;
+
+ (void) arg;
+
+ /* Try with a missing port configuration. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 3\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("with no ports configured.");
+ teardown_capture_of_logs();
+ }
+
+ /* Too many introduction points. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceNumIntroductionPoints 21\n"; /* One too many. */
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceNumIntroductionPoints must "
+ "be between 3 and 20, not 21.");
+ teardown_capture_of_logs();
+ }
+
+ /* Too little introduction points. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceNumIntroductionPoints 1\n";
+ setup_full_capture_of_logs(LOG_WARN);
+ ret = helper_config_service(conf, validate_only);
+ tt_int_op(ret, OP_EQ, -1);
+ expect_log_msg_containing("HiddenServiceNumIntroductionPoints must "
+ "be between 3 and 20, not 1.");
+ teardown_capture_of_logs();
+ }
+
+ done:
+ ;
+}
+
+static void
+test_valid_service_v3(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* Valid complex configuration. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 80\n"
+ "HiddenServicePort 22 localhost:22\n"
+#ifdef HAVE_SYS_UN_H
+ "HiddenServicePort 42 unix:/path/to/socket\n"
+#endif
+ "HiddenServiceAllowUnknownPorts 1\n"
+ "HiddenServiceMaxStreams 42\n"
+ "HiddenServiceMaxStreamsCloseCircuit 0\n"
+ "HiddenServiceDirGroupReadable 1\n"
+ "HiddenServiceNumIntroductionPoints 7\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Valid complex configuration. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 65535\n"
+ "HiddenServicePort 22 1.1.1.1:22\n"
+#ifdef HAVE_SYS_UN_H
+ "HiddenServicePort 9000 unix:/path/to/socket\n"
+#endif
+ "HiddenServiceAllowUnknownPorts 0\n"
+ "HiddenServiceMaxStreams 42\n"
+ "HiddenServiceMaxStreamsCloseCircuit 0\n"
+ "HiddenServiceDirGroupReadable 1\n"
+ "HiddenServiceNumIntroductionPoints 20\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Mix of v2 and v3. Still valid. */
+ {
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 80\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 81\n"
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n"
+ "HiddenServiceVersion 2\n"
+ "HiddenServicePort 82\n";
+ ret = helper_config_service(conf, 1);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_staging_service_v3(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* We don't validate a service object, this is the service test that are in
+ * charge of doing so. We just check for the stable state after
+ * registration. */
+
+ hs_init();
+
+ /* Time for a valid v3 service that should get staged. */
+ const char *conf =
+ "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
+ "HiddenServiceVersion 3\n"
+ "HiddenServicePort 65535\n"
+ "HiddenServicePort 22 1.1.1.1:22\n"
+#ifdef HAVE_SYS_UN_H
+ "HiddenServicePort 9000 unix:/path/to/socket\n"
+#endif
+ "HiddenServiceAllowUnknownPorts 0\n"
+ "HiddenServiceMaxStreams 42\n"
+ "HiddenServiceMaxStreamsCloseCircuit 0\n"
+ "HiddenServiceDirGroupReadable 1\n"
+ "HiddenServiceNumIntroductionPoints 20\n";
+ ret = helper_config_service(conf, 0);
+ tt_int_op(ret, OP_EQ, 0);
+ /* 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(rend_num_services(), OP_EQ, 0);
+
+ done:
+ hs_free_all();
+}
+
+struct testcase_t hs_config_tests[] = {
+ /* Invalid service not specific to any version. */
+ { "invalid_service", test_invalid_service, TT_FORK,
+ NULL, NULL },
+ { "valid_service", test_valid_service, TT_FORK,
+ NULL, NULL },
+
+ /* Test case only for version 2. */
+ { "invalid_service_v2", test_invalid_service_v2, TT_FORK,
+ NULL, NULL },
+ { "valid_service_v2", test_valid_service_v2, TT_FORK,
+ NULL, NULL },
+
+ /* Test case only for version 3. */
+ { "invalid_service_v3", test_invalid_service_v3, TT_FORK,
+ NULL, NULL },
+ { "valid_service_v3", test_valid_service_v3, TT_FORK,
+ NULL, NULL },
+
+ /* Test service staging. */
+ { "staging_service_v3", test_staging_service_v3, TT_FORK,
+ NULL, NULL },
+
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c
index b1abe381d4..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);
}
@@ -427,7 +433,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 +451,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 +551,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
diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c
index c6197875b5..a5031c5ae8 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,39 +432,34 @@ 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_i64_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);
- retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len);
+ retval = hs_intro_received_establish_intro(intro_circ, cell_body,
+ (size_t)cell_len);
expect_log_msg_containing("Failed to verify ESTABLISH_INTRO cell.");
teardown_capture_of_logs();
tt_int_op(retval, ==, -1);
done:
- trn_cell_establish_intro_free(establish_intro_cell);
circuit_free(TO_CIRCUIT(intro_circ));
}
@@ -439,32 +469,33 @@ 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_i64_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);
+ retval = hs_intro_received_establish_intro(intro_circ, cell_body,
+ (size_t) 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,27 +507,27 @@ 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 */
- cell_len = encode_establish_intro_cell_legacy((char*)cell_body,
- sizeof(cell_body),
- key1,
- (char *) circuit_key_material);
- tt_int_op(cell_len, >, 0);
+ /* Use old circ_nonce why not */
+ cell_len = rend_service_encode_establish_intro_cell(
+ (char*)cell_body,
+ sizeof(cell_body), key1,
+ circ_nonce);
+ tt_int_op(cell_len, OP_GT, 0);
/* Receive legacy establish_intro */
retval = hs_intro_received_establish_intro(intro_circ,
- cell_body, cell_len);
+ cell_body, (size_t) cell_len);
tt_int_op(retval, ==, 0);
done:
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 fcfb3b992d..60efca15f8 100644
--- a/src/test/test_hs_service.c
+++ b/src/test/test_hs_service.c
@@ -6,243 +6,1330 @@
* \brief Test hidden service functionality.
*/
+#define CIRCUITBUILD_PRIVATE
+#define CIRCUITLIST_PRIVATE
+#define CONFIG_PRIVATE
+#define CONNECTION_PRIVATE
+#define CRYPTO_PRIVATE
#define HS_COMMON_PRIVATE
#define HS_SERVICE_PRIVATE
#define HS_INTROPOINT_PRIVATE
+#define MAIN_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+#define STATEFILE_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 "config.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
+#include "circuituse.h"
#include "crypto.h"
+#include "networkstatus.h"
+#include "nodelist.h"
+#include "relay.h"
-#include "hs/cell_establish_intro.h"
#include "hs_common.h"
-#include "hs_service.h"
+#include "hs_config.h"
+#include "hs_circuit.h"
+#include "hs_ident.h"
#include "hs_intropoint.h"
-
#include "hs_ntor.h"
+#include "hs_service.h"
+#include "main.h"
+#include "rendservice.h"
+#include "statefile.h"
+
+/* 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;
+}
+
+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
+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
+helper_config_service(const char *conf)
+{
+ int ret = 0;
+ or_options_t *options = NULL;
+ tt_assert(conf);
+ options = helper_parse_options(conf);
+ tt_assert(options);
+ ret = hs_config_service_all(options, 0);
+ done:
+ or_options_free(options);
+ return ret;
+}
+
+/* Test: Ensure that setting up rendezvous circuits works correctly. */
+static void
+test_e2e_rend_circuit_setup(void *arg)
+{
+ ed25519_public_key_t service_pk;
+ origin_circuit_t *or_circ;
+ int retval;
+
+ /** In this test we create a v3 prop224 service-side rendezvous circuit.
+ * We simulate an HS ntor key exchange with a client, and check that
+ * the circuit was setup correctly and is ready to accept rendezvous data */
+
+ (void) arg;
+
+ /* Now make dummy circuit */
+ {
+ or_circ = origin_circuit_new();
+
+ or_circ->base_.purpose = CIRCUIT_PURPOSE_S_CONNECT_REND;
+
+ or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
+ or_circ->build_state->is_internal = 1;
+
+ /* prop224: Setup hs conn identifier on the stream */
+ ed25519_secret_key_t sk;
+ tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0));
+ tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk));
+
+ or_circ->hs_ident = hs_ident_circuit_new(&service_pk,
+ HS_IDENT_CIRCUIT_RENDEZVOUS);
+
+ TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN;
+ }
+
+ /* Check number of hops */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, OP_EQ, 0);
+
+ /* Setup the circuit: do the ntor key exchange */
+ {
+ uint8_t ntor_key_seed[DIGEST256_LEN] = {2};
+ retval = hs_circuit_setup_e2e_rend_circ(or_circ,
+ ntor_key_seed, sizeof(ntor_key_seed),
+ 1);
+ tt_int_op(retval, OP_EQ, 0);
+ }
+
+ /* See that a hop was added to the circuit's cpath */
+ retval = cpath_get_n_hops(&or_circ->cpath);
+ tt_int_op(retval, OP_EQ, 1);
+
+ /* Check the digest algo */
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest),
+ OP_EQ, DIGEST_SHA3_256);
+ tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest),
+ OP_EQ, DIGEST_SHA3_256);
+ tt_assert(or_circ->cpath->f_crypto);
+ tt_assert(or_circ->cpath->b_crypto);
+
+ /* Ensure that circ purpose was changed */
+ tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED);
+
+ done:
+ 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)
+{
+ int ret;
+ char *conf = NULL;
+ char *hsdir_v2 = tor_strdup(get_fname("hs2"));
+ char *hsdir_v3 = tor_strdup(get_fname("hs3"));
+ char addr[HS_SERVICE_ADDR_LEN_BASE32 + 1];
+
+ (void) arg;
+
+ /* We'll register two services, a v2 and a v3, then we'll load keys and
+ * validate that both are in a correct state. */
+
+ hs_init();
+
+#define conf_fmt \
+ "HiddenServiceDir %s\n" \
+ "HiddenServiceVersion %d\n" \
+ "HiddenServicePort 65535\n"
+
+ /* v2 service. */
+ tor_asprintf(&conf, conf_fmt, hsdir_v2, HS_VERSION_TWO);
+ ret = helper_config_service(conf);
+ tor_free(conf);
+ 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(rend_num_services(), OP_EQ, 1);
+
+ /* v3 service. */
+ tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE);
+ ret = helper_config_service(conf);
+ tor_free(conf);
+ tt_int_op(ret, OP_EQ, 0);
+ /* It's in staging? */
+ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
+
+ /* Load the keys for these. After that, the v3 service should be registered
+ * in the global map. */
+ hs_service_load_all_keys();
+ tt_int_op(get_hs_service_map_size(), OP_EQ, 1);
+ hs_service_t *s = get_first_service();
+ tt_assert(s);
+
+ /* Ok we have the service object. Validate few things. */
+ tt_assert(!tor_mem_is_zero(s->onion_address, sizeof(s->onion_address)));
+ tt_int_op(hs_address_is_valid(s->onion_address), OP_EQ, 1);
+ tt_assert(!tor_mem_is_zero((char *) s->keys.identity_sk.seckey,
+ ED25519_SECKEY_LEN));
+ tt_assert(!tor_mem_is_zero((char *) s->keys.identity_pk.pubkey,
+ ED25519_PUBKEY_LEN));
+ /* Check onion address from identity key. */
+ hs_build_address(&s->keys.identity_pk, s->config.version, addr);
+ tt_int_op(hs_address_is_valid(addr), OP_EQ, 1);
+ tt_str_op(addr, OP_EQ, s->onion_address);
+
+ done:
+ tor_free(hsdir_v2);
+ tor_free(hsdir_v3);
+ hs_free_all();
+}
+
+static void
+test_access_service(void *arg)
+{
+ int ret;
+ char *conf = NULL;
+ char *hsdir_v3 = tor_strdup(get_fname("hs3"));
+ hs_service_ht *global_map;
+ hs_service_t *s = NULL;
+
+ (void) arg;
+
+ /* We'll register two services, a v2 and a v3, then we'll load keys and
+ * validate that both are in a correct state. */
+
+ hs_init();
+
+#define conf_fmt \
+ "HiddenServiceDir %s\n" \
+ "HiddenServiceVersion %d\n" \
+ "HiddenServicePort 65535\n"
+
+ /* v3 service. */
+ tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE);
+ ret = helper_config_service(conf);
+ tor_free(conf);
+ tt_int_op(ret, OP_EQ, 0);
+ /* It's in staging? */
+ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
+
+ /* Load the keys for these. After that, the v3 service should be registered
+ * in the global map. */
+ hs_service_load_all_keys();
+ tt_int_op(get_hs_service_map_size(), OP_EQ, 1);
+ s = get_first_service();
+ tt_assert(s);
+ global_map = get_hs_service_map();
+ tt_assert(global_map);
+
+ /* From here, we'll try the service accessors. */
+ hs_service_t *query = find_service(global_map, &s->keys.identity_pk);
+ tt_assert(query);
+ tt_mem_op(query, OP_EQ, s, sizeof(hs_service_t));
+ /* Remove service, check if it actually works and then put it back. */
+ remove_service(global_map, s);
+ tt_int_op(get_hs_service_map_size(), OP_EQ, 0);
+ query = find_service(global_map, &s->keys.identity_pk);
+ tt_assert(!query);
+
+ /* Register back the service in the map. */
+ ret = register_service(global_map, s);
+ tt_int_op(ret, OP_EQ, 0);
+ tt_int_op(get_hs_service_map_size(), OP_EQ, 1);
+ /* Twice should fail. */
+ ret = register_service(global_map, s);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Remove service from map so we don't double free on cleanup. */
+ remove_service(global_map, s);
+ tt_int_op(get_hs_service_map_size(), OP_EQ, 0);
+ query = find_service(global_map, &s->keys.identity_pk);
+ tt_assert(!query);
+ /* Let's try to remove twice for fun. */
+ setup_full_capture_of_logs(LOG_WARN);
+ remove_service(global_map, s);
+ expect_log_msg_containing("Could not find service in the global map");
+ teardown_capture_of_logs();
+
+ done:
+ hs_service_free(s);
+ tor_free(hsdir_v3);
+ hs_free_all();
+}
+
+/** Test that we can create intro point objects, index them and find them */
+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;
+}
-/** 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)
+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;
- 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));
+ 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;
- /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
- attempt to parse it. */
+ /* 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() */
{
- cell_out = generate_establish_intro_cell(circuit_key_material,
- sizeof(circuit_key_material));
- tt_assert(cell_out);
+ const node_t *node = get_node_from_intro_point(ip);
+ 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) {
+ /* 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);
+ }
- retval = get_establish_intro_payload(buf, sizeof(buf), cell_out);
- tt_int_op(retval, >=, 0);
+ /* 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);
}
- /* Parse it as the receiver */
+ /* Testing intro_point_should_expire(). */
{
- ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in,
- buf, sizeof(buf));
- tt_int_op(parse_result, >=, 0);
+ 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);
- retval = verify_establish_intro_cell(cell_in,
- circuit_key_material,
- sizeof(circuit_key_material));
- tt_int_op(retval, >=, 0);
+ /* 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:
- trn_cell_establish_intro_free(cell_out);
- trn_cell_establish_intro_free(cell_in);
+ /* This will free the service and all objects associated to it. */
+ hs_service_free_all();
+ UNMOCK(node_get_by_id);
}
-/* 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 */
+/** Test that we do the right operations when an intro circuit opens */
+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_);
+}
+
+/** Test the operations we do on a circuit after we learn that we successfuly
+ * established an intro point on it */
static void
-test_gen_establish_intro_cell_bad(void *arg)
+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;
- trn_cell_establish_intro_t *cell = NULL;
- uint8_t circuit_key_material[DIGEST_LEN] = {0};
- MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
+ hs_init();
+ MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
- crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
+ 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);
- /* 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.");
+ 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();
- tt_assert(!cell);
+
+ /* 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:
- trn_cell_establish_intro_free(cell);
- UNMOCK(ed25519_sign_prefixed);
+ circuit_free(TO_CIRCUIT(circ));
+ hs_free_all();
+ UNMOCK(circuit_mark_for_close_);
}
-/** 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. */
+/** Check the operations we do on a rendezvous circuit after we learn it's
+ * open */
static void
-test_hs_ntor(void *arg)
+test_rdv_circuit_opened(void *arg)
{
- int retval;
+ int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL;
+ origin_circuit_t *circ = NULL;
+ hs_service_t *service;
- uint8_t subcredential[DIGEST256_LEN];
+ (void) arg;
- ed25519_keypair_t service_intro_auth_keypair;
- curve25519_keypair_t service_intro_enc_keypair;
- curve25519_keypair_t service_ephemeral_rend_keypair;
+ hs_init();
+ MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close);
+ MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
- curve25519_keypair_t client_ephemeral_enc_keypair;
+ 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));
- hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys;
- hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys;
+ /* 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_);
+}
- hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys;
- hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys;
+/** Test sending and receiving introduce2 cells */
+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;
- /* Generate fake data for this unittest */
+ 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);
+
+ /* 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. */
{
- /* 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:
- ;
-}
-
-/** Test that our HS time period calculation functions work properly */
+ 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:
+ or_state_free(dummy_state);
+ dummy_state = NULL;
+ circuit_free(TO_CIRCUIT(circ));
+ hs_free_all();
+ UNMOCK(circuit_mark_for_close_);
+}
+
+/** Test basic hidden service housekeeping operations (maintaining intro
+ * points, etc) */
static void
-test_time_period(void *arg)
+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;
- 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);
+ 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_);
+}
+
+/** Test that we rotate descriptors correctly in overlap period */
+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);
+ 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
+ * 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);
+ 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. */
+ 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);
+ 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);
+ 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);
+}
+
+/** Test building descriptors: picking intro points, setting up their link
+ * specifiers, etc. */
+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);
+ 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());
+ 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;
+
+ 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;
+ 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 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);
+ 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. */
+ 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.");
+ 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);
+}
- /* Check that the time period number is right */
- tn = get_time_period_num(fake_time);
- tt_u64_op(tn, ==, 16903);
+static void
+test_upload_descriptors(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);
+ 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());
+ 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);
+}
+
+/** 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));
- /* 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);
- tt_u64_op(tn, ==, 16903);
+ /* 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");
- /* 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);
- tt_u64_op(tn, ==, 16904);
+ state_line_two = encode_desc_rev_counter_for_state(desc_two);
+ tt_str_op(state_line_two, OP_EQ,
+ "8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PA 240");
- /* 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);
+ /* 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[] = {
- { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK,
+ { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
+ NULL, NULL },
+ { "load_keys", test_load_keys, TT_FORK,
+ 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 },
- { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK,
+ { "build_update_descriptors", test_build_update_descriptors, TT_FORK,
NULL, NULL },
- { "hs_ntor", test_hs_ntor, TT_FORK,
+ { "upload_descriptors", test_upload_descriptors, TT_FORK,
NULL, NULL },
- { "time_period", test_time_period, TT_FORK,
+ { "revision_counter_state", test_revision_counter_state, TT_FORK,
NULL, NULL },
END_OF_TESTCASES
diff --git a/src/test/test_introduce.c b/src/test/test_introduce.c
index cfb8d83b1d..5a24630475 100644
--- a/src/test/test_introduce.c
+++ b/src/test/test_introduce.c
@@ -355,7 +355,7 @@ make_intro_from_plaintext(
/*
* Figure out an upper bound on how big the ciphertext will be
- * (see crypto_pk_public_hybrid_encrypt())
+ * (see crypto_pk_obsolete_public_hybrid_encrypt())
*/
ciphertext_size = PKCS1_OAEP_PADDING_OVERHEAD;
ciphertext_size += crypto_pk_keysize(key);
@@ -372,7 +372,7 @@ make_intro_from_plaintext(
tt_assert(r >= 0);
/* Do encryption */
- r = crypto_pk_public_hybrid_encrypt(
+ r = crypto_pk_obsolete_public_hybrid_encrypt(
key, cell + DIGEST_LEN, ciphertext_size,
buf, len,
PK_PKCS1_OAEP_PADDING, 0);
diff --git a/src/test/test_key_expiration.sh b/src/test/test_key_expiration.sh
new file mode 100755
index 0000000000..95d7911f04
--- /dev/null
+++ b/src/test/test_key_expiration.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+# Note: some of this code is lifted from zero_length_keys.sh and
+# test_keygen.sh, and could be unified.
+
+umask 077
+set -e
+
+if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then
+ if [ "$TESTING_TOR_BINARY" = "" ] ; then
+ echo "Usage: ${0} PATH_TO_TOR [case-number]"
+ exit 1
+ fi
+fi
+
+if [ $# -ge 1 ]; then
+ TOR_BINARY="${1}"
+ shift
+else
+ TOR_BINARY="${TESTING_TOR_BINARY}"
+fi
+
+if [ $# -ge 1 ]; then
+ dflt=0
+else
+ dflt=1
+fi
+
+CASE1=$dflt
+CASE2=$dflt
+CASE3=$dflt
+
+if [ $# -ge 1 ]; then
+ eval "CASE${1}"=1
+fi
+
+
+dump() { xxd -p "$1" | tr -d '\n '; }
+die() { echo "$1" >&2 ; exit 5; }
+check_dir() { [ -d "$1" ] || die "$1 did not exist"; }
+check_file() { [ -e "$1" ] || die "$1 did not exist"; }
+check_no_file() { [ -e "$1" ] && die "$1 was not supposed to exist" || true; }
+check_files_eq() { cmp "$1" "$2" || die "$1 and $2 did not match: `dump $1` vs `dump $2`"; }
+check_keys_eq() { check_files_eq "${SRC}/keys/${1}" "${ME}/keys/${1}"; }
+
+DATA_DIR=`mktemp -d -t tor_key_expiration_tests.XXXXXX`
+if [ -z "$DATA_DIR" ]; then
+ echo "Failure: mktemp invocation returned empty string" >&2
+ exit 3
+fi
+if [ ! -d "$DATA_DIR" ]; then
+ echo "Failure: mktemp invocation result doesn't point to directory" >&2
+ exit 3
+fi
+trap "rm -rf '$DATA_DIR'" 0
+
+# Use an absolute path for this or Tor will complain
+DATA_DIR=`cd "${DATA_DIR}" && pwd`
+
+touch "${DATA_DIR}/empty_torrc"
+
+QUIETLY="--hush"
+SILENTLY="--quiet"
+TOR="${TOR_BINARY} --DisableNetwork 1 --ShutdownWaitLength 0 --ORPort 12345 --ExitRelay 0 -f ${DATA_DIR}/empty_torrc --DataDirectory ${DATA_DIR}"
+
+##### SETUP
+#
+# Here we create a set of keys.
+
+# Step 1: Start Tor with --list-fingerprint --quiet. Make sure everything is there.
+echo "Setup step #1"
+${TOR} --list-fingerprint ${SILENTLY} > /dev/null
+
+check_dir "${DATA_DIR}/keys"
+check_file "${DATA_DIR}/keys/ed25519_master_id_public_key"
+check_file "${DATA_DIR}/keys/ed25519_master_id_secret_key"
+check_file "${DATA_DIR}/keys/ed25519_signing_cert"
+check_file "${DATA_DIR}/keys/ed25519_signing_secret_key"
+check_file "${DATA_DIR}/keys/secret_id_key"
+check_file "${DATA_DIR}/keys/secret_onion_key"
+check_file "${DATA_DIR}/keys/secret_onion_key_ntor"
+
+##### TEST CASES
+
+echo "=== Starting key expiration tests."
+
+FN="${DATA_DIR}/stderr"
+
+if [ "$CASE1" = 1 ]; then
+ echo "==== Case 1: Test --key-expiration without argument and ensure usage"
+ echo " instructions are printed."
+
+ ${TOR} ${QUIETLY} --key-expiration 2>"$FN"
+ grep "No valid argument to --key-expiration found!" "$FN" >/dev/null || \
+ die "Tor didn't mention supported --key-expiration argmuents"
+
+ echo "==== Case 1: ok"
+fi
+
+if [ "$CASE2" = 1 ]; then
+ echo "==== Case 2: Start Tor with --key-expiration 'sign' and make sure it prints an expiration."
+
+ ${TOR} ${QUIETLY} --key-expiration sign 2>"$FN"
+ grep "signing-cert-expiry:" "$FN" >/dev/null || \
+ die "Tor didn't print an expiration"
+
+ echo "==== Case 2: ok"
+fi
+
+if [ "$CASE3" = 1 ]; then
+ echo "==== Case 3: Start Tor with --key-expiration 'sign', when there is no"
+ echo " signing key, and make sure that Tor generates a new key"
+ echo " and prints its certificate's expiration."
+
+ mv "${DATA_DIR}/keys/ed25519_signing_cert" \
+ "${DATA_DIR}/keys/ed25519_signing_cert.bak"
+
+ ${TOR} --key-expiration sign > "$FN" 2>&1
+ grep "It looks like I need to generate and sign a new medium-term signing key" "$FN" >/dev/null || \
+ die "Tor didn't create a new signing key"
+ check_file "${DATA_DIR}/keys/ed25519_signing_cert"
+ grep "signing-cert-expiry:" "$FN" >/dev/null || \
+ die "Tor didn't print an expiration"
+
+ mv "${DATA_DIR}/keys/ed25519_signing_cert.bak" \
+ "${DATA_DIR}/keys/ed25519_signing_cert"
+
+ echo "==== Case 3: ok"
+fi
diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c
index feba8f664e..c1c178930a 100644
--- a/src/test/test_rendcache.c
+++ b/src/test/test_rendcache.c
@@ -21,22 +21,6 @@ static const int TIME_IN_THE_PAST = -(REND_CACHE_MAX_AGE + \
REND_CACHE_MAX_SKEW + 60);
static const int TIME_IN_THE_FUTURE = REND_CACHE_MAX_SKEW + 60;
-static rend_data_t *
-mock_rend_data(const char *onion_address)
-{
- rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data));
- rend_data_t *rend_query = &v2_data->base_;
- rend_query->version = 2;
-
- strlcpy(v2_data->onion_address, onion_address,
- sizeof(v2_data->onion_address));
- v2_data->auth_type = REND_NO_AUTH;
- rend_query->hsdirs_fp = smartlist_new();
- smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa",
- DIGEST_LEN));
- return rend_query;
-}
-
static void
test_rend_cache_lookup_entry(void *data)
{
diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c
index 4c536b0905..a2e77a45d4 100644
--- a/src/test/test_scheduler.c
+++ b/src/test/test_scheduler.c
@@ -567,7 +567,7 @@ test_scheduler_loop(void *arg)
channel_register(ch1);
tt_assert(ch1->registered);
/* Finish opening it */
- channel_change_state(ch1, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch1);
/* It should start off in SCHED_CHAN_IDLE */
tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_IDLE);
@@ -636,7 +636,7 @@ test_scheduler_loop(void *arg)
tt_int_op(smartlist_len(channels_pending), ==, 0);
/* Now, finish opening ch2, and get both back to pending */
- channel_change_state(ch2, CHANNEL_STATE_OPEN);
+ channel_change_state_open(ch2);
scheduler_channel_wants_writes(ch1);
scheduler_channel_wants_writes(ch2);
scheduler_channel_has_waiting_cells(ch1);
diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c
index 026a0f3825..bee0ea0a32 100644
--- a/src/test/test_shared_random.c
+++ b/src/test/test_shared_random.c
@@ -189,6 +189,120 @@ 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",
+ &current_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",
+ &current_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",
+ &current_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",
+ &current_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:
+ ;
+}
+
+/** 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)
+{
+ (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)
@@ -1272,6 +1386,12 @@ 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_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,
NULL, NULL },
{ "vote", test_vote, TT_FORK,
diff --git a/src/test/test_socks.c b/src/test/test_socks.c
index bb1be11f2b..94b94640cc 100644
--- a/src/test/test_socks.c
+++ b/src/test/test_socks.c
@@ -229,25 +229,24 @@ test_socks_5_supported_commands(void *ptr)
tt_int_op(0,OP_EQ, buf_datalen(buf));
socks_request_clear(socks);
- /* SOCKS 5 Should reject RESOLVE [F0] request for IPv4 address
+ /* SOCKS 5 Should NOT reject RESOLVE [F0] request for IPv4 address
* string if SafeSocks is enabled. */
ADD_DATA(buf, "\x05\x01\x00");
ADD_DATA(buf, "\x05\xF0\x00\x03\x07");
ADD_DATA(buf, "8.8.8.8");
- ADD_DATA(buf, "\x01\x02");
+ ADD_DATA(buf, "\x11\x11");
tt_assert(fetch_from_buf_socks(buf,socks,get_options()->TestSocks,1)
- == -1);
+ == 1);
- tt_int_op(5,OP_EQ,socks->socks_version);
- tt_int_op(10,OP_EQ,socks->replylen);
- tt_int_op(5,OP_EQ,socks->reply[0]);
- tt_int_op(SOCKS5_NOT_ALLOWED,OP_EQ,socks->reply[1]);
- tt_int_op(1,OP_EQ,socks->reply[3]);
+ tt_str_op("8.8.8.8", OP_EQ, socks->address);
+ tt_int_op(4369, OP_EQ, socks->port);
+
+ tt_int_op(0, OP_EQ, buf_datalen(buf));
socks_request_clear(socks);
- /* SOCKS 5 should reject RESOLVE [F0] reject for IPv6 address
+ /* SOCKS 5 should NOT reject RESOLVE [F0] reject for IPv6 address
* string if SafeSocks is enabled. */
ADD_DATA(buf, "\x05\x01\x00");
@@ -257,11 +256,10 @@ test_socks_5_supported_commands(void *ptr)
tt_assert(fetch_from_buf_socks(buf,socks,get_options()->TestSocks,1)
== -1);
- tt_int_op(5,OP_EQ,socks->socks_version);
- tt_int_op(10,OP_EQ,socks->replylen);
- tt_int_op(5,OP_EQ,socks->reply[0]);
- tt_int_op(SOCKS5_NOT_ALLOWED,OP_EQ,socks->reply[1]);
- tt_int_op(1,OP_EQ,socks->reply[3]);
+ tt_str_op("2001:0db8:85a3:0000:0000:8a2e:0370:7334", OP_EQ, socks->address);
+ tt_int_op(258, OP_EQ, socks->port);
+
+ tt_int_op(0, OP_EQ, buf_datalen(buf));
socks_request_clear(socks);
diff --git a/src/test/test_util.c b/src/test/test_util.c
index c6c0f1cd6a..891e4f6dbf 100644
--- a/src/test/test_util.c
+++ b/src/test/test_util.c
@@ -2125,10 +2125,13 @@ test_util_parse_integer(void *arg)
/* Base different than 10 */
tt_int_op(2L,OP_EQ, tor_parse_long("10",2,0,100,NULL,NULL));
tt_int_op(0L,OP_EQ, tor_parse_long("2",2,0,100,NULL,NULL));
- tt_int_op(0L,OP_EQ, tor_parse_long("10",-2,0,100,NULL,NULL));
tt_int_op(68284L,OP_EQ, tor_parse_long("10abc",16,0,70000,NULL,NULL));
tt_int_op(68284L,OP_EQ, tor_parse_long("10ABC",16,0,70000,NULL,NULL));
+ tor_capture_bugs_(2);
+ tt_int_op(0L,OP_EQ, tor_parse_long("10",-2,0,100,NULL,NULL));
tt_int_op(0,OP_EQ, tor_parse_long("10ABC",-1,0,70000,&i,NULL));
+ tt_int_op(2, OP_EQ, smartlist_len(tor_get_captured_bug_log_()));
+ tor_end_capture_bugs_();
tt_int_op(i,OP_EQ, 0);
/* Test parse_ulong */
@@ -2141,7 +2144,10 @@ test_util_parse_integer(void *arg)
tt_int_op(0UL,OP_EQ, tor_parse_ulong("8",8,0,100,NULL,NULL));
tt_int_op(50UL,OP_EQ, tor_parse_ulong("50",10,50,100,NULL,NULL));
tt_int_op(0UL,OP_EQ, tor_parse_ulong("-50",10,0,100,NULL,NULL));
+ tor_capture_bugs_(1);
tt_int_op(0UL,OP_EQ, tor_parse_ulong("50",-1,50,100,&i,NULL));
+ tt_int_op(1, OP_EQ, smartlist_len(tor_get_captured_bug_log_()));
+ tor_end_capture_bugs_();
tt_int_op(0,OP_EQ, i);
tt_int_op(0UL,OP_EQ, tor_parse_ulong("-50",10,0,100,&i,NULL));
tt_int_op(0,OP_EQ, i);
@@ -2157,8 +2163,11 @@ test_util_parse_integer(void *arg)
tt_assert(U64_LITERAL(0) ==
tor_parse_uint64("12345678901",10,500,INT32_MAX, &i, &cp));
tt_int_op(0,OP_EQ, i);
+ tor_capture_bugs_(1);
tt_assert(U64_LITERAL(0) ==
tor_parse_uint64("123",-1,0,INT32_MAX, &i, &cp));
+ tt_int_op(1, OP_EQ, smartlist_len(tor_get_captured_bug_log_()));
+ tor_end_capture_bugs_();
tt_int_op(0,OP_EQ, i);
{
diff --git a/src/trunnel/channelpadding_negotiation.c b/src/trunnel/channelpadding_negotiation.c
index 172d6f8a03..02fd823bf6 100644
--- a/src/trunnel/channelpadding_negotiation.c
+++ b/src/trunnel/channelpadding_negotiation.c
@@ -1,4 +1,4 @@
-/* channelpadding_negotiation.c -- generated by Trunnel v1.4.3.
+/* channelpadding_negotiation.c -- generated by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -57,7 +57,7 @@ channelpadding_negotiate_free(channelpadding_negotiate_t *obj)
}
uint8_t
-channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp)
+channelpadding_negotiate_get_version(const channelpadding_negotiate_t *inp)
{
return inp->version;
}
@@ -72,7 +72,7 @@ channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_t va
return 0;
}
uint8_t
-channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp)
+channelpadding_negotiate_get_command(const channelpadding_negotiate_t *inp)
{
return inp->command;
}
@@ -87,7 +87,7 @@ channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_t va
return 0;
}
uint16_t
-channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp)
+channelpadding_negotiate_get_ito_low_ms(const channelpadding_negotiate_t *inp)
{
return inp->ito_low_ms;
}
@@ -98,7 +98,7 @@ channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uint16_
return 0;
}
uint16_t
-channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp)
+channelpadding_negotiate_get_ito_high_ms(const channelpadding_negotiate_t *inp)
{
return inp->ito_high_ms;
}
diff --git a/src/trunnel/channelpadding_negotiation.h b/src/trunnel/channelpadding_negotiation.h
index e58bda3be1..631c99670c 100644
--- a/src/trunnel/channelpadding_negotiation.h
+++ b/src/trunnel/channelpadding_negotiation.h
@@ -1,4 +1,4 @@
-/* channelpadding_negotiation.h -- generated by by Trunnel v1.4.3.
+/* channelpadding_negotiation.h -- generated by by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -60,7 +60,7 @@ int channelpadding_negotiate_clear_errors(channelpadding_negotiate_t *obj);
/** Return the value of the version field of the
* channelpadding_negotiate_t in 'inp'
*/
-uint8_t channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp);
+uint8_t channelpadding_negotiate_get_version(const channelpadding_negotiate_t *inp);
/** Set the value of the version field of the
* channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -69,7 +69,7 @@ int channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_
/** Return the value of the command field of the
* channelpadding_negotiate_t in 'inp'
*/
-uint8_t channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp);
+uint8_t channelpadding_negotiate_get_command(const channelpadding_negotiate_t *inp);
/** Set the value of the command field of the
* channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -78,7 +78,7 @@ int channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_
/** Return the value of the ito_low_ms field of the
* channelpadding_negotiate_t in 'inp'
*/
-uint16_t channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp);
+uint16_t channelpadding_negotiate_get_ito_low_ms(const channelpadding_negotiate_t *inp);
/** Set the value of the ito_low_ms field of the
* channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -87,7 +87,7 @@ int channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uin
/** Return the value of the ito_high_ms field of the
* channelpadding_negotiate_t in 'inp'
*/
-uint16_t channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp);
+uint16_t channelpadding_negotiate_get_ito_high_ms(const channelpadding_negotiate_t *inp);
/** Set the value of the ito_high_ms field of the
* channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
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;
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 <stdlib.h>
+#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 <stdint.h>
+#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)
diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h
index fc52ca3a17..9b16c64752 100644
--- a/src/win32/orconfig.h
+++ b/src/win32/orconfig.h
@@ -218,7 +218,7 @@
#define USING_TWOS_COMPLEMENT
/* Version number of package */
-#define VERSION "0.3.1.5-alpha-dev"
+#define VERSION "0.3.2.0-alpha-dev"