diff options
Diffstat (limited to 'src')
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, + ô->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(ô->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", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:59 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + { + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + ¤t_time); + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + /* Now let's alter the voting schedule and check the correctness of the + * function. Voting interval of 10 seconds, means that an SRV protocol run + * takes 10 seconds * 24 rounds = 4 mins */ + { + or_options_t *options = get_options_mutable(); + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:15:32 UTC", + ¤t_time); + + tt_int_op(retval, ==, 0); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); + + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:12:00", OP_EQ, tbuf); + } + + done: + ; +} + +/** 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" |