diff options
-rw-r--r-- | src/app/config/config.c | 7 | ||||
-rw-r--r-- | src/app/config/or_options_st.h | 2 | ||||
-rw-r--r-- | src/feature/hs/hs_client.c | 215 | ||||
-rw-r--r-- | src/feature/hs/hs_client.h | 13 | ||||
-rw-r--r-- | src/feature/hs/hs_config.c | 27 | ||||
-rw-r--r-- | src/feature/hs/hs_config.h | 1 |
6 files changed, 263 insertions, 2 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c index 339f8e2475..ce9ae8d7ce 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -450,6 +450,7 @@ static config_var_t option_vars_[] = { VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), V(HidServAuth, LINELIST, NULL), + V(ClientOnionAuthDir, FILENAME, NULL), OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"), OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"), V(HiddenServiceSingleHopMode, BOOL, "0"), @@ -1917,7 +1918,7 @@ options_act(const or_options_t *old_options) // LCOV_EXCL_STOP } - if (running_tor && rend_parse_service_authorization(options, 0) < 0) { + if (running_tor && hs_config_client_auth_all(options, 0) < 0) { // LCOV_EXCL_START log_warn(LD_BUG, "Previously validated client authorization for " "hidden services could not be added!"); @@ -3188,6 +3189,8 @@ warn_about_relative_paths(or_options_t *options) n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); n += warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("ClientOnionAuthDir", + options->ClientOnionAuthDir); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { @@ -4339,7 +4342,7 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Failed to configure rendezvous options. See logs for details."); /* Parse client-side authorization for hidden services. */ - if (rend_parse_service_authorization(options, 1) < 0) + if (hs_config_client_auth_all(options, 1) < 0) REJECT("Failed to configure client authorization for hidden services. " "See logs for details."); diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 8ef01f80e7..f6d7966387 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -380,6 +380,8 @@ struct or_options_t { struct config_line_t *HidServAuth; /**< List of configuration lines for * client-side authorizations for hidden * services */ + char *ClientOnionAuthDir; /**< Directory to keep client + * onion service authorization secret keys */ char *ContactInfo; /**< Contact info to be published in the directory. */ int HeartbeatPeriod; /**< Log heartbeat messages after this many seconds 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 diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 6ee9f40c00..6d4c847742 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -31,6 +31,16 @@ typedef enum { HS_CLIENT_FETCH_PENDING = 5, } hs_client_fetch_status_t; +/** Client-side configuration of authorization for a service. */ +typedef struct hs_client_service_authorization_t { + /* An curve25519 secret key used to compute decryption keys that + * allow the client to decrypt the hidden service descriptor. */ + curve25519_secret_key_t enc_seckey; + + /* An onion address that is used to connect to the onion service. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32+1]; +} hs_client_service_authorization_t; + void hs_client_note_connection_attempt_succeeded( const edge_connection_t *conn); @@ -63,6 +73,9 @@ void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); extend_info_t *hs_client_get_random_intro_from_edge( const edge_connection_t *edge_conn); +int hs_config_client_authorization(const or_options_t *options, + int validate_only); + int hs_client_reextend_intro_circuit(origin_circuit_t *circ); void hs_client_purge_state(void); diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index e972576482..eaeb58829a 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -27,7 +27,9 @@ #include "feature/hs/hs_common.h" #include "feature/hs/hs_config.h" +#include "feature/hs/hs_client.h" #include "feature/hs/hs_service.h" +#include "feature/rend/rendclient.h" #include "feature/rend/rendservice.h" #include "lib/encoding/confline.h" #include "app/config/or_options_st.h" @@ -613,3 +615,28 @@ hs_config_service_all(const or_options_t *options, int validate_only) /* Tor main should call the free all function on error. */ return ret; } + +/* From a set of <b>options</b>, setup every client authorization 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 + * configured state. */ +int +hs_config_client_auth_all(const or_options_t *options, int validate_only) +{ + int ret = -1; + + /* Configure v2 authorization. */ + if (rend_parse_service_authorization(options, validate_only) < 0) { + goto done; + } + + /* Configure v3 authorization. */ + if (hs_config_client_authorization(options, validate_only) < 0) { + goto done; + } + + /* Success. */ + ret = 0; + done: + return ret; +} diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h index 96eb19ee70..f443e814c4 100644 --- a/src/feature/hs/hs_config.h +++ b/src/feature/hs/hs_config.h @@ -19,6 +19,7 @@ /* API */ int hs_config_service_all(const or_options_t *options, int validate_only); +int hs_config_client_auth_all(const or_options_t *options, int validate_only); #endif /* !defined(TOR_HS_CONFIG_H) */ |