diff options
Diffstat (limited to 'src')
100 files changed, 5236 insertions, 1120 deletions
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/crypto.c b/src/common/crypto.c index 0fc8474832..c258f239a8 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 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..d61549b797 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 @@ -491,7 +497,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 +509,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 +531,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 +762,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/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/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..6bc22675ae 100644 --- a/src/ext/ed25519/donna/ed25519_tor.c +++ b/src/ext/ed25519/donna/ed25519_tor.c @@ -304,7 +304,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 +342,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..31332a2719 100644 --- a/src/ext/ed25519/ref10/blinding.c +++ b/src/ext/ed25519/ref10/blinding.c @@ -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..d5ecfb8488 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -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..d11e128787 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -67,6 +67,7 @@ #include "main.h" #include "hs_circuitmap.h" #include "hs_common.h" +#include "hs_ident.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -957,6 +958,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) { diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index d647062f46..2f76252563 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -48,6 +48,7 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const uint8_t *digest, uint8_t purpose); origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_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/circuituse.c b/src/or/circuituse.c index 9f9d3abf7c..a3b7066b18 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -42,6 +42,8 @@ #include "control.h" #include "entrynodes.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -55,6 +57,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 +201,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; } } @@ -2348,8 +2375,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/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 a0ff0e871a..de27ddb7c4 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" @@ -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 } }; @@ -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; } @@ -2798,10 +2806,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 +3159,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 +4007,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. */ @@ -6252,8 +6256,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) @@ -6937,7 +6942,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..ddcff6aa94 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -2455,8 +2455,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 +3007,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 @@ -3566,8 +3566,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..051bf9a176 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -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/directory.c b/src/or/directory.c index 45fbd1dd33..13daea354c 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" @@ -4918,7 +4917,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 +5123,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 +5192,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 +5202,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 +5223,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 +5379,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 +5408,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 +5438,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 +5461,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 +5494,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 +5523,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..3e574cc6ac 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -123,12 +123,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 +144,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 +158,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 +199,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 +212,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..cc062e30ef 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); } @@ -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"); - } + } } } 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/hs_circuit.c b/src/or/hs_circuit.c new file mode 100644 index 0000000000..2f595d72e5 --- /dev/null +++ b/src/or/hs_circuit.c @@ -0,0 +1,224 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.c + **/ + +#include "or.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" + +#include "hs_circuit.h" +#include "hs_ident.h" +#include "hs_ntor.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); + } +} + +/* 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..71ce5c3331 --- /dev/null +++ b/src/or/hs_circuit.h @@ -0,0 +1,24 @@ +/* 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" + +/* 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..27330bfcdb 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -15,9 +15,25 @@ #include "config.h" #include "networkstatus.h" +#include "hs_cache.h" #include "hs_common.h" +#include "hs_service.h" #include "rendcommon.h" +/* 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>. * If <b>create</b> is true: @@ -344,3 +360,204 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* 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 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)); +} + +/* 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(); +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a8fded652a..203a5d0818 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -16,10 +16,13 @@ #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. */ @@ -49,9 +52,48 @@ /* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ +/* Prefix of the onion address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" +/* 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)) + +/* 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; + +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 rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); 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..2393eac252 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -62,6 +62,7 @@ #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. */ @@ -1747,18 +1748,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 +1795,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 +2033,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; } diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index b8b94792de..58c4089795 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -223,9 +223,6 @@ 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); diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c new file mode 100644 index 0000000000..5b5dc9aaff --- /dev/null +++ b/src/or/hs_ident.c @@ -0,0 +1,81 @@ +/* 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; + } + if (ident->auth_key_type == HS_AUTH_KEY_TYPE_LEGACY) { + crypto_pk_free(ident->auth_rsa_pk); + } + memwipe(ident, 0, sizeof(hs_ident_circuit_t)); + tor_free(ident); +} + +/* 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..8a7c3598cf --- /dev/null +++ b/src/or/hs_ident.h @@ -0,0 +1,124 @@ +/* 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; + + /* (Only intro point circuit) Which type of authentication key this + * circuit identifier is using. */ + hs_auth_key_type_t auth_key_type; + + /* (Only intro point circuit) Introduction point authentication key. In + * legacy mode, we use an RSA key else an ed25519 public key. */ + crypto_pk_t *auth_rsa_pk; + ed25519_public_key_t auth_ed25519_pk; + + /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + * service with an INTRODUCE1 cell and used by the service in an + * RENDEZVOUS1 cell. */ + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + + /* (Only rendezvous circuit) The HANDSHAKE_INFO needed in the RENDEZVOUS1 + * cell of the service. The construction is as follows: + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] + */ + uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + /* (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); + +/* 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.h b/src/or/hs_intropoint.h index 163ed810e7..bfb1331ba0 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -9,6 +9,9 @@ #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 { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, @@ -24,6 +27,15 @@ 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 { + /* 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); 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..5fde42ddbb 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -6,19 +6,644 @@ * \brief Implement next generation hidden service functionality **/ +#define HS_SERVICE_PRIVATE + #include "or.h" +#include "circuitlist.h" +#include "config.h" #include "relay.h" #include "rendservice.h" -#include "circuitlist.h" -#include "circpathbias.h" +#include "router.h" +#include "routerkeys.h" +#include "hs_common.h" +#include "hs_config.h" #include "hs_intropoint.h" #include "hs_service.h" -#include "hs_common.h" #include "hs/cell_establish_intro.h" #include "hs/cell_common.h" +/* 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; + +/* 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); +} + +/* 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) +{ + tor_assert(map); + tor_assert(service); + tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk)); + + 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; + } + + 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) +{ + tor_assert(c); + c->ports = smartlist_new(); + c->directory_path = NULL; + c->descriptor_post_period = options->RendPostPeriod; + c->max_streams_per_rdv_circuit = 0; + c->max_streams_close_circuit = 0; + c->num_intro_points = NUM_INTRO_POINTS_DEFAULT; + c->allow_unknown_ports = 0; + c->is_single_onion = 0; + c->dir_group_readable = 0; + c->is_ephemeral = 0; +} + +/* From a service configuration object config, clear everything from it + * meaning free allocated pointers and reset the values. */ +static void +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)); +} + +/* 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; + } + + 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; + } +} + +/* Close all rendezvous circuits for the given service. */ +static void +close_service_rp_circuits(hs_service_t *service) +{ + tor_assert(service); + /* XXX: To implement. */ + return; +} + +/* 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_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + if (ocirc) { + hs_circuitmap_remove_circuit(TO_CIRCUIT(ocirc)); + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Close all introduction circuits for the given service. */ +static void +close_service_intro_circuits(hs_service_t *service) +{ + tor_assert(service); + + if (service->desc_current) { + close_intro_circuits(&service->desc_current->intro_points); + } + if (service->desc_next) { + close_intro_circuits(&service->desc_next->intro_points); + } +} + +/* 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); + + /* XXX: Free dst introduction points. */ + 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); + + /* Cleanup destination. */ + 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); + + /* 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; + hs_service_t *s, **iter; + + 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) { + /* 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. */ + HT_FOREACH(iter, hs_service_ht, hs_service_map) { + close_service_circuits(*iter); + } + + /* 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; + /* Length of an address plus the sizeof the address tld (onion) which counts + * the NUL terminated byte so we keep it for the "." and the newline. */ + char buf[HS_SERVICE_ADDR_LEN_BASE32 + sizeof(address_tld) + 1]; + + tor_assert(service); + tor_assert(fname_); + + /* Construct the full address with the onion tld and write the hostname file + * to disk. */ + tor_snprintf(buf, sizeof(buf), "%s.%s\n", service->onion_address, + address_tld); + /* Notice here that we use the given "fname_". */ + fname = hs_path_from_filename(service->config.directory_path, fname_); + if (write_str_to_file(fname, buf, 0) < 0) { + 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); + 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; +} + +/* 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 (num_rend_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; + 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. */ + if (service->desc_current) { + hs_descriptor_free(service->desc_current->desc); + /* Wipe keys. */ + memwipe(&service->desc_current->signing_kp, 0, + sizeof(service->desc_current->signing_kp)); + memwipe(&service->desc_current->blinded_kp, 0, + sizeof(service->desc_current->blinded_kp)); + /* XXX: Free intro points. */ + tor_free(service->desc_current); + } + if (service->desc_next) { + hs_descriptor_free(service->desc_next->desc); + /* Wipe keys. */ + memwipe(&service->desc_next->signing_kp, 0, + sizeof(service->desc_next->signing_kp)); + memwipe(&service->desc_next->blinded_kp, 0, + sizeof(service->desc_next->blinded_kp)); + /* XXX: Free intro points. */ + tor_free(service->desc_next); + } + + /* Free service configuration. */ + service_clear_config(&service->config); + + /* Wipe service keys. */ + memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); + + tor_free(service); +} + +/* 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(); +} + /* 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. */ @@ -172,3 +797,37 @@ generate_establish_intro_cell(const uint8_t *circuit_key_material, return NULL; } +#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..54b9e69724 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -3,15 +3,222 @@ /** * \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_intropoint.h" + +/* Trunnel */ #include "hs/cell_establish_intro.h" +/* 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 + +/* Service side introduction point. */ +typedef struct hs_service_intro_point_t { + /* Top level intropoint "shared" data between client/service. */ + hs_intropoint_t base; + + /* Authentication keypair used to create the authentication certificate + * which is published in the descriptor. */ + ed25519_keypair_t auth_key_kp; + + /* Encryption private key. */ + curve25519_secret_key_t enc_key_sk; + + /* 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; +} 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; +} 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 time period after which a descriptor is uploaded to the directories + * in seconds. Specified by RendPostPeriod option. */ + uint32_t descriptor_post_period; + + /* The maximum number of simultaneous streams per rendezvous circuit that + * are allowed to be created. No limit if 0. Specified by + * HiddenServiceMaxStreams option. */ + 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; +} 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); + +void hs_service_stage_services(const smartlist_t *service_list); +int hs_service_load_all_keys(void); + /* 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. */ @@ -23,5 +230,25 @@ ssize_t get_establish_intro_payload(uint8_t *buf, size_t buf_len, const trn_cell_establish_intro_t *cell); +#ifdef HS_SERVICE_PRIVATE + +#ifdef TOR_UNIT_TESTS + +/* 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); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* HS_SERVICE_PRIVATE */ + #endif /* TOR_HS_SERVICE_H */ diff --git a/src/or/include.am b/src/or/include.am index 1ef5afa013..15b86ef50b 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -50,16 +50,20 @@ 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_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 +184,16 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_circuit.h \ + src/or/hs_circuitmap.h \ + src/or/hs_client.h \ src/or/hs_common.h \ + src/or/hs_config.h \ src/or/hs_descriptor.h \ - src/or/hs_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..dc23184961 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -2060,6 +2060,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 +2074,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 @@ -2355,7 +2364,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 +2508,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 +2883,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 +3019,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 +3222,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 +3236,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 +3244,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 +3482,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) 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..aff36b4c0b 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; @@ -1783,7 +1783,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/nodelist.c b/src/or/nodelist.c index 3ac5c3e302..dafeb9f12d 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -630,11 +630,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 +705,48 @@ 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; + } + 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/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..f6c42b7a99 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -846,6 +846,11 @@ 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; + /** 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 +1638,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 +1793,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 +2073,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 @@ -3186,6 +3203,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. */ 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..986bfde75f 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; 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..98ed1100ec 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" @@ -231,18 +232,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(); +} - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_list); - rend_service_list = NULL; +/* Initialize the subsystem. */ +void +rend_service_init(void) +{ + tor_assert(!rend_service_list); + tor_assert(!rend_service_staging_list); + + 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 +276,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 +308,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 +329,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 +491,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 +617,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 +672,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 +697,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 +713,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 +721,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 +736,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 +773,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 +795,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 +1063,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 +1434,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 */ @@ -2195,7 +2081,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 +2731,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); @@ -3258,7 +3144,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); diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 1583a6010b..ffed21d14e 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -13,6 +13,7 @@ #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; @@ -119,10 +120,6 @@ 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); @@ -144,8 +141,11 @@ 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_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); @@ -179,6 +179,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..1b81a228af 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. @@ -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/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..f4e87a00d8 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; @@ -3360,8 +3358,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 +5286,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/test/ed25519_exts_ref.py b/src/test/ed25519_exts_ref.py index af5010415e..1898256540 100644 --- a/src/test/ed25519_exts_ref.py +++ b/src/test/ed25519_exts_ref.py @@ -69,6 +69,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 +131,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/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/include.am b/src/test/include.am index 29ba1ce7c9..2e448c8b39 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -114,7 +114,9 @@ src_test_test_SOURCES = \ src/test/test_guardfraction.c \ src/test/test_extorport.c \ src/test/test_hs.c \ + src/test/test_hs_config.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 +272,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 \ 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..c5c394900c 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,10 @@ struct testgroup_t testgroups[] = { { "extorport/", extorport_tests }, { "legacy_hs/", hs_tests }, { "hs_cache/", hs_cache }, + { "hs_config/", hs_config_tests }, { "hs_descriptor/", hs_descriptor }, { "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..9b2a0b842f 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -207,8 +207,10 @@ 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_config_tests[]; extern struct testcase_t hs_descriptor[]; 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_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_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 b4817a21ea..093c7ec35e 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); @@ -573,17 +662,8 @@ test_single_onion_poisoning(void *arg) smartlist_t *services = smartlist_new(); char *poison_path = 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; @@ -941,6 +1025,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_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_config.c b/src/test/test_hs_config.c new file mode 100644 index 0000000000..d14e3dd8ae --- /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(num_rend_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_service.c b/src/test/test_hs_service.c index fcfb3b992d..4a7cb81140 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -6,20 +6,58 @@ * \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 TOR_CHANNEL_INTERNAL_ #include "test.h" +#include "test_helpers.h" #include "log_test_helpers.h" +#include "rend_test_helpers.h" + +#include "or.h" +#include "channeltls.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "connection.h" #include "crypto.h" - -#include "hs/cell_establish_intro.h" +#include "hs_circuit.h" #include "hs_common.h" -#include "hs_service.h" +#include "hs_config.h" +#include "hs_ident.h" #include "hs_intropoint.h" - #include "hs_ntor.h" +#include "hs_service.h" +#include "main.h" +#include "rendservice.h" + +/* Trunnel */ +#include "hs/cell_establish_intro.h" + +/* 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; +} /** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we * parse it from the receiver side. */ @@ -193,6 +231,85 @@ test_hs_ntor(void *arg) tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, service_hs_ntor_rend_cell_keys.ntor_key_seed, DIGEST256_LEN); + done: + ; +} + +static void +test_validate_address(void *arg) +{ + int ret; + + (void) arg; + + /* Address too short and too long. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid("blah"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + /* Invalid checksum (taken from prop224) */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + /* Non base32 decodable string. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "????????????????????????????????????????????????????????"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("can't be decoded"); + teardown_capture_of_logs(); + + /* Valid address. */ + ret = hs_address_is_valid( + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnad"); + tt_int_op(ret, OP_EQ, 1); + + done: + ; +} + +static void +test_build_address(void *arg) +{ + int ret; + char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_public_key_t pubkey; + + (void) arg; + + /* The following has been created with hs_build_address.py script that + * follows proposal 224 specification to build an onion address. */ + static const char *test_addr = + "ijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbeeqscijbezhid"; + + /* Let's try to build the same onion address that the script can do. Key is + * a long set of very random \x42 :). */ + memset(&pubkey, '\x42', sizeof(pubkey)); + hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); + tt_str_op(test_addr, OP_EQ, onion_addr); + /* Validate that address. */ + ret = hs_address_is_valid(onion_addr); + tt_int_op(ret, OP_EQ, 1); done: ; @@ -235,6 +352,207 @@ test_time_period(void *arg) ; } +/* 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)); +} + +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(num_rend_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(); +} + struct testcase_t hs_service_tests[] = { { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, NULL, NULL }, @@ -244,6 +562,16 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "time_period", test_time_period, TT_FORK, NULL, NULL }, + { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, + NULL, NULL }, + { "build_address", test_build_address, TT_FORK, + NULL, NULL }, + { "validate_address", test_validate_address, TT_FORK, + NULL, NULL }, + { "load_keys", test_load_keys, TT_FORK, + NULL, NULL }, + { "access_service", test_access_service, TT_FORK, + NULL, NULL }, 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_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_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/win32/orconfig.h b/src/win32/orconfig.h index caaf481731..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.4-alpha-dev" +#define VERSION "0.3.2.0-alpha-dev" |