diff options
-rw-r--r-- | changes/feature17178 | 15 | ||||
-rw-r--r-- | doc/tor.1.txt | 35 | ||||
-rw-r--r-- | src/or/circuitbuild.c | 9 | ||||
-rw-r--r-- | src/or/circuitstats.c | 28 | ||||
-rw-r--r-- | src/or/config.c | 160 | ||||
-rw-r--r-- | src/or/config.h | 2 | ||||
-rw-r--r-- | src/or/connection_edge.c | 29 | ||||
-rw-r--r-- | src/or/directory.c | 12 | ||||
-rw-r--r-- | src/or/directory.h | 3 | ||||
-rw-r--r-- | src/or/main.c | 13 | ||||
-rw-r--r-- | src/or/or.h | 21 | ||||
-rw-r--r-- | src/or/rendclient.c | 49 | ||||
-rw-r--r-- | src/or/rendclient.h | 3 | ||||
-rw-r--r-- | src/or/rendcommon.c | 48 | ||||
-rw-r--r-- | src/or/rendcommon.h | 6 | ||||
-rw-r--r-- | src/or/rendservice.c | 299 | ||||
-rw-r--r-- | src/or/rendservice.h | 9 | ||||
-rw-r--r-- | src/test/test_hs.c | 207 | ||||
-rw-r--r-- | src/test/test_options.c | 149 |
19 files changed, 1020 insertions, 77 deletions
diff --git a/changes/feature17178 b/changes/feature17178 new file mode 100644 index 0000000000..465f491e85 --- /dev/null +++ b/changes/feature17178 @@ -0,0 +1,15 @@ + o Major features (onion services): + - Add experimental OnionServiceSingleHopMode and + OnionServiceNonAnonymousMode options. When both are set to 1, every + hidden service on a tor instance becomes a non-anonymous Single Onion + Service. Single Onions make one-hop (direct) connections to their + introduction and renzedvous points. One-hop circuits make Single Onion + servers easily locatable, but clients remain location-anonymous. + This is compatible with the existing hidden service implementation, and + works on the current tor network without any changes to older relays or + clients. + Implements proposal #260, completes ticket #17178. Patch by teor & asn. + o Minor bug fixes (Tor2web): + - Prevent Tor2web clients running hidden services, these services are + not anonymous due to the one-hop client paths. + Fixes bug #19678. Patch by teor. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 0687991e1f..bd25a614a8 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1425,7 +1425,9 @@ The following options are useful only for clients (that is, if non-hidden-service hostnames through Tor. It **must only** be used when running a tor2web Hidden Service web proxy. To enable this option the compile time flag --enable-tor2web-mode must be - specified. (Default: 0) + specified. Since Tor2webMode is non-anonymous, you can not run an + anonymous Hidden Service on a tor version compiled with Tor2webMode. + (Default: 0) [[Tor2webRendezvousPoints]] **Tor2webRendezvousPoints** __node__,__node__,__...__:: A list of identity fingerprints, nicknames, country codes and @@ -2375,6 +2377,37 @@ The following options are used to configure a hidden service. Number of introduction points the hidden service will have. You can't have more than 10. (Default: 3) +[[OnionServiceSingleHopMode]] **OnionServiceSingleHopMode** **0**|**1**:: + **Experimental - Non Anonymous** Hidden Services on a tor instance in + OnionServiceSingleHopMode make one-hop (direct) circuits between the onion + service server, and the introduction and rendezvous points. (Onion service + descriptors are still posted using 3-hop paths, to avoid onion service + directories blocking the service.) + This option makes every hidden service instance hosted by a tor instance a + Single Onion Service. One-hop circuits make Single Onion servers easily + locatable, but clients remain location-anonymous. However, the fact that a + client is accessing a Single Onion rather than a Hidden Service may be + statistically distinguishable. + + **WARNING:** Once a hidden service directory has been used by a tor + instance in OnionServiceSingleHopMode, it can **NEVER** be used again for + a hidden service. It is best practice to create a new hidden service + directory, key, and address for each new Single Onion Service and Hidden + Service. It is not possible to run Single Onion Services and Hidden + Services from the same tor instance: they should be run on different + servers with different IP addresses. + + OnionServiceSingleHopMode requires OnionServiceNonAnonymousMode to be set + to 1. Since a Single Onion is non-anonymous, you can not to run an + anonymous SOCKSPort on the same tor instance as a Single Onion service. + (Default: 0) + +[[OnionServiceNonAnonymousMode]] **OnionServiceNonAnonymousMode** **0**|**1**:: + Makes hidden services non-anonymous on this tor instance. Allows the + non-anonymous OnionServiceSingleHopMode. Enables direct connections in the + server-side hidden service protocol. + (Default: 0) + TESTING NETWORK OPTIONS ----------------------- diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 2e7ea2f79a..69a8a9c5ec 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -28,6 +28,7 @@ #include "connection_edge.h" #include "connection_or.h" #include "control.h" +#include "crypto.h" #include "directory.h" #include "entrynodes.h" #include "main.h" @@ -38,14 +39,14 @@ #include "onion_tap.h" #include "onion_fast.h" #include "policies.h" -#include "transports.h" #include "relay.h" +#include "rendcommon.h" #include "rephist.h" #include "router.h" #include "routerlist.h" #include "routerparse.h" #include "routerset.h" -#include "crypto.h" +#include "transports.h" static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, @@ -1996,7 +1997,9 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) cpath_build_state_t *state = circ->build_state; if (state->onehop_tunnel) { - log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel."); + log_debug(LD_CIRC, "Launching a one-hop circuit for dir tunnel%s.", + (rend_allow_non_anonymous_connection(get_options()) ? + ", or intro or rendezvous connection" : "")); state->desired_path_len = 1; } else { int r = new_route_len(circ->base_.purpose, exit_ei, nodelist_get_list()); diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index f4db64ebca..fe8860e9c9 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -21,6 +21,8 @@ #include "control.h" #include "main.h" #include "networkstatus.h" +#include "rendclient.h" +#include "rendservice.h" #include "statefile.h" #undef log @@ -81,12 +83,14 @@ get_circuit_build_timeout_ms(void) /** * This function decides if CBT learning should be disabled. It returns - * true if one or more of the following four conditions are met: + * true if one or more of the following conditions are met: * * 1. If the cbtdisabled consensus parameter is set. * 2. If the torrc option LearnCircuitBuildTimeout is false. * 3. If we are a directory authority * 4. If we fail to write circuit build time history to our state file. + * 5. If we are compiled or configured in Tor2web mode + * 6. If we are configured in Single Onion mode */ int circuit_build_times_disabled(void) @@ -94,14 +98,30 @@ circuit_build_times_disabled(void) if (unit_tests) { return 0; } else { + const or_options_t *options = get_options(); int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled", 0, 0, 1); - int config_disabled = !get_options()->LearnCircuitBuildTimeout; - int dirauth_disabled = get_options()->AuthoritativeDir; + int config_disabled = !options->LearnCircuitBuildTimeout; + int dirauth_disabled = options->AuthoritativeDir; int state_disabled = did_last_state_file_write_fail() ? 1 : 0; + /* LearnCircuitBuildTimeout and Tor2webMode/OnionServiceSingleHopMode are + * incompatible in two ways: + * + * - LearnCircuitBuildTimeout results in a low CBT, which + * Single Onion use of one-hop intro and rendezvous circuits lowers + * much further, producing *far* too many timeouts. + * + * - The adaptive CBT code does not update its timeout estimate + * using build times for single-hop circuits. + * + * If we fix both of these issues someday, we should test + * these modes with LearnCircuitBuildTimeout on again. */ + int tor2web_disabled = rend_client_allow_non_anonymous_connection(options); + int single_onion_disabled = rend_service_allow_non_anonymous_connection( + options); if (consensus_disabled || config_disabled || dirauth_disabled || - state_disabled) { + state_disabled || tor2web_disabled || single_onion_disabled) { #if 0 log_debug(LD_CIRC, "CircuitBuildTime learning is disabled. " diff --git a/src/or/config.c b/src/or/config.c index 136958c6aa..f7f1f571ce 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -18,6 +18,7 @@ #include "circuitlist.h" #include "circuitmux.h" #include "circuitmux_ewma.h" +#include "circuitstats.h" #include "config.h" #include "connection.h" #include "connection_edge.h" @@ -297,6 +298,8 @@ static config_var_t option_vars_[] = { V(HidServAuth, LINELIST, NULL), V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"), V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"), + V(OnionServiceSingleHopMode, BOOL, "0"), + V(OnionServiceNonAnonymousMode,BOOL, "0"), V(HTTPProxy, STRING, NULL), V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), @@ -1558,10 +1561,10 @@ options_act(const or_options_t *old_options) if (consider_adding_dir_servers(options, old_options) < 0) return -1; -#ifdef NON_ANONYMOUS_MODE_ENABLED - log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a " - "non-anonymous mode. It will provide NO ANONYMITY."); -#endif + if (rend_non_anonymous_mode_enabled(options)) { + log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run " + "in a non-anonymous mode. It will provide NO ANONYMITY."); + } #ifdef ENABLE_TOR2WEB_MODE /* LCOV_EXCL_START */ @@ -1723,8 +1726,27 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); + /* We must create new keys after we poison the directories, because our + * poisoning code checks for existing keys, and refuses to modify their + * directories. */ + + /* If we use the insecure OnionServiceSingleHopMode, make sure we poison any + new hidden service directories, so that we never accidentally launch the + non-anonymous hidden services thinking they are anonymous. */ + if (running_tor && rend_service_allow_non_anonymous_connection(options)) { + if (options->RendConfigLines && !num_rend_services()) { + log_warn(LD_BUG,"Error: hidden services configured, but not parsed."); + return -1; + } + if (rend_service_poison_new_single_onion_dirs(NULL) < 0) { + log_warn(LD_GENERAL,"Failed to mark new hidden services as Single " + "Onion."); + return -1; + } + } + /* reload keys as needed for rendezvous services. */ - if (rend_service_load_all_keys()<0) { + if (rend_service_load_all_keys(NULL)<0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } @@ -2796,6 +2818,88 @@ warn_about_relative_paths(or_options_t *options) } } +/* Validate options related to OnionServiceSingleHopMode. + * Modifies some options that are incompatible with OnionServiceSingleHopMode. + * On failure returns -1, and sets *msg to an error string. + * Returns 0 on success. */ +STATIC int +options_validate_single_onion(or_options_t *options, char **msg) +{ + /* You must set OnionServiceNonAnonymousMode to 1 to use + * OnionServiceSingleHopMode */ + if (options->OnionServiceSingleHopMode && + !rend_service_non_anonymous_mode_enabled(options)) { + REJECT("OnionServiceSingleHopMode does not provide any server anonymity. " + "It must be used with OnionServiceNonAnonymousMode set to 1."); + } + + /* If you have OnionServiceNonAnonymousMode set, you must use + * OnionServiceSingleHopMode. */ + if (rend_service_non_anonymous_mode_enabled(options) && + !options->OnionServiceSingleHopMode) { + REJECT("OnionServiceNonAnonymousMode does not provide any server " + "anonymity. It must be used with OnionServiceSingleHopMode set to " + "1."); + } + + /* If you run an anonymous client with an active Single Onion service, the + * client loses anonymity. */ + const int client_port_set = (options->SocksPort_set || + options->TransPort_set || + options->NATDPort_set || + options->DNSPort_set); + if (options->OnionServiceSingleHopMode && client_port_set && + !options->Tor2webMode) { + REJECT("OnionServiceSingleHopMode is incompatible with using Tor as an " + "anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or " + "OnionServiceSingleHopMode to 0, or use the non-anonymous " + "Tor2webMode."); + } + + /* If you run a hidden service in non-anonymous mode, the hidden service + * loses anonymity, even if SOCKSPort / Tor2web mode isn't used. */ + if (!options->OnionServiceSingleHopMode && options->RendConfigLines + && options->Tor2webMode) { + REJECT("Non-anonymous (Tor2web) mode is incompatible with using Tor as a " + "hidden service. Please remove all HiddenServiceDir lines, or use " + "a version of tor compiled without --enable-tor2web-mode, or use " + "the non-anonymous OnionServiceSingleHopMode."); + } + + if (options->OnionServiceSingleHopMode + && options->UseEntryGuards) { + /* Single Onion services do not (and should not) use entry guards + * in any meaningful way. Further, Single Onions causes the hidden + * service code to do things which break the path bias + * detector, and it's far easier to turn off entry guards (and + * thus the path bias detector with it) than to figure out how to + * make a piece of code which cannot possibly help Single Onions, + * compatible with OnionServiceSingleHopMode. + */ + log_notice(LD_CONFIG, + "OnionServiceSingleHopMode is enabled; disabling " + "UseEntryGuards."); + options->UseEntryGuards = 0; + } + + /* Check if existing hidden service keys were created with a different + * setting of OnionServiceNonAnonymousMode, and refuse to launch if they + * have. We'll poison new keys in options_act() just before we create them. + */ + if (rend_service_list_verify_single_onion_poison(NULL, options) < 0) { + log_warn(LD_GENERAL, "We are configured with OnionServiceSingleHopMode " + "%d, but one or more hidden service keys were created in %s " + "mode. This is not allowed.", + rend_service_non_anonymous_mode_enabled(options) ? 1 : 0, + rend_service_non_anonymous_mode_enabled(options) ? + "an anonymous" : "a non-anonymous" + ); + return -1; + } + + return 0; +} + /** Return 0 if every setting in <b>options</b> is reasonable, is a * permissible transition from <b>old_options</b>, and none of the * testing-only settings differ from <b>default_options</b> unless in @@ -3291,25 +3395,11 @@ options_validate(or_options_t *old_options, or_options_t *options, options->PredictedPortsRelevanceTime = MAX_PREDICTED_CIRCS_RELEVANCE; } -#ifdef ENABLE_TOR2WEB_MODE - if (options->Tor2webMode && options->LearnCircuitBuildTimeout) { - /* LearnCircuitBuildTimeout and Tor2webMode are incompatible in - * two ways: - * - * - LearnCircuitBuildTimeout results in a low CBT, which - * Tor2webMode's use of one-hop rendezvous circuits lowers - * much further, producing *far* too many timeouts. - * - * - The adaptive CBT code does not update its timeout estimate - * using build times for single-hop circuits. - * - * If we fix both of these issues someday, we should test - * Tor2webMode with LearnCircuitBuildTimeout on again. */ - log_notice(LD_CONFIG,"Tor2webMode is enabled; turning " - "LearnCircuitBuildTimeout off."); - options->LearnCircuitBuildTimeout = 0; - } + /* Check the Single Onion Service options */ + if (options_validate_single_onion(options, msg) < 0) + return -1; +#ifdef ENABLE_TOR2WEB_MODE if (options->Tor2webMode && options->UseEntryGuards) { /* tor2web mode clients do not (and should not) use entry guards * in any meaningful way. Further, tor2web mode causes the hidden @@ -3353,6 +3443,17 @@ options_validate(or_options_t *old_options, or_options_t *options, return -1; } + /* OnionServiceSingleHopMode: one hop between the onion service server and + * intro and rendezvous points */ + if (options->OnionServiceSingleHopMode) { + log_warn(LD_CONFIG, + "OnionServiceSingleHopMode is set. Every hidden service on this " + "tor instance is NON-ANONYMOUS. If OnionServiceSingleHopMode is " + "disabled, Tor will refuse to launch hidden services from the " + "same directories, to protect against config errors. This " + "setting is for experimental use only."); + } + if (!options->LearnCircuitBuildTimeout && options->CircuitBuildTimeout && options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) { log_warn(LD_CONFIG, @@ -4295,6 +4396,19 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->OnionServiceSingleHopMode != new_val->OnionServiceSingleHopMode) { + *msg = tor_strdup("While Tor is running, changing " + "OnionServiceSingleHopMode is not allowed."); + return -1; + } + + if (old->OnionServiceNonAnonymousMode != + new_val->OnionServiceNonAnonymousMode) { + *msg = tor_strdup("While Tor is running, changing " + "OnionServiceNonAnonymousMode is not allowed."); + return -1; + } + if (old->DisableDebuggerAttachment && !new_val->DisableDebuggerAttachment) { *msg = tor_strdup("While Tor is running, disabling " diff --git a/src/or/config.h b/src/or/config.h index 7db66a31b9..208659acb7 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -166,6 +166,8 @@ extern struct config_format_t options_format; STATIC port_cfg_t *port_cfg_new(size_t namelen); STATIC void port_cfg_free(port_cfg_t *port); STATIC void or_options_free(or_options_t *options); +STATIC int options_validate_single_onion(or_options_t *options, + char **msg); STATIC int options_validate(or_options_t *old_options, or_options_t *options, or_options_t *default_options, diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index dc6b0930ca..8ad0f0c71d 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -27,6 +27,7 @@ #include "control.h" #include "dns.h" #include "dnsserv.h" +#include "directory.h" #include "dirserv.h" #include "hibernate.h" #include "main.h" @@ -2271,6 +2272,7 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) char payload[CELL_PAYLOAD_SIZE]; int payload_len; int begin_type; + const or_options_t *options = get_options(); origin_circuit_t *circ; edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(ap_conn); connection_t *base_conn = TO_CONN(edge_conn); @@ -2314,10 +2316,31 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) begin_type = ap_conn->use_begindir ? RELAY_COMMAND_BEGIN_DIR : RELAY_COMMAND_BEGIN; + + /* Check that circuits are anonymised, based on their type. */ if (begin_type == RELAY_COMMAND_BEGIN) { -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(circ->build_state->onehop_tunnel == 0); -#endif + /* This connection is a standard OR connection. + * Make sure its path length is anonymous, or that we're in a + * non-anonymous mode. */ + assert_circ_anonymity_ok(circ, options); + } else if (begin_type == RELAY_COMMAND_BEGIN_DIR) { + /* This connection is a begindir directory connection. + * Look at the linked directory connection to access the directory purpose. + * (This must be non-NULL, because we're doing begindir.) */ + tor_assert(base_conn->linked); + connection_t *linked_dir_conn_base = base_conn->linked_conn; + tor_assert(linked_dir_conn_base); + /* Sensitive directory connections must have an anonymous path length. + * Otherwise, directory connections are typically one-hop. + * This matches the earlier check for directory connection path anonymity + * in directory_initiate_command_rend(). */ + if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) { + assert_circ_anonymity_ok(circ, options); + } + } else { + /* This code was written for the two connection types BEGIN and BEGIN_DIR + */ + tor_assert_unreached(); } if (connection_edge_send_command(edge_conn, begin_type, diff --git a/src/or/directory.c b/src/or/directory.c index d37b5c2e0f..52b14b9bae 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -1085,7 +1085,7 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, * <b>dir_purpose</b> reveals sensitive information about a Tor * instance's client activities. (Such connections must be performed * through normal three-hop Tor circuits.) */ -static int +int is_sensitive_dir_purpose(uint8_t dir_purpose) { return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) || @@ -1140,12 +1140,10 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose)); -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(is_sensitive_dir_purpose(dir_purpose) && - !anonymized_connection)); -#else - (void)is_sensitive_dir_purpose; -#endif + if (is_sensitive_dir_purpose(dir_purpose)) { + tor_assert(anonymized_connection || + rend_non_anonymous_mode_enabled(options)); + } /* use encrypted begindir connections for everything except relays * this provides better protection for directory fetches */ diff --git a/src/or/directory.h b/src/or/directory.h index f04e7ab315..9477948aa0 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -132,7 +132,10 @@ 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); +/* Yes, these two functions are confusingly similar. + * Let's sort that out in #20077. */ int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose); +int is_sensitive_dir_purpose(uint8_t dir_purpose); #ifdef TOR_UNIT_TESTS /* Used only by directory.c and test_dir.c */ diff --git a/src/or/main.c b/src/or/main.c index 03c2b7ed58..76f9f38d3c 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -2833,11 +2833,6 @@ tor_init(int argc, char *argv[]) "Expect more bugs than usual."); } -#ifdef NON_ANONYMOUS_MODE_ENABLED - log_warn(LD_GENERAL, "This copy of Tor was compiled to run in a " - "non-anonymous mode. It will provide NO ANONYMITY."); -#endif - if (network_init()<0) { log_err(LD_BUG,"Error initializing network; exiting."); return -1; @@ -2849,6 +2844,14 @@ tor_init(int argc, char *argv[]) return -1; } + /* The options are now initialised */ + const or_options_t *options = get_options(); + + if (rend_non_anonymous_mode_enabled(options)) { + log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run " + "in a non-anonymous mode. It will provide NO ANONYMITY."); + } + #ifndef _WIN32 if (geteuid()==0) log_warn(LD_GENERAL,"You are running Tor as root. You don't need to, " diff --git a/src/or/or.h b/src/or/or.h index 574f184a13..dd3ab8a03a 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3701,6 +3701,27 @@ typedef struct { * they reach the normal circuit-build timeout. */ int CloseHSServiceRendCircuitsImmediatelyOnTimeout; + /** Onion Services in OnionServiceSingleHopMode make one-hop (direct) + * circuits between the onion service server, and the introduction and + * rendezvous points. (Onion service descriptors are still posted using + * 3-hop paths, to avoid onion service directories blocking the service.) + * This option makes every hidden service instance hosted by + * this tor instance a Single Onion Service. One-hop circuits make Single + * Onion servers easily locatable, but clients remain location-anonymous. + * OnionServiceSingleHopMode requires OnionServiceNonAnonymousMode to be set + * to 1. + * Use rend_service_allow_non_anonymous_connection() or + * rend_service_reveal_startup_time() instead of using this option directly. + */ + int OnionServiceSingleHopMode; + /* Makes hidden service clients and servers non-anonymous on this tor + * instance. Allows the non-anonymous OnionServiceSingleHopMode. Enables + * direct connections in the hidden service protocol. + * Use rend_service_non_anonymous_mode() instead of using this option + * directly. + */ + int OnionServiceNonAnonymousMode; + int ConnLimit; /**< Demanded minimum number of simultaneous connections. */ int ConnLimit_; /**< Maximum allowed number of simultaneous connections. */ int ConnLimit_high_thresh; /**< start trying to lower socket usage if we diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 3a742fec0a..9d16e3b716 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -134,6 +134,7 @@ int rend_client_send_introduction(origin_circuit_t *introcirc, origin_circuit_t *rendcirc) { + const or_options_t *options = get_options(); size_t payload_len; int r, v3_shift = 0; char payload[RELAY_PAYLOAD_SIZE]; @@ -150,10 +151,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, tor_assert(rendcirc->rend_data); tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address, rendcirc->rend_data->onion_address)); -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(introcirc->build_state->onehop_tunnel)); - tor_assert(!(rendcirc->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(introcirc, options); + assert_circ_anonymity_ok(rendcirc, options); r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1, &entry); @@ -387,6 +386,7 @@ int rend_client_introduction_acked(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { + const or_options_t *options = get_options(); origin_circuit_t *rendcirc; (void) request; // XXXX Use this. @@ -398,10 +398,9 @@ rend_client_introduction_acked(origin_circuit_t *circ, return -1; } + tor_assert(circ->build_state); tor_assert(circ->build_state->chosen_exit); -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(circ->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(circ, options); tor_assert(circ->rend_data); /* For path bias: This circuit was used successfully. Valid @@ -416,9 +415,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, log_info(LD_REND,"Received ack. Telling rend circ..."); rendcirc = circuit_get_ready_rend_circ_by_rend_data(circ->rend_data); if (rendcirc) { /* remember the ack */ -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(rendcirc->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(rendcirc, options); circuit_change_purpose(TO_CIRCUIT(rendcirc), CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); /* Set timestamp_dirty, because circuit_expire_building expects @@ -1552,3 +1549,35 @@ rend_parse_service_authorization(const or_options_t *options, return res; } +/* Can Tor client code make direct (non-anonymous) connections to introduction + * or rendezvous points? + * Returns true if tor was compiled with NON_ANONYMOUS_MODE_ENABLED, and is + * configured in Tor2web mode. */ +int +rend_client_allow_non_anonymous_connection(const or_options_t *options) +{ + /* Tor2web support needs to be compiled in to a tor binary. */ +#ifdef NON_ANONYMOUS_MODE_ENABLED + /* Tor2web */ + return options->Tor2webMode ? 1 : 0; +#else + (void)options; + return 0; +#endif +} + +/* At compile-time, was non-anonymous mode enabled via + * NON_ANONYMOUS_MODE_ENABLED ? */ +int +rend_client_non_anonymous_mode_enabled(const or_options_t *options) +{ + (void)options; + /* Tor2web support needs to be compiled in to a tor binary. */ +#ifdef NON_ANONYMOUS_MODE_ENABLED + /* Tor2web */ + return 1; +#else + return 0; +#endif +} + diff --git a/src/or/rendclient.h b/src/or/rendclient.h index e90dac07ab..b8f8c2f871 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -51,5 +51,8 @@ rend_service_authorization_t *rend_client_lookup_service_authorization( const char *onion_address); void rend_service_authorization_free_all(void); +int rend_client_allow_non_anonymous_connection(const or_options_t *options); +int rend_client_non_anonymous_mode_enabled(const or_options_t *options); + #endif diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 01b0766cf0..12dd0f44e5 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -1067,3 +1067,51 @@ rend_auth_decode_cookie(const char *cookie_in, uint8_t *cookie_out, return res; } +/* Is this a rend client or server that allows direct (non-anonymous) + * connections? + * Clients must be specifically compiled and configured in this mode. + * Onion services can be configured to start in this mode. + * Prefer rend_client_allow_non_anonymous_connection() or + * rend_service_allow_non_anonymous_connection() whenever possible, so that + * checks are specific to Single Onion Services or Tor2web. */ +int +rend_allow_non_anonymous_connection(const or_options_t* options) +{ + return (rend_client_allow_non_anonymous_connection(options) + || rend_service_allow_non_anonymous_connection(options)); +} + +/* Is this a rend client or server in non-anonymous mode? + * Clients must be specifically compiled in this mode. + * Onion services can be configured to start in this mode. + * Prefer rend_client_non_anonymous_mode_enabled() or + * rend_service_non_anonymous_mode_enabled() whenever possible, so that checks + * are specific to Single Onion Services or Tor2web. */ +int +rend_non_anonymous_mode_enabled(const or_options_t *options) +{ + return (rend_client_non_anonymous_mode_enabled(options) + || rend_service_non_anonymous_mode_enabled(options)); +} + +/* Make sure that tor only builds one-hop circuits when they would not + * compromise user anonymity. + * + * One-hop circuits are permitted in Tor2webMode or OnionServiceSingleHopMode. + * + * Tor2webMode and OnionServiceSingleHopMode are also allowed to make + * multi-hop circuits. For example, single onion HSDir circuits are 3-hop to + * prevent denial of service. + */ +void +assert_circ_anonymity_ok(origin_circuit_t *circ, + const or_options_t *options) +{ + tor_assert(options); + tor_assert(circ); + tor_assert(circ->build_state); + + if (circ->build_state->onehop_tunnel) { + tor_assert(rend_allow_non_anonymous_connection(options)); + } +} diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 88cf512f4a..090e6f25e0 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -77,5 +77,11 @@ int rend_auth_decode_cookie(const char *cookie_in, rend_auth_type_t *auth_type_out, char **err_msg_out); +int rend_allow_non_anonymous_connection(const or_options_t* options); +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); + #endif diff --git a/src/or/rendservice.c b/src/or/rendservice.c index fa147ba98e..0ba9205c6e 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -20,6 +20,7 @@ #include "main.h" #include "networkstatus.h" #include "nodelist.h" +#include "policies.h" #include "rendclient.h" #include "rendcommon.h" #include "rendservice.h" @@ -949,6 +950,208 @@ rend_service_update_descriptor(rend_service_t *service) } } +static const char *sos_poison_fname = "non_anonymous_hidden_service"; + +/* Allocate and return a string containing the path to the single onion + * poison file in service. + * The caller must free this path. + * Returns NULL if there is no directory for service. */ +static char * +sos_poison_path(const rend_service_t *service) +{ + char *poison_path; + + tor_assert(service->directory); + + tor_asprintf(&poison_path, "%s%s%s", + service->directory, PATH_SEPARATOR, sos_poison_fname); + + return poison_path; +} + +/** Return True if hidden services <b>service> has been poisoned by single + * onion mode. */ +static int +service_is_single_onion_poisoned(const rend_service_t *service) +{ + char *poison_fname = NULL; + file_status_t fstatus; + + if (!service->directory) { + return 0; + } + + poison_fname = sos_poison_path(service); + tor_assert(poison_fname); + + fstatus = file_status(poison_fname); + tor_free(poison_fname); + + /* If this fname is occupied, the hidden service has been poisoned. */ + if (fstatus == FN_FILE || fstatus == FN_EMPTY) { + return 1; + } + + return 0; +} + +/* Return 1 if the private key file for service exists and has a non-zero size, + * and 0 otherwise. */ +static int +rend_service_private_key_exists(const rend_service_t *service) +{ + char *private_key_path = rend_service_path(service, private_key_fname); + const file_status_t private_key_status = file_status(private_key_path); + tor_free(private_key_path); + /* Only non-empty regular private key files could have been used before. */ + return private_key_status == FN_FILE; +} + +/** Check the single onion service poison state of all existing hidden service + * directories: + * - If each service is poisoned, and we are in OnionServiceSingleHopMode, + * return 0, + * - If each service is not poisoned, and we are not in + * OnionServiceSingleHopMode, return 0, + * - Otherwise, the poison state is invalid, and a service that was created in + * one mode is being used in the other, return -1. + * Hidden service directories without keys are not checked for consistency. + * When their keys are created, they will be poisoned (if needed). + * If a <b>service_list</b> is provided, treat it + * as the list of hidden services (used in unittests). */ +int +rend_service_list_verify_single_onion_poison(const smartlist_t *service_list, + const or_options_t *options) +{ + const smartlist_t *s_list; + /* If no special service list is provided, then just use the global one. */ + if (!service_list) { + if (!rend_service_list) { /* No global HS list. Nothing to see here. */ + return 0; + } + + s_list = rend_service_list; + } else { + s_list = service_list; + } + + int consistent = 1; + SMARTLIST_FOREACH_BEGIN(s_list, const rend_service_t *, s) { + if (service_is_single_onion_poisoned(s) != + rend_service_allow_non_anonymous_connection(options) && + rend_service_private_key_exists(s)) { + consistent = 0; + } + } SMARTLIST_FOREACH_END(s); + + return consistent ? 0 : -1; +} + +/*** Helper for rend_service_poison_new_single_onion_dirs(). When in single + * onion mode, add a file to this hidden service directory that marks it as a + * single onion hidden service. Returns 0 when a directory is successfully + * poisoned, or if it is already poisoned. Returns -1 on a failure to read + * the directory or write the poison file, or if there is an existing private + * key file in the directory. (The service should have been poisoned when the + * key was created.) */ +static int +poison_new_single_onion_hidden_service_dir(const rend_service_t *service) +{ + /* We must only poison directories if we're in Single Onion mode */ + tor_assert(rend_service_allow_non_anonymous_connection(get_options())); + + int fd; + int retval = -1; + char *poison_fname = NULL; + + if (!service->directory) { + log_info(LD_REND, "Ephemeral HS started in OnionServiceSingleHopMode."); + return 0; + } + + /* Make sure we're only poisoning new hidden service directories */ + if (rend_service_private_key_exists(service)) { + log_warn(LD_BUG, "Tried to single onion poison a service directory after " + "the private key was created."); + return -1; + } + + poison_fname = sos_poison_path(service); + + switch (file_status(poison_fname)) { + case FN_DIR: + case FN_ERROR: + log_warn(LD_FS, "Can't read single onion poison file \"%s\"", + poison_fname); + goto done; + case FN_FILE: /* single onion poison file already exists. NOP. */ + case FN_EMPTY: /* single onion poison file already exists. NOP. */ + log_debug(LD_FS, "Tried to re-poison a single onion poisoned file \"%s\"", + poison_fname); + break; + case FN_NOENT: + fd = tor_open_cloexec(poison_fname, O_RDWR|O_CREAT|O_TRUNC, 0600); + if (fd < 0) { + log_warn(LD_FS, "Could not create single onion poison file %s", + poison_fname); + goto done; + } + close(fd); + break; + default: + tor_assert(0); + } + + retval = 0; + + done: + tor_free(poison_fname); + + return retval; +} + +/** We just got launched in OnionServiceSingleHopMode. That's a non-anoymous + * mode for hidden services; hence we should mark all new hidden service + * directories appropriately so that they are never launched as + * location-private hidden services again. (New directories don't have private + * key files.) + * If a <b>service_list</b> is provided, treat it as the list of hidden + * services (used in unittests). + * Return 0 on success, -1 on fail. */ +int +rend_service_poison_new_single_onion_dirs(const smartlist_t *service_list) +{ + /* We must only poison directories if we're in Single Onion mode */ + tor_assert(rend_service_allow_non_anonymous_connection(get_options())); + + const smartlist_t *s_list; + /* If no special service list is provided, then just use the global one. */ + if (!service_list) { + if (!rend_service_list) { /* No global HS list. Nothing to see here. */ + return 0; + } + + s_list = rend_service_list; + } else { + s_list = service_list; + } + + SMARTLIST_FOREACH_BEGIN(s_list, const rend_service_t *, s) { + if (!rend_service_private_key_exists(s)) { + if (poison_new_single_onion_hidden_service_dir(s) < 0) { + return -1; + } + } + } SMARTLIST_FOREACH_END(s); + + /* The keys for these services are linked to the server IP address */ + log_notice(LD_REND, "The configured onion service directories have been " + "used in single onion mode. They can not be used for anonymous " + "hidden services."); + + return 0; +} + /** Load and/or generate private keys for all hidden services, possibly * including keys for client authorization. Return 0 on success, -1 on * failure. */ @@ -1372,6 +1575,19 @@ rend_check_authorization(rend_service_t *service, return 1; } +/* Can this service make a direct connection to ei? + * It must be a single onion service, and the firewall rules must allow ei. */ +static int +rend_service_use_direct_connection(const or_options_t* options, + const extend_info_t* ei) +{ + /* The prefer_ipv6 argument to fascist_firewall_allows_address_addr is + * ignored, because pref_only is 0. */ + return (rend_service_allow_direct_connection(options) && + fascist_firewall_allows_address_addr(&ei->addr, ei->port, + FIREWALL_OR_CONNECTION, 0, 0)); +} + /****** * Handle cells ******/ @@ -1421,9 +1637,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, goto err; } -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(circuit->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(circuit, options); tor_assert(circuit->rend_data); /* We'll use this in a bazillion log messages */ @@ -1627,6 +1841,11 @@ rend_service_receive_introduction(origin_circuit_t *circuit, for (i=0;i<MAX_REND_FAILURES;i++) { int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; + /* A Single Onion Service only uses a direct connection if its + * firewall rules permit direct connections to the address. */ + if (rend_service_use_direct_connection(options, rp)) { + flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL; + } launched = circuit_launch_by_extend_info( CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags); @@ -1739,7 +1958,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro, goto err; } - rp = extend_info_from_node(node, 0); + /* Are we in single onion mode? */ + const int allow_direct = rend_service_allow_non_anonymous_connection( + get_options()); + rp = extend_info_from_node(node, allow_direct); if (!rp) { if (err_msg_out) { tor_asprintf(&err_msg, @@ -1764,6 +1986,10 @@ find_rp_for_intro(const rend_intro_cell_t *intro, goto err; } + /* rp is always set here: extend_info_dup guarantees a non-NULL result, and + * the other cases goto err. */ + tor_assert(rp); + /* Make sure the RP we are being asked to connect to is _not_ a private * address unless it's allowed. Let's avoid to build a circuit to our * second middle node and fail right after when extending to the RP. */ @@ -2538,6 +2764,10 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) log_info(LD_REND,"Reattempting rendezvous circuit to '%s'", safe_str(extend_info_describe(oldstate->chosen_exit))); + /* You'd think Single Onion Services would want to retry the rendezvous + * using a direct connection. But if it's blocked by a firewall, or the + * service is IPv6-only, or the rend point avoiding becoming a one-hop + * proxy, we need a 3-hop connection. */ newcirc = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, oldstate->chosen_exit, CIRCLAUNCH_NEED_CAPACITY|CIRCLAUNCH_IS_INTERNAL); @@ -2566,6 +2796,11 @@ rend_service_launch_establish_intro(rend_service_t *service, rend_intro_point_t *intro) { origin_circuit_t *launched; + int flags = CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL; + + if (rend_service_allow_direct_connection(get_options())) { + flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL; + } log_info(LD_REND, "Launching circuit to introduction point %s for service %s", @@ -2576,8 +2811,7 @@ rend_service_launch_establish_intro(rend_service_t *service, ++service->n_intro_circuits_launched; launched = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, - intro->extend_info, - CIRCLAUNCH_NEED_UPTIME|CIRCLAUNCH_IS_INTERNAL); + intro->extend_info, flags); if (!launched) { log_info(LD_REND, @@ -2651,9 +2885,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) int reason = END_CIRC_REASON_TORPROTOCOL; tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(circuit->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(circuit, get_options()); tor_assert(circuit->cpath); tor_assert(circuit->rend_data); @@ -2720,6 +2952,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) log_info(LD_REND, "Established circuit %u as introduction point for service %s", (unsigned)circuit->base_.n_circ_id, serviceid); + circuit_log_path(LOG_INFO, LD_REND, circuit); /* Use the intro key instead of the service key in ESTABLISH_INTRO. */ crypto_pk_t *intro_key = circuit->intro_key; @@ -2849,9 +3082,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); tor_assert(circuit->cpath); tor_assert(circuit->build_state); -#ifndef NON_ANONYMOUS_MODE_ENABLED - tor_assert(!(circuit->build_state->onehop_tunnel)); -#endif + assert_circ_anonymity_ok(circuit, get_options()); tor_assert(circuit->rend_data); /* Declare the circuit dirty to avoid reuse, and for path-bias */ @@ -2871,6 +3102,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) "Done building circuit %u to rendezvous with " "cookie %s for service %s", (unsigned)circuit->base_.n_circ_id, hexcookie, serviceid); + circuit_log_path(LOG_INFO, LD_REND, circuit); /* Clear the 'in-progress HS circ has timed out' flag for * consistency with what happens on the client side; this line has @@ -3547,7 +3779,8 @@ rend_consider_services_intro_points(void) * pick it again in the next iteration. */ smartlist_add(exclude_nodes, (void*)node); intro = tor_malloc_zero(sizeof(rend_intro_point_t)); - intro->extend_info = extend_info_from_node(node, 0); + intro->extend_info = extend_info_from_node(node, + rend_service_allow_direct_connection(options)); intro->intro_key = crypto_pk_new(); const int fail = crypto_pk_generate_key(intro->intro_key); tor_assert(!fail); @@ -3592,8 +3825,9 @@ rend_consider_services_upload(time_t now) { int i; rend_service_t *service; - int rendpostperiod = get_options()->RendPostPeriod; - int rendinitialpostdelay = (get_options()->TestingTorNetwork ? + const or_options_t *options = get_options(); + int rendpostperiod = options->RendPostPeriod; + int rendinitialpostdelay = (options->TestingTorNetwork ? MIN_REND_INITIAL_POST_DELAY_TESTING : MIN_REND_INITIAL_POST_DELAY); @@ -3604,6 +3838,12 @@ rend_consider_services_upload(time_t now) * the descriptor is stable before being published. See comment below. */ service->next_upload_time = now + rendinitialpostdelay + crypto_rand_int(2*rendpostperiod); + /* Single Onion Services prioritise availability over hiding their + * startup time, as their IP address is publicly discoverable anyway. + */ + if (rend_service_reveal_startup_time(options)) { + service->next_upload_time = now + rendinitialpostdelay; + } } /* Does every introduction points have been established? */ unsigned int intro_points_ready = @@ -3844,11 +4084,30 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return -2; } -/* Stub that should be replaced with the #17178 version of the function - * when merging. */ +/* Do the options allow onion services to make direct (non-anonymous) + * connections to introduction or rendezvous points? + * Returns true if tor is in OnionServiceSingleHopMode. */ int -rend_service_allow_direct_connection(const or_options_t *options) +rend_service_allow_non_anonymous_connection(const or_options_t *options) { - (void)options; - return 0; + return options->OnionServiceSingleHopMode ? 1 : 0; +} + +/* Do the options allow us to reveal the exact startup time of the onion + * service? + * Single Onion Services prioritise availability over hiding their + * startup time, as their IP address is publicly discoverable anyway. + * Returns true if tor is in OnionServiceSingleHopMode. */ +int +rend_service_reveal_startup_time(const or_options_t *options) +{ + return rend_service_allow_non_anonymous_connection(options); +} + +/* Is non-anonymous mode enabled using the OnionServiceNonAnonymousMode + * config option? */ +int +rend_service_non_anonymous_mode_enabled(const or_options_t *options) +{ + return options->OnionServiceNonAnonymousMode ? 1 : 0; } diff --git a/src/or/rendservice.h b/src/or/rendservice.h index d39d7bde5b..0cf448e207 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -164,6 +164,11 @@ void rend_service_port_config_free(rend_service_port_config_t *p); void rend_authorized_client_free(rend_authorized_client_t *client); +int rend_service_list_verify_single_onion_poison( + const smartlist_t *service_list, + const or_options_t *options); +int rend_service_poison_new_single_onion_dirs(const smartlist_t *service_list); + /** Return value from rend_service_add_ephemeral. */ typedef enum { RSAE_BADAUTH = -5, /**< Invalid auth_type/auth_clients */ @@ -187,7 +192,9 @@ void directory_post_to_hs_dir(rend_service_descriptor_t *renddesc, const char *service_id, int seconds_valid); void rend_service_desc_has_uploaded(const rend_data_t *rend_data); -int rend_service_allow_direct_connection(const or_options_t *options); +int rend_service_allow_non_anonymous_connection(const or_options_t *options); +int rend_service_reveal_startup_time(const or_options_t *options); +int rend_service_non_anonymous_mode_enabled(const or_options_t *options); #endif diff --git a/src/test/test_hs.c b/src/test/test_hs.c index 1daa1552e9..297fb0e97f 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -8,12 +8,14 @@ #define CONTROL_PRIVATE #define CIRCUITBUILD_PRIVATE +#define RENDSERVICE_PRIVATE #include "or.h" #include "test.h" #include "control.h" #include "config.h" #include "rendcommon.h" +#include "rendservice.h" #include "routerset.h" #include "circuitbuild.h" #include "test_helpers.h" @@ -496,6 +498,209 @@ test_hs_auth_cookies(void *arg) return; } +static int mock_get_options_calls = 0; +static or_options_t *mock_options = NULL; + +static void +reset_options(or_options_t *options, int *get_options_calls) +{ + memset(options, 0, sizeof(or_options_t)); + options->TestingTorNetwork = 1; + + *get_options_calls = 0; +} + +static const or_options_t * +mock_get_options(void) +{ + ++mock_get_options_calls; + tor_assert(mock_options); + return mock_options; +} + +/* Test that single onion poisoning works. */ +static void +test_single_onion_poisoning(void *arg) +{ + or_options_t opt; + mock_options = &opt; + reset_options(mock_options, &mock_get_options_calls); + MOCK(get_options, mock_get_options); + + int ret = -1; + mock_options->DataDirectory = tor_strdup(get_fname("test_data_dir")); + rend_service_t *service_1 = tor_malloc_zero(sizeof(rend_service_t)); + char *dir1 = tor_strdup(get_fname("test_hs_dir1")); + rend_service_t *service_2 = tor_malloc_zero(sizeof(rend_service_t)); + char *dir2 = tor_strdup(get_fname("test_hs_dir2")); + smartlist_t *services = smartlist_new(); + + (void) arg; + + /* No services, no problem! */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Either way, no problem. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Create directories for both services */ + +#ifdef _WIN32 + ret = mkdir(mock_options->DataDirectory); + tt_assert(ret == 0); + ret = mkdir(dir1); + tt_assert(ret == 0); + ret = mkdir(dir2); +#else + ret = mkdir(mock_options->DataDirectory, 0700); + tt_assert(ret == 0); + ret = mkdir(dir1, 0700); + tt_assert(ret == 0); + ret = mkdir(dir2, 0700); +#endif + tt_assert(ret == 0); + + service_1->directory = dir1; + service_2->directory = dir2; + smartlist_add(services, service_1); + /* But don't add the second service yet. */ + + /* Service directories, but no previous keys, no problem! */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Either way, no problem. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Poison! Poison! Poison! + * This can only be done in OnionServiceSingleHopMode. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_poison_new_single_onion_dirs(services); + tt_assert(ret == 0); + /* Poisoning twice is a no-op. */ + ret = rend_service_poison_new_single_onion_dirs(services); + tt_assert(ret == 0); + + /* Poisoned service directories, but no previous keys, no problem! */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Either way, no problem. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Now add some keys, and we'll have a problem. */ + ret = rend_service_load_all_keys(services); + tt_assert(ret == 0); + + /* Poisoned service directories with previous keys are not allowed. */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret < 0); + + /* But they are allowed if we're in non-anonymous mode. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Re-poisoning directories with existing keys is a no-op, because + * directories with existing keys are ignored. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_poison_new_single_onion_dirs(services); + tt_assert(ret == 0); + /* And it keeps the poison. */ + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Now add the second service: it has no key and no poison file */ + smartlist_add(services, service_2); + + /* A new service, and an existing poisoned service. Not ok. */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret < 0); + + /* But ok to add in non-anonymous mode. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Now remove the poisoning from the first service, and we have the opposite + * problem. */ + char *poison_path = rend_service_sos_poison_path(service_1); + ret = unlink(poison_path); + tor_free(poison_path); + tt_assert(ret == 0); + + /* Unpoisoned service directories with previous keys are ok, as are empty + * directories. */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* But the existing unpoisoned key is not ok in non-anonymous mode, even if + * there is an empty service. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret < 0); + + /* Poisoning directories with existing keys is a no-op, because directories + * with existing keys are ignored. But the new directory should poison. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_poison_new_single_onion_dirs(services); + tt_assert(ret == 0); + /* And the old directory remains unpoisoned. */ + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret < 0); + + /* And the new directory should be ignored, because it has no key. */ + mock_options->OnionServiceSingleHopMode = 0; + mock_options->OnionServiceNonAnonymousMode = 0; + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret == 0); + + /* Re-poisoning directories without existing keys is a no-op. */ + mock_options->OnionServiceSingleHopMode = 1; + mock_options->OnionServiceNonAnonymousMode = 1; + ret = rend_service_poison_new_single_onion_dirs(services); + tt_assert(ret == 0); + /* And the old directory remains unpoisoned. */ + ret = rend_service_list_verify_single_onion_poison(services, mock_options); + tt_assert(ret < 0); + + done: + /* TODO: should we delete the directories here? */ + rend_service_free(service_1); + rend_service_free(service_2); + smartlist_free(services); + UNMOCK(get_options); + tor_free(mock_options->DataDirectory); +} + struct testcase_t hs_tests[] = { { "hs_rend_data", test_hs_rend_data, TT_FORK, NULL, NULL }, @@ -508,6 +713,8 @@ struct testcase_t hs_tests[] = { NULL, NULL }, { "hs_auth_cookies", test_hs_auth_cookies, TT_FORK, NULL, NULL }, + { "single_onion_poisoning", test_single_onion_poisoning, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_options.c b/src/test/test_options.c index 87f896607a..cd1821a77e 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -2758,6 +2758,154 @@ test_options_validate__rend(void *ignored) } static void +test_options_validate__single_onion(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + /* Test that OnionServiceSingleHopMode must come with + * OnionServiceNonAnonymousMode */ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 0\n" + "OnionServiceSingleHopMode 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "OnionServiceSingleHopMode does not provide any " + "server anonymity. It must be used with " + "OnionServiceNonAnonymousMode set to 1."); + tor_free(msg); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 0\n" + "OnionServiceSingleHopMode 1\n" + "OnionServiceNonAnonymousMode 0\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "OnionServiceSingleHopMode does not provide any " + "server anonymity. It must be used with " + "OnionServiceNonAnonymousMode set to 1."); + tor_free(msg); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 0\n" + "OnionServiceSingleHopMode 1\n" + "OnionServiceNonAnonymousMode 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + free_options_test_data(tdata); + + /* Test that SOCKSPort must come with Tor2webMode if + * OnionServiceSingleHopMode is 1 */ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 5000\n" + "OnionServiceSingleHopMode 1\n" + "OnionServiceNonAnonymousMode 1\n" + "Tor2webMode 0\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "OnionServiceSingleHopMode is incompatible with using " + "Tor as an anonymous client. Please set Socks/Trans/NATD/DNSPort " + "to 0, or OnionServiceSingleHopMode to 0, or use the " + "non-anonymous Tor2webMode."); + tor_free(msg); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 0\n" + "OnionServiceSingleHopMode 1\n" + "OnionServiceNonAnonymousMode 1\n" + "Tor2webMode 0\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 5000\n" + "OnionServiceSingleHopMode 0\n" + "Tor2webMode 0\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "SOCKSPort 5000\n" + "OnionServiceSingleHopMode 1\n" + "OnionServiceNonAnonymousMode 1\n" + "Tor2webMode 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + free_options_test_data(tdata); + + /* Test that a hidden service can't be run with Tor2web + * Use OnionServiceNonAnonymousMode instead of Tor2webMode, because + * Tor2webMode requires a compilation #define */ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "OnionServiceNonAnonymousMode 1\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "OnionServiceNonAnonymousMode does not provide any " + "server anonymity. It must be used with OnionServiceSingleHopMode " + "set to 1."); + tor_free(msg); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "OnionServiceNonAnonymousMode 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "OnionServiceNonAnonymousMode does not provide any " + "server anonymity. It must be used with OnionServiceSingleHopMode " + "set to 1."); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + free_options_test_data(tdata); + + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "OnionServiceNonAnonymousMode 1\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + "OnionServiceSingleHopMode 1\n" + "SOCKSPort 0\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_ptr_op(msg, OP_EQ, NULL); + + done: + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void test_options_validate__accounting(void *ignored) { (void)ignored; @@ -4379,6 +4527,7 @@ struct testcase_t options_tests[] = { LOCAL_VALIDATE_TEST(port_forwarding), LOCAL_VALIDATE_TEST(tor2web), LOCAL_VALIDATE_TEST(rend), + LOCAL_VALIDATE_TEST(single_onion), LOCAL_VALIDATE_TEST(accounting), LOCAL_VALIDATE_TEST(proxy), LOCAL_VALIDATE_TEST(control), |