/* Copyright (c) 2017-2021, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file test_hs_control.c * \brief Unit tests for hidden service control port event and command. **/ #define CONTROL_EVENTS_PRIVATE #define CONTROL_CMD_PRIVATE #define HS_CLIENT_PRIVATE #define HS_SERVICE_PRIVATE #include "core/or/or.h" #include "test/test.h" #include "test/test_helpers.h" #include "core/mainloop/connection.h" #include "feature/control/control.h" #include "feature/control/control_cmd.h" #include "feature/control/control_events.h" #include "feature/control/control_fmt.h" #include "feature/control/control_connection_st.h" #include "app/config/config.h" #include "feature/hs/hs_common.h" #include "feature/hs/hs_client.h" #include "feature/hs/hs_control.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerstatus_st.h" #include "lib/container/smartlist.h" #include "lib/crypt_ops/crypto_format.h" #ifdef HAVE_SYS_STAT_H #include #endif #ifdef _WIN32 /* For mkdir() */ #include #else #include #endif /* defined(_WIN32) */ /* mock ID digest and longname for node that's in nodelist */ #define HSDIR_EXIST_ID \ "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" \ "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" #define STR_HSDIR_EXIST_LONGNAME \ "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=TestDir" #define STR_HSDIR_NONE_EXIST_LONGNAME \ "$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" /* Helper global variable for hidden service descriptor event test. * It's used as a pointer to dynamically created message buffer in * send_control_event_string_replacement function, which mocks * send_control_event_string function. * * Always free it after use! */ static char *received_msg = NULL; /** Mock function for send_control_event_string */ static void queue_control_event_string_replacement(uint16_t event, char *msg) { (void) event; tor_free(received_msg); received_msg = msg; } /** Mock function for node_describe_longname_by_id, it returns either * STR_HSDIR_EXIST_LONGNAME or STR_HSDIR_NONE_EXIST_LONGNAME */ static const char * node_describe_longname_by_id_replacement(const char *id_digest) { if (!strcmp(id_digest, HSDIR_EXIST_ID)) { return STR_HSDIR_EXIST_LONGNAME; } else { return STR_HSDIR_NONE_EXIST_LONGNAME; } } /* HSDir fetch index is a series of 'D' */ #define HSDIR_INDEX_FETCH_HEX \ "4343434343434343434343434343434343434343434343434343434343434343" #define HSDIR_INDEX_STORE_HEX \ "4444444444444444444444444444444444444444444444444444444444444444" static const node_t * mock_node_get_by_id(const char *digest) { static node_t node; memcpy(node.identity, digest, DIGEST_LEN); memset(node.hsdir_index.fetch, 'C', DIGEST256_LEN); memset(node.hsdir_index.store_first, 'D', DIGEST256_LEN); return &node; } static void test_hs_desc_event(void *arg) { int ret; char *expected_msg = NULL; char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; ed25519_keypair_t identity_kp; ed25519_public_key_t blinded_pk; char base64_blinded_pk[ED25519_BASE64_LEN + 1]; routerstatus_t hsdir_rs; hs_ident_dir_conn_t ident; (void) arg; MOCK(queue_control_event_string, queue_control_event_string_replacement); MOCK(node_describe_longname_by_id, node_describe_longname_by_id_replacement); MOCK(node_get_by_id, mock_node_get_by_id); /* Setup what we need for this test. */ ed25519_keypair_generate(&identity_kp, 0); hs_build_address(&identity_kp.pubkey, HS_VERSION_THREE, onion_address); ret = hs_address_is_valid(onion_address); tt_int_op(ret, OP_EQ, 1); memset(&blinded_pk, 'B', sizeof(blinded_pk)); memset(&hsdir_rs, 0, sizeof(hsdir_rs)); memcpy(hsdir_rs.identity_digest, HSDIR_EXIST_ID, DIGEST_LEN); ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); memcpy(&ident.identity_pk, &identity_kp.pubkey, sizeof(ed25519_public_key_t)); memcpy(&ident.blinded_pk, &blinded_pk, sizeof(blinded_pk)); /* HS_DESC REQUESTED ... */ hs_control_desc_event_requested(&identity_kp.pubkey, base64_blinded_pk, &hsdir_rs); tor_asprintf(&expected_msg, "650 HS_DESC REQUESTED %s NO_AUTH " STR_HSDIR_EXIST_LONGNAME " %s HSDIR_INDEX=" HSDIR_INDEX_FETCH_HEX "\r\n", onion_address, base64_blinded_pk); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); /* HS_DESC CREATED... */ hs_control_desc_event_created(onion_address, &blinded_pk); tor_asprintf(&expected_msg, "650 HS_DESC CREATED %s UNKNOWN " "UNKNOWN %s\r\n", onion_address, base64_blinded_pk); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); /* HS_DESC UPLOAD... */ uint8_t hsdir_index_store[DIGEST256_LEN]; memset(hsdir_index_store, 'D', sizeof(hsdir_index_store)); hs_control_desc_event_upload(onion_address, HSDIR_EXIST_ID, &blinded_pk, hsdir_index_store); tor_asprintf(&expected_msg, "650 HS_DESC UPLOAD %s UNKNOWN " STR_HSDIR_EXIST_LONGNAME " %s " "HSDIR_INDEX=" HSDIR_INDEX_STORE_HEX "\r\n", onion_address, base64_blinded_pk); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); /* HS_DESC FAILED... */ hs_control_desc_event_failed(&ident, HSDIR_EXIST_ID, "BAD_DESC"); tor_asprintf(&expected_msg, "650 HS_DESC FAILED %s NO_AUTH " STR_HSDIR_EXIST_LONGNAME " %s " "REASON=BAD_DESC\r\n", onion_address, base64_blinded_pk); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); /* HS_DESC RECEIVED... */ hs_control_desc_event_received(&ident, HSDIR_EXIST_ID); tor_asprintf(&expected_msg, "650 HS_DESC RECEIVED %s NO_AUTH " STR_HSDIR_EXIST_LONGNAME " %s\r\n", onion_address, base64_blinded_pk); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); /* HS_DESC UPLOADED... */ hs_control_desc_event_uploaded(&ident, HSDIR_EXIST_ID); tor_asprintf(&expected_msg, "650 HS_DESC UPLOADED %s UNKNOWN " STR_HSDIR_EXIST_LONGNAME "\r\n", onion_address); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, expected_msg); tor_free(received_msg); tor_free(expected_msg); done: UNMOCK(queue_control_event_string); UNMOCK(node_describe_longname_by_id); UNMOCK(node_get_by_id); tor_free(received_msg); tor_free(expected_msg); } /** Test that we can correctly add, remove and view client auth credentials * using the control port. */ static void test_hs_control_good_onion_client_auth_add(void *arg) { (void) arg; MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); int retval; ed25519_public_key_t service_identity_pk_2fv, service_identity_pk_jt4, service_identity_pk_jam; control_connection_t conn; char *args = NULL; char *cp1 = NULL; size_t sz; hs_init(); { /* Setup the control conn */ memset(&conn, 0, sizeof(control_connection_t)); TO_CONN(&conn)->outbuf = buf_new(); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_ADD"); } { /* Setup the services */ retval = hs_parse_address( "2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd", &service_identity_pk_2fv, NULL, NULL); tt_int_op(retval, OP_EQ, 0); retval = hs_parse_address( "jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd", &service_identity_pk_jt4, NULL, NULL); tt_int_op(retval, OP_EQ, 0); retval = hs_parse_address( "jamie3vkiwibfiwucd6vxijskbhpjdyajmzeor4mc4i7yopvpo4p7cyd", &service_identity_pk_jam, NULL, NULL); tt_int_op(retval, OP_EQ, 0); } digest256map_t *client_auths = get_hs_client_auths_map(); tt_assert(!client_auths); /* Register first service */ args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= "); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); tor_free(args); /* Register second service (even with an unrecognized argument) */ args = tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " "x25519:eIIdIGoSZwI2Q/lSzpf92akGki5I+PZIDz37MA5BhlA= DropSound=No"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); tor_free(args); /* Register second service (even with an unrecognized argument) */ args = tor_strdup("jamie3vkiwibfiwucd6vxijskbhpjdyajmzeor4mc4i7yopvpo4p7cyd " "x25519:FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ= " "ClientName=MeganNicole "); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); client_auths = get_hs_client_auths_map(); tt_assert(client_auths); tt_uint_op(digest256map_size(client_auths), OP_EQ, 3); hs_client_service_authorization_t *client_2fv = digest256map_get(client_auths, service_identity_pk_2fv.pubkey); tt_assert(client_2fv); tt_int_op(client_2fv->flags, OP_EQ, 0); hs_client_service_authorization_t *client_jt4 = digest256map_get(client_auths, service_identity_pk_jt4.pubkey); tt_assert(client_jt4); tt_int_op(client_jt4->flags, OP_EQ, 0); hs_client_service_authorization_t *client_jam = digest256map_get(client_auths, service_identity_pk_jam.pubkey); tt_assert(client_jam); tt_int_op(client_jam->flags, OP_EQ, 0); /* Now let's VIEW the auth credentials */ tor_free(conn.current_cmd); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_VIEW"); /* First go with no arguments, so that we view all the credentials */ tor_free(args); args = tor_strdup(""); #define VIEW_CORRECT_REPLY_NO_ADDR "250-ONION_CLIENT_AUTH_VIEW\r\n" \ "250-CLIENT 2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " \ "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ=\r\n" \ "250-CLIENT jamie3vkiwibfiwucd6vxijskbhpjdyajmzeor4mc4i7yopvpo4p7cyd " \ "x25519:FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ= " \ "ClientName=MeganNicole\r\n" \ "250-CLIENT jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " \ "x25519:eIIdIGoSZwI2Q/lSzpf92akGki5I+PZIDz37MA5BhlA=\r\n" \ "250 OK\r\n" retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, VIEW_CORRECT_REPLY_NO_ADDR); tor_free(cp1); /* Now specify an HS addr, and see that we only view those creds */ tor_free(args); args = tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd"); #define VIEW_CORRECT_REPLY_JT4 "250-ONION_CLIENT_AUTH_VIEW " \ "jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd\r\n" \ "250-CLIENT jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " \ "x25519:eIIdIGoSZwI2Q/lSzpf92akGki5I+PZIDz37MA5BhlA=\r\n" \ "250 OK\r\n" retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, VIEW_CORRECT_REPLY_JT4); tor_free(cp1); /* Now try to REMOVE the auth credentials */ tor_free(conn.current_cmd); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_REMOVE"); /* First try with a wrong addr */ tor_free(args); args = tor_strdup("thatsok"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "512 Invalid v3 address \"thatsok\"\r\n"); tor_free(cp1); client_jt4 = digest256map_get(client_auths, service_identity_pk_jt4.pubkey); tt_assert(client_jt4); /* Now actually remove them. */ tor_free(args); args =tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); client_jt4 = digest256map_get(client_auths, service_identity_pk_jt4.pubkey); tt_assert(!client_jt4); /* Now try another time (we should get 'already removed' msg) */ retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "251 No credentials for " "\"jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd\"\r\n"); tor_free(cp1); client_jt4 = digest256map_get(client_auths, service_identity_pk_jt4.pubkey); tt_assert(!client_jt4); /* Now also remove the other one */ tor_free(args); args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); /* Now also remove the other one */ tor_free(args); args = tor_strdup("jamie3vkiwibfiwucd6vxijskbhpjdyajmzeor4mc4i7yopvpo4p7cyd"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); tor_free(cp1); /* Finally, do another VIEW and see that we get nothing. */ tor_free(conn.current_cmd); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_VIEW"); tor_free(args); args = tor_strdup(""); #define VIEW_CORRECT_REPLY_NOTHING "250-ONION_CLIENT_AUTH_VIEW\r\n250 OK\r\n" retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, VIEW_CORRECT_REPLY_NOTHING); tor_free(cp1); /* And a final VIEW with a wrong HS addr */ tor_free(args); args = tor_strdup("house"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "512 Invalid v3 address \"house\"\r\n"); done: tor_free(args); tor_free(cp1); buf_free(TO_CONN(&conn)->outbuf); tor_free(conn.current_cmd); hs_client_free_all(); } /** Test some error cases of ONION_CLIENT_AUTH_ADD */ static void test_hs_control_bad_onion_client_auth_add(void *arg) { (void) arg; MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); int retval; control_connection_t conn; char *cp1 = NULL; size_t sz; char *args = NULL; hs_init(); { /* Setup the control conn */ memset(&conn, 0, sizeof(control_connection_t)); TO_CONN(&conn)->outbuf = buf_new(); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_ADD"); } digest256map_t *client_auths = get_hs_client_auths_map(); tt_assert(!client_auths); /* Register first service */ args = tor_strdup( "badaddr x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ="); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "512 Invalid v3 address \"badaddr\"\r\n"); tor_free(cp1); tor_free(args); /* Register second service (even with an unrecognized argument) */ args = tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " "love:eIIdIGoSZwI2Q/lSzpf92akGki5I+PZIDz37MA5BhlA="); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "552 Unrecognized key type \"love\"\r\n"); tor_free(cp1); tor_free(args); /* Register second service (even with an unrecognized argument) */ args = tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " "x25519:QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEK"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "512 Failed to decode x25519 private key\r\n"); tor_free(cp1); tor_free(args); /* Register with an all zero client key */ args = tor_strdup("jt4grrjwzyz3pjkylwfau5xnjaj23vxmhskqaeyfhrfylelw4hvxcuyd " "x25519:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check contents */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "553 Invalid private key \"AAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAA=\"\r\n"); client_auths = get_hs_client_auths_map(); tt_assert(!client_auths); done: tor_free(args); tor_free(cp1); buf_free(TO_CONN(&conn)->outbuf); tor_free(conn.current_cmd); hs_client_free_all(); } /** Test that we can correctly add permanent client auth credentials using the * control port. */ static void test_hs_control_store_permanent_creds(void *arg) { (void) arg; MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); int retval; ed25519_public_key_t service_identity_pk_2fv; control_connection_t conn; char *args = NULL; char *cp1 = NULL; char *creds_file_str = NULL; char *creds_fname = NULL; size_t sz; hs_init(); { /* Setup the control conn */ memset(&conn, 0, sizeof(control_connection_t)); TO_CONN(&conn)->outbuf = buf_new(); conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_ADD"); } { /* Setup the services */ retval = hs_parse_address( "2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd", &service_identity_pk_2fv, NULL, NULL); tt_int_op(retval, OP_EQ, 0); } digest256map_t *client_auths = get_hs_client_auths_map(); tt_assert(!client_auths); /* Try registering first service with no ClientOnionAuthDir set */ args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= " "Flags=Permanent"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check control port response. This one should fail. */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "553 Unable to store creds for " "\"2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd\"\r\n"); { /* Setup ClientOnionAuthDir */ int ret; char *perm_creds_dir = tor_strdup(get_fname("permanent_credentials")); get_options_mutable()->ClientOnionAuthDir = perm_creds_dir; #ifdef _WIN32 ret = mkdir(perm_creds_dir); #else ret = mkdir(perm_creds_dir, 0700); #endif tt_int_op(ret, OP_EQ, 0); } tor_free(args); tor_free(cp1); /* Try the control port command again. This time it should work! */ args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= " "Flags=Permanent"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check control port response */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); /* Check file contents! */ creds_fname = tor_strdup(get_fname("permanent_credentials/" "2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd.auth_private")); creds_file_str = read_file_to_str(creds_fname, RFTS_BIN, NULL); tt_assert(creds_file_str); tt_str_op(creds_file_str, OP_EQ, "2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd:descriptor:" /* base32 representation of the base64 iJ1t... key above */ "x25519:rcow3dfavmyanyqvhwnvnmfdqw34ydtrgv7jnelmqs4wi4uuxrca"); tor_free(args); tor_free(cp1); /* Overwrite the credentials and check that they got overwritten. */ args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " "x25519:UDRvZLvcJo0QRLvDfkpgbtsqbkhIUQZyeo2FNBrgS18= " "Flags=Permanent"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check control port response: we replaced! */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "251 Client for onion existed and replaced\r\n"); tor_free(creds_file_str); /* Check creds file contents again. See that the key got updated */ creds_file_str = read_file_to_str(creds_fname, RFTS_BIN, NULL); tt_assert(creds_file_str); tt_str_op(creds_file_str, OP_EQ, "2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd:descriptor:" /* base32 representation of the base64 UDRv... key above */ "x25519:ka2g6zf33qti2ecexpbx4stan3nsu3sijbiqm4t2rwctigxajnpq"); /* Now for our next act!!! Actually get the HS client subsystem to parse the * whole directory and make sure that it extracted the right credential! */ hs_config_client_authorization(get_options(), 0); client_auths = get_hs_client_auths_map(); tt_assert(client_auths); tt_uint_op(digest256map_size(client_auths), OP_EQ, 1); hs_client_service_authorization_t *client_2fv = digest256map_get(client_auths, service_identity_pk_2fv.pubkey); tt_assert(client_2fv); tt_int_op(client_2fv->flags, OP_EQ, CLIENT_AUTH_FLAG_IS_PERMANENT); tt_str_op(hex_str((char*)client_2fv->enc_seckey.secret_key, 32), OP_EQ, "50346F64BBDC268D1044BBC37E4A606EDB2A6E48485106727A8D85341AE04B5F"); /* And now for the final act! Use the REMOVE control port command to remove the credential, and ensure that the file has also been removed! */ tor_free(conn.current_cmd); tor_free(cp1); tor_free(args); /* Ensure that the creds file exists */ tt_int_op(file_status(creds_fname), OP_EQ, FN_FILE); /* Do the REMOVE */ conn.current_cmd = tor_strdup("ONION_CLIENT_AUTH_REMOVE"); args =tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250 OK\r\n"); /* Ensure that the file has been removed and the map is empty */ tt_int_op(file_status(creds_fname), OP_EQ, FN_NOENT); tt_uint_op(digest256map_size(client_auths), OP_EQ, 0); done: tor_free(get_options_mutable()->ClientOnionAuthDir); tor_free(args); tor_free(cp1); buf_free(TO_CONN(&conn)->outbuf); tor_free(conn.current_cmd); tor_free(creds_fname); tor_free(creds_file_str); hs_client_free_all(); } /** Test that ADD_ONION properly handles an attacker passing it a bad private * key. */ static void test_hs_control_add_onion_with_bad_pubkey(void *arg) { (void) arg; MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); int retval; control_connection_t conn; char *args = NULL; char *cp1 = NULL; size_t sz; hs_init(); { /* Setup the control conn */ memset(&conn, 0, sizeof(control_connection_t)); TO_CONN(&conn)->outbuf = buf_new(); conn.current_cmd = tor_strdup("ADD_ONION"); } args = tor_strdup("ED25519-V3:AAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA " "Port=9735,127.0.0.1 Flags=DiscardPK"); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); /* Check control port response */ cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "551 Failed to generate onion address\r\n"); done: tor_free(args); tor_free(cp1); buf_free(TO_CONN(&conn)->outbuf); tor_free(conn.current_cmd); } /** Test that we can add the service via the control port. */ static void test_hs_control_add_auth_onion_service(void *arg) { control_connection_t conn; char *args = NULL, *cp1 = NULL; size_t sz; (void) arg; hs_init(); memset(&conn, 0, sizeof(control_connection_t)); TO_CONN(&conn)->outbuf = buf_new(); conn.current_cmd = tor_strdup("ADD_ONION"); args = tor_strdup("ED25519-V3:KLMQ4CLKwlDCHuMPn8j3od33cU5LhnrLNoZh7CWChl3VkY" "pNAkeP5dGW8xeKR9HxQBWQ/w7Kr12lA/U8Pd/oxw== " "ClientAuthV3=dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja " "Flags=V3Auth Port=9735,127.0.0.1"); handle_control_command(&conn, (uint32_t) strlen(args), args); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "250-ServiceID=n35etu3yjxrqjpntmfziom5sjwspoydchmelc4xleoy4jk2u4lziz2yd\r\n" "250-ClientAuthV3=dz4q5xqlb4ldnbs72iarrml4ephk3du4i7o2cgiva5lwr6wkquja\r\n" "250 OK\r\n"); tor_free(args); tor_free(cp1); args = tor_strdup("ED25519-V3:iIU8EBi71qE7G6UTsROU1kWN0JMrRP/YukC0Xk5WLGyil3" "gm4u3wEBXr+/TaCpXS+65Pcdqz+PG+4+oWHLN05A== " "ClientAuthV3=dummy Flags=V3Auth Port=9735,127.0.0.1"); handle_control_command(&conn, (uint32_t) strlen(args), args); cp1 = buf_get_contents(TO_CONN(&conn)->outbuf, &sz); tt_str_op(cp1, OP_EQ, "512 Cannot decode v3 client auth key\r\n"); done: tor_free(args); tor_free(cp1); tor_free(conn.current_cmd); buf_free(TO_CONN(&conn)->outbuf); SMARTLIST_FOREACH(conn.ephemeral_onion_services, char *, service, tor_free(service)); smartlist_free(conn.ephemeral_onion_services); hs_client_free_all(); } /** Test that add_onion_helper_add_service can add the service. */ static void test_hs_control_add_onion_helper_add_service(void *arg) { int hs_version_good, hs_version_bad; add_onion_secret_key_t sk_good, sk_bad; ed25519_public_key_t pk_good, pk_bad; char *key_new_blob_good = NULL, *key_new_blob_bad = NULL; const char *key_new_alg_good = NULL, *key_new_alg_bad = NULL; hs_service_authorized_client_t *client_good, *client_bad; smartlist_t *list_good, *list_bad; hs_service_ht *global_map; hs_port_config_t *portcfg; smartlist_t *portcfgs; char *address_out_good = NULL, *address_out_bad = NULL; hs_service_t *service_good = NULL; hs_service_t *service_bad = NULL; (void) arg; hs_init(); global_map = get_hs_service_map(); portcfg = hs_parse_port_config("8080", ",", NULL); portcfgs = smartlist_new(); smartlist_add(portcfgs, portcfg); memset(&sk_good, 0, sizeof(sk_good)); memset(&sk_bad, 0, sizeof(sk_bad)); add_onion_helper_keyarg("NEW:ED25519-V3", 0, &key_new_alg_good, &key_new_blob_good, &sk_good, &hs_version_good, NULL); add_onion_helper_keyarg("NEW:ED25519-V3", 0, &key_new_alg_bad, &key_new_blob_bad, &sk_bad, &hs_version_bad, NULL); ed25519_public_key_generate(&pk_good, sk_good.v3); ed25519_public_key_generate(&pk_bad, sk_bad.v3); client_good = parse_authorized_client_key( "N2NU7BSRL6YODZCYPN4CREB54TYLKGIE2KYOQWLFYC23ZJVCE5DQ", LOG_INFO); client_bad = parse_authorized_client_key("dummy", LOG_INFO); list_good = smartlist_new(); smartlist_add(list_good, client_good); add_onion_helper_add_service(HS_VERSION_THREE, &sk_good, portcfgs, 1, 1, list_good, &address_out_good); service_good = find_service(global_map, &pk_good); tt_int_op(smartlist_len(service_good->config.clients), OP_EQ, 1); remove_service(global_map, service_good); hs_service_free(service_good); list_bad = smartlist_new(); smartlist_add(list_bad, client_bad); portcfg = hs_parse_port_config("8080", ",", NULL); portcfgs = smartlist_new(); smartlist_add(portcfgs, portcfg); add_onion_helper_add_service(HS_VERSION_THREE, &sk_bad, portcfgs, 1, 1, list_bad, &address_out_bad); service_bad = find_service(global_map, &pk_bad); tt_int_op(smartlist_len(service_bad->config.clients), OP_EQ, 0); done: tor_free(key_new_blob_good); tor_free(key_new_blob_bad); tor_free(address_out_good); tor_free(address_out_bad); hs_service_free(service_good); hs_service_free(service_bad); } struct testcase_t hs_control_tests[] = { { "hs_desc_event", test_hs_desc_event, TT_FORK, NULL, NULL }, { "hs_control_good_onion_client_auth_add", test_hs_control_good_onion_client_auth_add, TT_FORK, NULL, NULL }, { "hs_control_bad_onion_client_auth_add", test_hs_control_bad_onion_client_auth_add, TT_FORK, NULL, NULL }, { "hs_control_store_permanent_creds", test_hs_control_store_permanent_creds, TT_FORK, NULL, NULL }, { "hs_control_add_onion_with_bad_pubkey", test_hs_control_add_onion_with_bad_pubkey, TT_FORK, NULL, NULL }, { "hs_control_add_auth_onion_service", test_hs_control_add_auth_onion_service, TT_FORK, NULL, NULL}, { "hs_control_add_onion_helper_add_service", test_hs_control_add_onion_helper_add_service, TT_FORK, NULL, NULL}, END_OF_TESTCASES };