diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index 7c545c35d5..18c79e0c47 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -1428,7 +1428,7 @@ client_service_authorization_free_all(void) /* Check if the auth key file name is valid or not. Return 1 if valid, * otherwise return 0. */ -static int +STATIC int auth_key_filename_is_valid(const char *filename) { int ret = 1; @@ -1448,7 +1448,7 @@ auth_key_filename_is_valid(const char *filename) return ret; } -static hs_client_service_authorization_t * +STATIC hs_client_service_authorization_t * parse_auth_file_content(const char *client_key_str) { char *onion_address = NULL; @@ -1836,3 +1836,13 @@ hs_client_dir_info_changed(void) * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */ retry_all_socks_conn_waiting_for_desc(); } + +#ifdef TOR_UNIT_TESTS + +STATIC digest256map_t * +get_hs_client_auths_map(void) +{ + return client_auths; +} + +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 6d4c847742..1ba0338dc3 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -84,6 +84,11 @@ void hs_client_free_all(void); #ifdef HS_CLIENT_PRIVATE +STATIC int auth_key_filename_is_valid(const char *filename); + +STATIC hs_client_service_authorization_t * +parse_auth_file_content(const char *client_key_str); + STATIC routerstatus_t * pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); @@ -99,6 +104,12 @@ STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, MOCK_DECL(STATIC hs_client_fetch_status_t, fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)); +#ifdef TOR_UNIT_TESTS + +STATIC digest256map_t *get_hs_client_auths_map(void); + +#endif /* defined(TOR_UNIT_TESTS) */ + #endif /* defined(HS_CLIENT_PRIVATE) */ #endif /* !defined(TOR_HS_CLIENT_H) */ diff --git a/src/test/test_hs_client.c b/src/test/test_hs_client.c index e03c80098b..eacbd328e5 100644 --- a/src/test/test_hs_client.c +++ b/src/test/test_hs_client.c @@ -6,6 +6,7 @@ * \brief Test prop224 HS client functionality. */ +#define CONFIG_PRIVATE #define CRYPTO_PRIVATE #define MAIN_PRIVATE #define HS_CLIENT_PRIVATE @@ -32,6 +33,7 @@ #include "feature/hs/hs_circuit.h" #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_client.h" +#include "feature/hs/hs_config.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_cache.h" #include "core/or/circuitlist.h" @@ -73,6 +75,20 @@ mock_networkstatus_get_live_consensus(time_t now) return &mock_ns; } +static int +helper_config_client(const char *conf, int validate_only) +{ + int ret = 0; + or_options_t *options = NULL; + tt_assert(conf); + options = helper_parse_options(conf); + tt_assert(options); + ret = hs_config_client_auth_all(options, validate_only); + done: + or_options_free(options); + return ret; +} + /* Test helper function: Setup a circuit and a stream with the same hidden * service destination, and put them in circ_out and * conn_out. Make the stream wait for circuits to be established to the @@ -601,6 +617,158 @@ test_descriptor_fetch(void *arg) hs_free_all(); } +static void +test_auth_key_filename_is_valid(void *arg) +{ + (void) arg; + + /* Valid file name. */ + tt_assert(auth_key_filename_is_valid("a.auth_private")); + /* Valid file name with special character. */ + tt_assert(auth_key_filename_is_valid("a-.auth_private")); + /* Invalid extension. */ + tt_assert(!auth_key_filename_is_valid("a.ath_private")); + /* Nothing before the extension. */ + tt_assert(!auth_key_filename_is_valid(".auth_private")); + + done: + ; +} + +static void +test_parse_auth_file_content(void *arg) +{ + hs_client_service_authorization_t *auth = NULL; + + (void) arg; + + /* Valid authorized client. */ + auth = parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"); + tt_assert(auth); + + /* Wrong number of fields. */ + tt_assert(!parse_auth_file_content("a:b")); + /* Wrong auth type. */ + tt_assert(!parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:x:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq")); + /* Wrong key type. */ + tt_assert(!parse_auth_file_content( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq")); + /* Some malformed string. */ + tt_assert(!parse_auth_file_content("xx:descriptor:x25519:aa==")); + + done: + tor_free(auth); +} + +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("auth_keys" PATH_SEPARATOR + "client1.auth_private"))) { + ret = tor_strdup( + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad:descriptor:" + "x25519:zdsyvn2jq534ugyiuzgjy4267jbtzcjbsgedhshzx5mforyxtryq"); + goto done; + } + + if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR "dummy.xxx"))) { + ret = tor_strdup( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:descriptor:" + "x25519:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + goto done; + } + + if (!strcmp(filename, get_fname("auth_keys" PATH_SEPARATOR + "client2.auth_private"))) { + ret = tor_strdup( + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid:descriptor:" + "x25519:fdreqzjqso7d2ac7qscrxfl5qfpamdvgy5d6cxejcgzc3hvhurmq"); + goto done; + } + + done: + return ret; +} + +static int +mock_check_private_dir(const char *dirname, cpd_check_t check, + const char *effective_user) +{ + (void) dirname; + (void) check; + (void) effective_user; + + return 0; +} + +static smartlist_t * +mock_tor_listdir(const char *dirname) +{ + smartlist_t *file_list = smartlist_new(); + + (void) dirname; + + smartlist_add(file_list, tor_strdup("client1.auth_private")); + smartlist_add(file_list, tor_strdup("dummy.xxx")); + smartlist_add(file_list, tor_strdup("client2.auth_private")); + + return file_list; +} + +static void +test_config_client_authorization(void *arg) +{ + int ret; + char *conf = NULL; + ed25519_public_key_t pk1, pk2; + digest256map_t *global_map = NULL; + char *key_dir = tor_strdup(get_fname("auth_keys")); + + (void) arg; + + MOCK(read_file_to_str, mock_read_file_to_str); + MOCK(tor_listdir, mock_tor_listdir); + MOCK(check_private_dir, mock_check_private_dir); + +#define conf_fmt \ + "ClientOnionAuthDir %s\n" + + tor_asprintf(&conf, conf_fmt, key_dir); + ret = helper_config_client(conf, 0); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + +#undef conf_fmt + + global_map = get_hs_client_auths_map(); + tt_int_op(digest256map_size(global_map), OP_EQ, 2); + + hs_parse_address("4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad", + &pk1, NULL, NULL); + hs_parse_address("25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid", + &pk2, NULL, NULL); + + tt_assert(digest256map_get(global_map, pk1.pubkey)); + tt_assert(digest256map_get(global_map, pk2.pubkey)); + + done: + tor_free(key_dir); + hs_free_all(); + UNMOCK(read_file_to_str); + UNMOCK(tor_listdir); + UNMOCK(check_private_dir); +} + struct testcase_t hs_client_tests[] = { { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy, TT_FORK, NULL, NULL }, @@ -610,5 +778,11 @@ struct testcase_t hs_client_tests[] = { TT_FORK, NULL, NULL }, { "descriptor_fetch", test_descriptor_fetch, TT_FORK, NULL, NULL }, + { "auth_key_filename_is_valid", test_auth_key_filename_is_valid, TT_FORK, + NULL, NULL }, + { "parse_auth_file_content", test_parse_auth_file_content, TT_FORK, + NULL, NULL }, + { "config_client_authorization", test_config_client_authorization, + TT_FORK, NULL, NULL }, END_OF_TESTCASES };