summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorteor (Tim Wilson-Brown) <teor2345@gmail.com>2016-07-14 14:04:02 +1000
committerNick Mathewson <nickm@torproject.org>2016-09-13 10:10:54 -0400
commitb560f852f220f5630f6bf5a300d15b40c9c235cf (patch)
tree8f245dab0172516b01f5617adb0c856ed991a0f2 /src/test
parentb494ccc3c91423c4280c1fc003b5117d9aae54c0 (diff)
downloadtor-b560f852f220f5630f6bf5a300d15b40c9c235cf.tar.gz
tor-b560f852f220f5630f6bf5a300d15b40c9c235cf.zip
Implement Prop #260: Single 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. squash! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Implement Prop #260: Single Onion Services Redesign single onion service poisoning. When in OnionServiceSingleHopMode, each hidden service key is poisoned (marked as non-anonymous) on creation by creating a poison file in the hidden service directory. Existing keys are considered non-anonymous if this file exists, and anonymous if it does not. Tor refuses to launch in OnionServiceSingleHopMode if any existing keys are anonymous. Similarly, it refuses to launch in anonymous client mode if any existing keys are non-anonymous. Rewrite the unit tests to match and be more comprehensive. Adds a bonus unit test for rend_service_load_all_keys().
Diffstat (limited to 'src/test')
-rw-r--r--src/test/test_hs.c207
-rw-r--r--src/test/test_options.c149
2 files changed, 356 insertions, 0 deletions
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),