[tor-commits] [tor/master] hs-v3: Load client authorization secret key from file

nickm at torproject.org nickm at torproject.org
Fri Sep 7 19:06:18 UTC 2018


commit 8e81fcd51ae9b9b373f0254381728a8f4d93236d
Author: Suphanat Chunhapanya <haxx.pop at gmail.com>
Date:   Sun Aug 19 08:22:13 2018 +0700

    hs-v3: Load client authorization secret key from file
    
    The new ClientOnionAuthDir option is introduced which is where tor looks to
    find the HS v3 client authorization files containing the client private key
    material.
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
---
 src/app/config/config.c        |   7 +-
 src/app/config/or_options_st.h |   2 +
 src/feature/hs/hs_client.c     | 215 +++++++++++++++++++++++++++++++++++++++++
 src/feature/hs/hs_client.h     |  13 +++
 src/feature/hs/hs_config.c     |  27 ++++++
 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 339f8e247..ce9ae8d7c 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 8ef01f80e..f6d796638 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 1f9218e15..7c545c35d 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 6ee9f40c0..6d4c84774 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 e97257648..eaeb58829 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 96eb19ee7..f443e814c 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) */
 





More information about the tor-commits mailing list