summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app/config/config.c7
-rw-r--r--src/app/config/or_options_st.h2
-rw-r--r--src/feature/hs/hs_client.c215
-rw-r--r--src/feature/hs/hs_client.h13
-rw-r--r--src/feature/hs/hs_config.c27
-rw-r--r--src/feature/hs/hs_config.h1
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) */