/* 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; intptr_t create_dir_mask = (intptr_t)arg; /* Get directories with a random suffix so we can repeat the tests */ mock_options->DataDirectory = tor_strdup(get_fname_rnd("test_data_dir")); rend_service_t *service_1 = tor_malloc_zero(sizeof(rend_service_t)); char *dir1 = tor_strdup(get_fname_rnd("test_hs_dir1")); rend_service_t *service_2 = tor_malloc_zero(sizeof(rend_service_t)); char *dir2 = tor_strdup(get_fname_rnd("test_hs_dir2")); smartlist_t *services = smartlist_new(); char *poison_path = NULL; char *err_msg = NULL; mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; /* Create the data directory, and, if the correct bit in arg is set, * create a directory for that service. * The data directory is required for the lockfile, which is used when * loading keys. */ ret = check_private_dir(mock_options->DataDirectory, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); if (create_dir_mask & CREATE_HS_DIR1) { ret = check_private_dir(dir1, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); } if (create_dir_mask & CREATE_HS_DIR2) { ret = check_private_dir(dir2, CPD_CREATE, NULL); tt_int_op(ret, OP_EQ, 0); } service_1->directory = dir1; service_2->directory = dir2; /* The services own the directory pointers now */ dir1 = dir2 = NULL; /* Add port to service 1 */ service_1->ports = smartlist_new(); service_2->ports = smartlist_new(); rend_service_port_config_t *port1 = rend_service_parse_port_config("80", " ", &err_msg); tt_assert(port1); tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_1->ports, port1); rend_service_port_config_t *port2 = rend_service_parse_port_config("90", " ", &err_msg); /* Add port to service 2 */ tt_assert(port2); tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_2->ports, port2); /* No services, a service to verify, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Add the first service */ 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! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poison! Poison! Poison! * This can only be done in HiddenServiceSingleHopMode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poisoning twice is a no-op. */ ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now add some keys, and we'll have a problem. */ ret = rend_service_load_all_keys(services); tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories with previous keys are not allowed. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But they are allowed if we're in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories with existing keys is a no-op, because * directories with existing keys are ignored. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); /* And it keeps the poison. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now add the second service: it has no key and no poison file */ 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; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But ok to add in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Now remove the poisoning from the first service, and we have the opposite * problem. */ poison_path = rend_service_sos_poison_path(service_1); tt_assert(poison_path); ret = unlink(poison_path); tt_int_op(ret, OP_EQ, 0); /* Unpoisoned service directories with previous keys are ok, as are empty * directories. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* But the existing unpoisoned key is not ok in non-anonymous mode, even if * there is an empty service. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the new directory should be ignored, because it has no key. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories without existing keys is a no-op. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); tt_int_op(ret, OP_EQ, 0); done: /* The test harness deletes the directories at exit */ tor_free(poison_path); tor_free(dir1); tor_free(dir2); smartlist_free(services); rend_service_free(service_1); rend_service_free(service_2); UNMOCK(get_options); tor_free(mock_options->DataDirectory); tor_free(err_msg); }
/* 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_list_verify_single_onion_poison(services, mock_options); tt_assert(ret == 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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; dir1 = dir2 = NULL; smartlist_add(services, service_1); /* But don't add the second service yet. */ /* Service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_list_verify_single_onion_poison(services, mock_options); tt_assert(ret == 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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 HiddenServiceSingleHopMode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_list_verify_single_onion_poison(services, mock_options); tt_assert(ret == 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 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->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 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); tor_free(dir1); tor_free(dir2); }