diff options
Diffstat (limited to 'src/feature/hs/hs_client.c')
-rw-r--r-- | src/feature/hs/hs_client.c | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index 1f9218e15a..7c545c35d5 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -42,6 +42,10 @@ #include "core/or/extend_info_st.h" #include "core/or/origin_circuit_st.h" +/* Client-side authorizations for hidden services; map of service identity + * public key to hs_client_service_authorization_t *. */ +static digest256map_t *client_auths = NULL; + /* Return a human-readable string for the client fetch status code. */ static const char * fetch_status_to_string(hs_client_fetch_status_t status) @@ -1393,6 +1397,216 @@ hs_client_receive_rendezvous_acked(origin_circuit_t *circ, return -1; } +#define client_service_authorization_free(auth) \ + FREE_AND_NULL(hs_client_service_authorization_t, \ + client_service_authorization_free_, (auth)) + +static void +client_service_authorization_free_(hs_client_service_authorization_t *auth) +{ + if (auth) { + memwipe(auth, 0, sizeof(*auth)); + } + tor_free(auth); +} + +/** Helper for digest256map_free. */ +static void +client_service_authorization_free_void(void *auth) +{ + client_service_authorization_free_(auth); +} + +static void +client_service_authorization_free_all(void) +{ + if (!client_auths) { + return; + } + digest256map_free(client_auths, client_service_authorization_free_void); +} + +/* Check if the auth key file name is valid or not. Return 1 if valid, + * otherwise return 0. */ +static int +auth_key_filename_is_valid(const char *filename) +{ + int ret = 1; + const char *valid_extension = ".auth_private"; + + tor_assert(filename); + + /* The length of the filename must be greater than the length of the + * extension and the valid extension must be at the end of filename. */ + if (!strcmpend(filename, valid_extension) && + strlen(filename) != strlen(valid_extension)) { + ret = 1; + } else { + ret = 0; + } + + return ret; +} + +static hs_client_service_authorization_t * +parse_auth_file_content(const char *client_key_str) +{ + char *onion_address = NULL; + char *auth_type = NULL; + char *key_type = NULL; + char *seckey_b32 = NULL; + hs_client_service_authorization_t *auth = NULL; + smartlist_t *fields = smartlist_new(); + + tor_assert(client_key_str); + + smartlist_split_string(fields, client_key_str, ":", + SPLIT_SKIP_SPACE, 0); + /* Wrong number of fields. */ + if (smartlist_len(fields) != 4) { + goto err; + } + + onion_address = smartlist_get(fields, 0); + auth_type = smartlist_get(fields, 1); + key_type = smartlist_get(fields, 2); + seckey_b32 = smartlist_get(fields, 3); + + /* Currently, the only supported auth type is "descriptor" and the only + * supported key type is "x25519". */ + if (strcmp(auth_type, "descriptor") || strcmp(key_type, "x25519")) { + goto err; + } + + auth = tor_malloc_zero(sizeof(hs_client_service_authorization_t)); + if (base32_decode((char *) auth->enc_seckey.secret_key, + sizeof(auth->enc_seckey.secret_key), + seckey_b32, strlen(seckey_b32)) < 0) { + goto err; + } + strncpy(auth->onion_address, onion_address, HS_SERVICE_ADDR_LEN_BASE32); + + /* Success. */ + goto done; + + err: + client_service_authorization_free(auth); + done: + /* It is also a good idea to wipe the private key. */ + if (seckey_b32) { + memwipe(seckey_b32, 0, strlen(seckey_b32)); + } + if (fields) { + SMARTLIST_FOREACH(fields, char *, s, tor_free(s)); + smartlist_free(fields); + } + return auth; +} + +/* From a set of <b>options</b>, setup every client authorization detail + * found. Return 0 on success or -1 on failure. If <b>validate_only</b> + * is set, parse, warn and return as normal, but don't actually change + * the configuration. */ +int +hs_config_client_authorization(const or_options_t *options, + int validate_only) +{ + 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); + + /* There is no client auth configured. We can just silently ignore this + * function. */ + if (!options->ClientOnionAuthDir) { + ret = 0; + 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) { + goto end; + } + + file_list = tor_listdir(key_dir); + if (file_list == NULL) { + log_warn(LD_REND, "Client authorization key directory %s can't be listed.", + key_dir); + goto end; + } + + SMARTLIST_FOREACH_BEGIN(file_list, char *, filename) { + + hs_client_service_authorization_t *auth = NULL; + ed25519_public_key_t identity_pk; + + if (auth_key_filename_is_valid(filename)) { + /* 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) { + 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) { + client_service_authorization_free(auth); + continue; + } + + if (digest256map_get(auths, identity_pk.pubkey)) { + client_service_authorization_free(auth); + + log_warn(LD_REND, "Duplicate authorization for the same hidden " + "service."); + goto end; + } + + digest256map_set(auths, identity_pk.pubkey, auth); + } + } + + } 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); + } + + if (!validate_only && ret == 0) { + client_service_authorization_free_all(); + client_auths = auths; + } else { + digest256map_free(auths, client_service_authorization_free_void); + } + + return ret; +} + /* This is called when a descriptor has arrived following a fetch request and * has been stored in the client cache. Every entry connection that matches * the service identity key in the ident will get attached to the hidden @@ -1589,6 +1803,7 @@ hs_client_free_all(void) { /* Purge the hidden service request cache. */ hs_purge_last_hid_serv_requests(); + client_service_authorization_free_all(); } /* Purge all potentially remotely-detectable state held in the hidden |