/* Copyright (c) 2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file test_hs_ob.c * \brief Test hidden service onion balance functionality. */ #define CONFIG_PRIVATE #define HS_SERVICE_PRIVATE #define HS_OB_PRIVATE #include "test/test.h" #include "test/test_helpers.h" #include "test/log_test_helpers.h" #include "app/config/config.h" #include "feature/hs/hs_config.h" #include "feature/hs/hs_ob.h" #include "feature/hs/hs_service.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/networkstatus_st.h" static ed25519_keypair_t onion_addr_kp_1; static char onion_addr_1[HS_SERVICE_ADDR_LEN_BASE32 + 1]; static ed25519_keypair_t onion_addr_kp_2; static char onion_addr_2[HS_SERVICE_ADDR_LEN_BASE32 + 1]; static bool config_is_good = true; static int helper_tor_config(const char *conf) { int ret = -1; or_options_t *options = helper_parse_options(conf); tt_assert(options); ret = hs_config_service_all(options, 0); done: or_options_free(options); return ret; } static networkstatus_t mock_ns; static networkstatus_t * mock_networkstatus_get_live_consensus(time_t now) { (void) now; return &mock_ns; } static char * mock_read_file_to_str(const char *filename, int flags, struct stat *stat_out) { char *ret = NULL; (void) flags; (void) stat_out; if (!strcmp(filename, get_fname("hs3" PATH_SEPARATOR "ob_config"))) { if (config_is_good) { tor_asprintf(&ret, "MasterOnionAddress %s.onion\n" "MasterOnionAddress %s.onion\n", onion_addr_1, onion_addr_2); } else { tor_asprintf(&ret, "MasterOnionAddress JUNKJUNKJUNK.onion\n" "UnknownOption BLAH\n"); } goto done; } done: return ret; } static void test_parse_config_file(void *arg) { int ret; char *conf = NULL; const ed25519_public_key_t *pkey; (void) arg; hs_init(); MOCK(read_file_to_str, mock_read_file_to_str); #define fmt_conf \ "HiddenServiceDir %s\n" \ "HiddenServicePort 22\n" \ "HiddenServiceOnionBalanceInstance 1\n" tor_asprintf(&conf, fmt_conf, get_fname("hs3")); #undef fmt_conf /* Build the OB frontend onion addresses. */ ed25519_keypair_generate(&onion_addr_kp_1, 0); hs_build_address(&onion_addr_kp_1.pubkey, HS_VERSION_THREE, onion_addr_1); ed25519_keypair_generate(&onion_addr_kp_2, 0); hs_build_address(&onion_addr_kp_2.pubkey, HS_VERSION_THREE, onion_addr_2); ret = helper_tor_config(conf); tor_free(conf); tt_int_op(ret, OP_EQ, 0); /* Load the keys for the service. After that, the v3 service should be * registered in the global map and we'll be able to access it. */ tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); hs_service_load_all_keys(); tt_int_op(get_hs_service_map_size(), OP_EQ, 1); const hs_service_t *s = get_first_service(); tt_assert(s); tt_assert(s->config.ob_master_pubkeys); tt_assert(hs_ob_service_is_instance(s)); tt_assert(smartlist_len(s->config.ob_master_pubkeys) == 2); /* Test the public keys we've added. */ pkey = smartlist_get(s->config.ob_master_pubkeys, 0); tt_mem_op(&onion_addr_kp_1.pubkey, OP_EQ, pkey, ED25519_PUBKEY_LEN); pkey = smartlist_get(s->config.ob_master_pubkeys, 1); tt_mem_op(&onion_addr_kp_2.pubkey, OP_EQ, pkey, ED25519_PUBKEY_LEN); done: hs_free_all(); UNMOCK(read_file_to_str); } static void test_parse_config_file_bad(void *arg) { int ret; char *conf = NULL; (void) arg; hs_init(); MOCK(read_file_to_str, mock_read_file_to_str); /* Indicate mock_read_file_to_str() to use the bad config. */ config_is_good = false; #define fmt_conf \ "HiddenServiceDir %s\n" \ "HiddenServicePort 22\n" \ "HiddenServiceOnionBalanceInstance 1\n" tor_asprintf(&conf, fmt_conf, get_fname("hs3")); #undef fmt_conf setup_full_capture_of_logs(LOG_INFO); ret = helper_tor_config(conf); tor_free(conf); tt_int_op(ret, OP_EQ, -1); expect_log_msg_containing("OnionBalance: MasterOnionAddress " "JUNKJUNKJUNK.onion is invalid"); expect_log_msg_containing("Found unrecognized option \'UnknownOption\'; " "saving it."); teardown_capture_of_logs(); done: hs_free_all(); UNMOCK(read_file_to_str); } static void test_get_subcredentials(void *arg) { int ret; hs_service_t *service = NULL; hs_service_config_t config; hs_subcredential_t *subcreds = NULL; (void) arg; MOCK(networkstatus_get_live_consensus, mock_networkstatus_get_live_consensus); /* Setup consensus with proper time so we can compute the time period. */ ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", &mock_ns.valid_after); tt_int_op(ret, OP_EQ, 0); ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", &mock_ns.fresh_until); tt_int_op(ret, OP_EQ, 0); config.ob_master_pubkeys = smartlist_new(); tt_assert(config.ob_master_pubkeys); /* Set up an instance */ service = tor_malloc_zero(sizeof(hs_service_t)); service->config = config; /* Setup the service descriptors */ service->desc_current = service_descriptor_new(); service->desc_next = service_descriptor_new(); /* First try to compute subcredentials but with no OB keys. Make sure that * subcreds get NULLed. To do this check we first poison subcreds. */ subcreds = (void*)999; tt_ptr_op(subcreds, OP_NE, NULL); size_t num = compute_subcredentials(service, &subcreds); tt_ptr_op(subcreds, OP_EQ, NULL); /* Generate a keypair to add to the OB keys list. */ ed25519_keypair_generate(&onion_addr_kp_1, 0); smartlist_add(config.ob_master_pubkeys, &onion_addr_kp_1.pubkey); /* Set up the instance subcredentials */ char current_subcred[SUBCRED_LEN]; char next_subcred[SUBCRED_LEN]; memset(current_subcred, 'C', SUBCRED_LEN); memset(next_subcred, 'N', SUBCRED_LEN); memcpy(service->desc_current->desc->subcredential.subcred, current_subcred, SUBCRED_LEN); memcpy(service->desc_next->desc->subcredential.subcred, next_subcred, SUBCRED_LEN); /* See that subcreds are computed properly */ num = compute_subcredentials(service, &subcreds); /* 5 subcredentials: 3 for the frontend, 2 for the instance */ tt_uint_op(num, OP_EQ, 5); tt_ptr_op(subcreds, OP_NE, NULL); /* Validate the subcredentials we just got. We'll build them oursevles with * the right time period steps and compare. */ const uint64_t tp = hs_get_time_period_num(0); const int steps[3] = {0, -1, 1}; unsigned int i; for (i = 0; i < 3; i++) { hs_subcredential_t subcredential; ed25519_public_key_t blinded_pubkey; hs_build_blinded_pubkey(&onion_addr_kp_1.pubkey, NULL, 0, tp + steps[i], &blinded_pubkey); hs_get_subcredential(&onion_addr_kp_1.pubkey, &blinded_pubkey, &subcredential); tt_mem_op(subcreds[i].subcred, OP_EQ, subcredential.subcred, SUBCRED_LEN); } tt_mem_op(subcreds[i++].subcred, OP_EQ, current_subcred, SUBCRED_LEN); tt_mem_op(subcreds[i++].subcred, OP_EQ, next_subcred, SUBCRED_LEN); done: tor_free(subcreds); smartlist_free(config.ob_master_pubkeys); if (service) { memset(&service->config, 0, sizeof(hs_service_config_t)); hs_service_free(service); } UNMOCK(networkstatus_get_live_consensus); } struct testcase_t hs_ob_tests[] = { { "parse_config_file", test_parse_config_file, TT_FORK, NULL, NULL }, { "parse_config_file_bad", test_parse_config_file_bad, TT_FORK, NULL, NULL }, { "get_subcredentials", test_get_subcredentials, TT_FORK, NULL, NULL }, END_OF_TESTCASES };