diff options
author | David Goulet <dgoulet@torproject.org> | 2019-12-03 09:22:21 -0500 |
---|---|---|
committer | David Goulet <dgoulet@torproject.org> | 2019-12-03 09:22:21 -0500 |
commit | 65759f29019ed4cd33502fef9b06bb92aa6e348a (patch) | |
tree | dc7761b71389d7f18dc85b0df90ae1748130a49c | |
parent | df6c5382ad0c3da45808bf67cec63f40942b561b (diff) | |
parent | 12305b6bb6a68640e011dc5e2f2557bdccc2ae22 (diff) | |
download | tor-65759f29019ed4cd33502fef9b06bb92aa6e348a.tar.gz tor-65759f29019ed4cd33502fef9b06bb92aa6e348a.zip |
Merge branch 'tor-github/pr/1563'
-rw-r--r-- | src/feature/control/control_hs.c | 18 | ||||
-rw-r--r-- | src/feature/hs/hs_client.c | 296 | ||||
-rw-r--r-- | src/feature/hs/hs_client.h | 8 | ||||
-rw-r--r-- | src/test/test_hs_control.c | 181 |
4 files changed, 422 insertions, 81 deletions
diff --git a/src/feature/control/control_hs.c b/src/feature/control/control_hs.c index 4c1d16a8c8..94940a7396 100644 --- a/src/feature/control/control_hs.c +++ b/src/feature/control/control_hs.c @@ -73,7 +73,6 @@ const control_cmd_syntax_t onion_client_auth_add_syntax = { * register the new client-side client auth credentials: * "ONION_CLIENT_AUTH_ADD" SP HSAddress * SP KeyType ":" PrivateKeyBlob - * [SP "ClientName=" Nickname] * [SP "Type=" TYPE] CRLF */ int @@ -112,14 +111,7 @@ handle_control_onion_client_auth_add(control_connection_t *conn, /* Now let's parse the remaining arguments (variable size) */ for (const config_line_t *line = args->kwargs; line; line = line->next) { - if (!strcasecmp(line->key, "ClientName")) { - if (strlen(line->value) > HS_CLIENT_AUTH_MAX_NICKNAME_LENGTH) { - control_write_endreply(conn, 512, "Too big 'ClientName' argument"); - goto err; - } - creds->nickname = tor_strdup(line->value); - - } else if (!strcasecmpstart(line->key, "Flags")) { + if (!strcasecmpstart(line->key, "Flags")) { smartlist_split_string(flags, line->value, ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { control_write_endreply(conn, 512, "Invalid 'Flags' argument"); @@ -145,6 +137,10 @@ handle_control_onion_client_auth_add(control_connection_t *conn, /* It's a bug because the service addr has already been validated above */ control_printf_endreply(conn, 512, "Invalid v3 address \"%s\"", hsaddress); break; + case REGISTER_FAIL_PERMANENT_STORAGE: + control_printf_endreply(conn, 553, "Unable to store creds for \"%s\"", + hsaddress); + break; case REGISTER_SUCCESS_ALREADY_EXISTS: control_printf_endreply(conn, 251,"Client for onion existed and replaced"); break; @@ -245,10 +241,6 @@ encode_client_auth_cred_for_control_port( smartlist_add_asprintf(control_line, "CLIENT x25519:%s", x25519_b64); - if (cred->nickname) { /* nickname is optional */ - smartlist_add_asprintf(control_line, " ClientName=%s", cred->nickname); - } - if (cred->flags) { /* flags are also optional */ if (cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { smartlist_add_asprintf(control_line, " Flags=Permanent"); diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index a2a47c1da6..b5030da473 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -1445,6 +1445,80 @@ client_dir_fetch_unexpected(dir_connection_t *dir_conn, const char *reason, NULL); } +/** Get the full filename for storing the client auth credentials for the + * service in <b>onion_address</b>. The base directory is <b>dir</b>. + * This function never returns NULL. */ +static char * +get_client_auth_creds_filename(const char *onion_address, + const char *dir) +{ + char *full_fname = NULL; + char *fname; + + tor_asprintf(&fname, "%s.auth_private", onion_address); + full_fname = hs_path_from_filename(dir, fname); + tor_free(fname); + + return full_fname; +} + +/** Permanently store the credentials in <b>creds</b> to disk. + * + * Return -1 if there was an error while storing the credentials, otherwise + * return 0. + */ +static int +store_permanent_client_auth_credentials( + const hs_client_service_authorization_t *creds) +{ + const or_options_t *options = get_options(); + char *full_fname = NULL; + char *file_contents = NULL; + char priv_key_b32[BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)+1]; + int retval = -1; + + tor_assert(creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT); + + /* We need ClientOnionAuthDir to be set, otherwise we can't proceed */ + if (!options->ClientOnionAuthDir) { + log_warn(LD_GENERAL, "Can't register permanent client auth credentials " + "for %s without ClientOnionAuthDir option. Discarding.", + creds->onion_address); + goto err; + } + + /* Make sure the directory exists and is private enough. */ + if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) { + goto err; + } + + /* Get filename that we should store the credentials */ + full_fname = get_client_auth_creds_filename(creds->onion_address, + options->ClientOnionAuthDir); + + /* Encode client private key */ + base32_encode(priv_key_b32, sizeof(priv_key_b32), + (char*)creds->enc_seckey.secret_key, + sizeof(creds->enc_seckey.secret_key)); + + /* Get the full file contents and write it to disk! */ + tor_asprintf(&file_contents, "%s:descriptor:x25519:%s", + creds->onion_address, priv_key_b32); + if (write_str_to_file(full_fname, file_contents, 0) < 0) { + log_warn(LD_GENERAL, "Failed to write client auth creds file for %s!", + creds->onion_address); + goto err; + } + + retval = 0; + + err: + tor_free(file_contents); + tor_free(full_fname); + + return retval; +} + /** Register the credential <b>creds</b> as part of the client auth subsystem. * * Takes ownership of <b>creds</b>. @@ -1468,6 +1542,15 @@ hs_client_register_auth_credentials(hs_client_service_authorization_t *creds) return REGISTER_FAIL_BAD_ADDRESS; } + /* If we reach this point, the credentials will be stored one way or another: + * Make them permanent if the user asked us to. */ + if (creds->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { + if (store_permanent_client_auth_credentials(creds) < 0) { + client_service_authorization_free(creds); + return REGISTER_FAIL_PERMANENT_STORAGE; + } + } + old_creds = digest256map_get(client_auths, service_identity_pk.pubkey); if (old_creds) { digest256map_remove(client_auths, service_identity_pk.pubkey); @@ -1486,6 +1569,128 @@ hs_client_register_auth_credentials(hs_client_service_authorization_t *creds) return retval; } +/** Load a client authorization file with <b>filename</b> that is stored under + * the global client auth directory, and return a newly-allocated credentials + * object if it parsed well. Otherwise, return NULL. + */ +static hs_client_service_authorization_t * +get_creds_from_client_auth_filename(const char *filename, + const or_options_t *options) +{ + hs_client_service_authorization_t *auth = NULL; + char *client_key_file_path = NULL; + char *client_key_str = NULL; + + log_info(LD_REND, "Loading a client authorization key file %s...", + filename); + + if (!auth_key_filename_is_valid(filename)) { + log_notice(LD_REND, "Client authorization unrecognized filename %s. " + "File must end in .auth_private. Ignoring.", + filename); + goto err; + } + + /* Create a full path for a file. */ + client_key_file_path = hs_path_from_filename(options->ClientOnionAuthDir, + filename); + + client_key_str = read_file_to_str(client_key_file_path, 0, NULL); + if (!client_key_str) { + log_warn(LD_REND, "The file %s cannot be read.", filename); + goto err; + } + + auth = parse_auth_file_content(client_key_str); + if (!auth) { + goto err; + } + + err: + tor_free(client_key_str); + tor_free(client_key_file_path); + + return auth; +} + +/* + * Remove the file in <b>filename</b> under the global client auth credential + * storage. + */ +static void +remove_client_auth_creds_file(const char *filename) +{ + char *creds_file_path = NULL; + const or_options_t *options = get_options(); + + creds_file_path = hs_path_from_filename(options->ClientOnionAuthDir, + filename); + if (tor_unlink(creds_file_path) != 0) { + log_warn(LD_REND, "Failed to remove client auth file (%s).", + creds_file_path); + goto end; + } + + log_warn(LD_REND, "Successfuly removed client auth file (%s).", + creds_file_path); + + end: + tor_free(creds_file_path); +} + +/** + * Find the filesystem file corresponding to the permanent client auth + * credentials in <b>cred</b> and remove it. + */ +static void +find_and_remove_client_auth_creds_file( + const hs_client_service_authorization_t *cred) +{ + smartlist_t *file_list = NULL; + const or_options_t *options = get_options(); + + tor_assert(cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT); + + if (!options->ClientOnionAuthDir) { + log_warn(LD_REND, "Found permanent credential but no ClientOnionAuthDir " + "configured. There is no file to be removed."); + goto end; + } + + file_list = tor_listdir(options->ClientOnionAuthDir); + if (file_list == NULL) { + log_warn(LD_REND, "Client authorization key directory %s can't be listed.", + options->ClientOnionAuthDir); + goto end; + } + + SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) { + hs_client_service_authorization_t *tmp_cred = NULL; + + tmp_cred = get_creds_from_client_auth_filename(filename, options); + if (!tmp_cred) { + continue; + } + + /* Find the right file for this credential */ + if (!strcmp(tmp_cred->onion_address, cred->onion_address)) { + /* Found it! Remove the file! */ + remove_client_auth_creds_file(filename); + /* cleanup and get out of here */ + client_service_authorization_free(tmp_cred); + break; + } + + client_service_authorization_free(tmp_cred); + } SMARTLIST_FOREACH_END(filename); + + end: + if (file_list) { + SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); + smartlist_free(file_list); + } +} + /** Remove client auth credentials for the service <b>hs_address</b>. */ hs_client_removal_auth_status_t hs_client_remove_auth_credentials(const char *hsaddress) @@ -1502,8 +1707,14 @@ hs_client_remove_auth_credentials(const char *hsaddress) hs_client_service_authorization_t *cred = NULL; cred = digest256map_remove(client_auths, service_identity_pk.pubkey); + /* digestmap_remove() returns the previously stored data if there were any */ if (cred) { + if (cred->flags & CLIENT_AUTH_FLAG_IS_PERMANENT) { + /* These creds are stored on disk: remove the corresponding file. */ + find_and_remove_client_auth_creds_file(cred); + } + client_service_authorization_free(cred); return REMOVAL_SUCCESS; } @@ -1799,10 +2010,6 @@ client_service_authorization_free_(hs_client_service_authorization_t *auth) return; } - if (auth->nickname) { - tor_free(auth->nickname); - } - memwipe(auth, 0, sizeof(*auth)); tor_free(auth); } @@ -1845,6 +2052,13 @@ auth_key_filename_is_valid(const char *filename) return ret; } +/** Parse the client auth credentials off a string in <b>client_key_str</b> + * based on the file format documented in the "Client side configuration" + * section of rend-spec-v3.txt. + * + * Return NULL if there was an error, otherwise return a newly allocated + * hs_client_service_authorization_t structure. + */ STATIC hs_client_service_authorization_t * parse_auth_file_content(const char *client_key_str) { @@ -1875,7 +2089,7 @@ parse_auth_file_content(const char *client_key_str) goto err; } - if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_PUBKEY_LEN)) { + if (strlen(seckey_b32) != BASE32_NOPAD_LEN(CURVE25519_SECKEY_LEN)) { log_warn(LD_REND, "Client authorization encoded base32 private key " "length is invalid: %s", seckey_b32); goto err; @@ -1892,6 +2106,9 @@ parse_auth_file_content(const char *client_key_str) } strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32); + /* We are reading this from the disk, so set the permanent flag anyway. */ + auth->flags |= CLIENT_AUTH_FLAG_IS_PERMANENT; + /* Success. */ goto done; @@ -1918,10 +2135,7 @@ hs_config_client_authorization(const or_options_t *options, { int ret = -1; digest256map_t *auths = digest256map_new(); - char *key_dir = NULL; smartlist_t *file_list = NULL; - char *client_key_str = NULL; - char *client_key_file_path = NULL; tor_assert(options); @@ -1932,82 +2146,54 @@ hs_config_client_authorization(const or_options_t *options, goto end; } - key_dir = tor_strdup(options->ClientOnionAuthDir); - /* Make sure the directory exists and is private enough. */ - if (check_private_dir(key_dir, 0, options->User) < 0) { + if (check_private_dir(options->ClientOnionAuthDir, 0, options->User) < 0) { goto end; } - file_list = tor_listdir(key_dir); + file_list = tor_listdir(options->ClientOnionAuthDir); if (file_list == NULL) { log_warn(LD_REND, "Client authorization key directory %s can't be listed.", - key_dir); + options->ClientOnionAuthDir); goto end; } - SMARTLIST_FOREACH_BEGIN(file_list, char *, filename) { - + SMARTLIST_FOREACH_BEGIN(file_list, const char *, filename) { hs_client_service_authorization_t *auth = NULL; ed25519_public_key_t identity_pk; - log_info(LD_REND, "Loading a client authorization key file %s...", - filename); - if (!auth_key_filename_is_valid(filename)) { - log_notice(LD_REND, "Client authorization unrecognized filename %s. " - "File must end in .auth_private. Ignoring.", - filename); + auth = get_creds_from_client_auth_filename(filename, options); + if (!auth) { continue; } - /* Create a full path for a file. */ - client_key_file_path = hs_path_from_filename(key_dir, filename); - client_key_str = read_file_to_str(client_key_file_path, 0, NULL); - /* Free the file path immediately after using it. */ - tor_free(client_key_file_path); - - /* If we cannot read the file, continue with the next file. */ - if (!client_key_str) { - log_warn(LD_REND, "The file %s cannot be read.", filename); + /* Parse the onion address to get an identity public key and use it + * as a key of global map in the future. */ + if (hs_parse_address(auth->onion_address, &identity_pk, + NULL, NULL) < 0) { + log_warn(LD_REND, "The onion address \"%s\" is invalid in " + "file %s", filename, auth->onion_address); + client_service_authorization_free(auth); continue; } - auth = parse_auth_file_content(client_key_str); - /* Free immediately after using it. */ - tor_free(client_key_str); - - if (auth) { - /* Parse the onion address to get an identity public key and use it - * as a key of global map in the future. */ - if (hs_parse_address(auth->onion_address, &identity_pk, - NULL, NULL) < 0) { - log_warn(LD_REND, "The onion address \"%s\" is invalid in " - "file %s", filename, auth->onion_address); - client_service_authorization_free(auth); - continue; - } - - if (digest256map_get(auths, identity_pk.pubkey)) { + if (digest256map_get(auths, identity_pk.pubkey)) { log_warn(LD_REND, "Duplicate authorization for the same hidden " - "service address %s.", + "service address %s.", safe_str_client_opts(options, auth->onion_address)); client_service_authorization_free(auth); goto end; - } - - digest256map_set(auths, identity_pk.pubkey, auth); - log_info(LD_REND, "Loaded a client authorization key file %s.", - filename); } + + digest256map_set(auths, identity_pk.pubkey, auth); + log_info(LD_REND, "Loaded a client authorization key file %s.", + filename); } SMARTLIST_FOREACH_END(filename); /* Success. */ ret = 0; end: - tor_free(key_dir); - tor_free(client_key_str); - tor_free(client_key_file_path); if (file_list) { SMARTLIST_FOREACH(file_list, char *, s, tor_free(s)); smartlist_free(file_list); diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 23effc06bd..959ba136cd 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -45,6 +45,8 @@ typedef enum { REGISTER_SUCCESS_AND_DECRYPTED, /* We failed to register these credentials, because of a bad HS address. */ REGISTER_FAIL_BAD_ADDRESS, + /* We failed to register these credentials, because of a bad HS address. */ + REGISTER_FAIL_PERMANENT_STORAGE, } hs_client_register_auth_status_t; /* Status code of client auth credential removal */ @@ -60,9 +62,6 @@ typedef enum { /** Flag to set when a client auth is permanent (saved on disk). */ #define CLIENT_AUTH_FLAG_IS_PERMANENT (1<<0) -/** Max length of a client auth nickname */ -#define HS_CLIENT_AUTH_MAX_NICKNAME_LENGTH 255 - /** Client-side configuration of client authorization */ typedef struct hs_client_service_authorization_t { /** An curve25519 secret key used to compute decryption keys that @@ -72,9 +71,6 @@ typedef struct hs_client_service_authorization_t { /** An onion address that is used to connect to the onion service. */ char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1]; - /* An optional nickname for this client */ - char *nickname; - /* Optional flags for this client. */ int flags; } hs_client_service_authorization_t; diff --git a/src/test/test_hs_control.c b/src/test/test_hs_control.c index 9279080329..d064f203a6 100644 --- a/src/test/test_hs_control.c +++ b/src/test/test_hs_control.c @@ -30,6 +30,17 @@ #include "test/test_helpers.h" +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef _WIN32 +/* For mkdir() */ +#include <direct.h> +#else +#include <dirent.h> +#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" \ @@ -234,8 +245,7 @@ test_hs_control_good_onion_client_auth_add(void *arg) /* Register first service */ args = tor_strdup("2fvhjskjet3n5syd6yfg5lhvwcs62bojmthr35ko5bllr3iqdb4ctdyd " - "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= " - "ClientName=bob Flags=Permanent"); + "x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= "); retval = handle_control_command(&conn, (uint32_t) strlen(args), args); tt_int_op(retval, OP_EQ, 0); @@ -266,13 +276,11 @@ test_hs_control_good_onion_client_auth_add(void *arg) hs_client_service_authorization_t *client_2fv = digest256map_get(client_auths, service_identity_pk_2fv.pubkey); tt_assert(client_2fv); - tt_str_op(client_2fv->nickname, OP_EQ, "bob"); - tt_int_op(client_2fv->flags, OP_EQ, CLIENT_AUTH_FLAG_IS_PERMANENT); + 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_assert(!client_jt4->nickname); tt_int_op(client_jt4->flags, OP_EQ, 0); /* Now let's VIEW the auth credentials */ @@ -285,8 +293,7 @@ test_hs_control_good_onion_client_auth_add(void *arg) #define VIEW_CORRECT_REPLY_NO_ADDR "250-ONION_CLIENT_AUTH_VIEW\r\n" \ "250-CLIENT x25519:eIIdIGoSZwI2Q/lSzpf92akGki5I+PZIDz37MA5BhlA=\r\n"\ - "250-CLIENT x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ= " \ - "ClientName=bob Flags=Permanent\r\n" \ + "250-CLIENT x25519:iJ1tjKCrMAbiFT2bVrCjhbfMDnE1fpaRbIS5ZHKUvEQ=\r\n" \ "250 OK\r\n" retval = handle_control_command(&conn, (uint32_t) strlen(args), args); @@ -466,6 +473,164 @@ test_hs_control_bad_onion_client_auth_add(void *arg) 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; + + { /* 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")); + + #ifdef _WIN32 + ret = mkdir(perm_creds_dir); + #else + ret = mkdir(perm_creds_dir, 0700); + #endif + tt_int_op(ret, OP_EQ, 0); + + get_options_mutable()->ClientOnionAuthDir = perm_creds_dir; + } + + 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:" + /* This is the base32 represenation of the base64 iJ1t... key above */ + "x25519:rcow3dfavmyanyqvhwnvnmfdqw34ydtrgv7jnelmqs4wi4uuxrca"); + + tor_free(args); + tor_free(cp1); + + /* Overwrite the credentials and check that they got overwrited. */ + 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:" + /* This is the base32 represenation 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(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(); +} + struct testcase_t hs_control_tests[] = { { "hs_desc_event", test_hs_desc_event, TT_FORK, NULL, NULL }, @@ -475,6 +640,8 @@ struct testcase_t hs_control_tests[] = { { "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 }, END_OF_TESTCASES }; |